如何设置 Dockerized 应用程序到 Elastic Beanstalk 的持续集成?

soa*_*gem 5 amazon-web-services jenkins docker amazon-elastic-beanstalk gitlab-ci-runner

我是 Docker 的新手,我之前的经验是将 Java Web 应用程序(在 Tomcat 容器中运行)部署到 Elastic Beanstalk。我习惯使用的管道是这样的:提交被检入 git,这会触发 Jenkins 作业,该作业构建应用程序 JAR(或 WAR)文件,将其发布到 Artifactory,然后将相同的 JAR 部署到应用程序在 Elastic Beanstalk 中使用eb deploy. (抱歉,如果“管道”是一个保留术语;我是在概念上使用它。)

顺便说一句,我还将使用 Gitlab 进行 CI/CD 而不是 Jenkins(由于我无法控制的组织原因),但是从 Jenkins 跳转到 Gitlab 对我来说似乎很简单——当然比从 Jenkins 跳转更重要直接部署 WAR 以部署 Dockerized 容器。

进入 Docker 世界,我想管道将是这样的:提交被检入 git,这会触发 Gitlab CI,然后它将构建 JAR 或 WAR 文件,将其发布到 Artifactory,然后使用Dockerfile构建Docker 映像,将该 Docker 映像发布到 Amazon ECR(也许?)...然后老实说,我不确定 Elastic Beanstalk 集成将如何从那里进行。我知道它与Dockerrun.aws.json文件有关,大概需要调用 AWS CLI。

我刚刚看完了来自 Amazon 的名为Running Microservices and Docker on AWS Elastic Beanstalk的网络研讨会,其中指出在我的存储库的根目录中应该有一个Dockerrun.aws.json文件,该文件基本上定义了与 EB 的集成。但是,JSON 文件似乎包含指向 ECR 中单个 Docker 映像的链接,这让我很不爽。每次构建新图像时,该链接不会更改吗?我想象 CI 需要动态更新 repo 中的 JSON 文件......这对我来说几乎感觉像是一种反模式。

在我上面链接的网络研讨会中,主持人创建了他的 Docker 映像并使用 CLI 手动推送了 ECR。然后他手动将Dockerrun.aws.json文件上传到EB。然而,他不需要上传应用程序,因为它已经包含在 Docker 映像中。这一切对我来说似乎很奇怪,我怀疑我是否正确理解了事情。该Dockerrun.aws.json文件是否需要在每次构建时更改?还是我想错了?

soa*_*gem 6

在我发布这个问题后的 8 个月里,我学到了很多东西,我们已经转向了不同的更好的技术。但我会发布我学到的东西来回答我原来的问题。

Dockerrun.aws.json文件几乎是完全一样的精英任务定义。使用 Beanstalk 的 Multi-Docker 容器部署版本(相对于单个容器)很重要,即使您只部署单个容器。IMO 他们应该摆脱 Beanstalk 的单容器平台,因为它非常无用。但假设您已将 Beanstalk 设置为多容器 Docker 平台,则 Dockerrun.aws.json 文件如下所示:

{
  "AWSEBDockerrunVersion": 2,
  "containerDefinitions": [
    {
      "name": "my-container-name-this-can-be-whatever-you-want",
      "image": "my.artifactory.com/docker/my-image:latest",
      "environment": [],
      "essential": true,
      "cpu": 10,
      "memory": 2048,
      "mountPoints": [],
      "volumesFrom": [],
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/aws/elasticbeanstalk/my-image/var/log/stdouterr.log",
          "awslogs-region": "us-east-1",
          "awslogs-datetime-format": "%Y-%m-%d %H:%M:%S.%L"
        }
      }
    }
  ]
}
Run Code Online (Sandbox Code Playgroud)

如果您决定在未来将整个事物转换为 ECS 服务而不是使用 Beanstalk,这将变得非常容易,因为上面的示例 JSON 通过提取“containerDefinitions”部分直接转换为 ECS 任务定义。因此等效的 ECS 任务定义可能如下所示:

[
  {
    "name": "my-container-name-this-can-be-whatever-you-want",
    "image": "my.artifactory.com/docker/my-image:latest",
    "environment": [
      {
        "name": "VARIABLE1",
        "value": "value1"
      }
    ],
    "essential": true,
    "cpu": 10,
    "memory": 2048,
    "mountPoints": [],
    "volumesFrom": [],
    "portMappings": [
      {
        "hostPort": 0,
        "containerPort": 80
      }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/aws/ecs/my-image/var/log/stdouterr.log",
        "awslogs-region": "us-east-1",
        "awslogs-datetime-format": "%Y-%m-%d %H:%M:%S.%L"
      }
    }
  }
]
Run Code Online (Sandbox Code Playgroud)

这里的主要区别在于,在 Beanstalk 版本中,您需要将端口 80 映射到端口 80,因为在 Beanstalk 上运行 Docker 的一个限制是您不能在同一实例上复制容器,而在 ECS 中则可以。这意味着在 ECS 中,您可以将容器端口映射到主机端口“零”,这实际上只是告诉 ECS 在临时范围内选择一个随机端口,这允许您在单个实例上堆叠容器的多个副本。其次用ECS如果你想传入环境变量,你需要将它们直接注入到任务定义JSON中。在 Beanstalk 世界中,您不需要将环境变量放在 Dockerrun.aws.json 文件中,因为 Beanstalk 有一个单独的工具来管理控制台中的环境变量。

事实上,Dockerrun.aws.json 文件真的应该被认为是一个模板。因为 Beanstalk 上的 Docker 在底层使用 ECS,它只是将您的 Dockerrun.aws.json 作为模板并使用它来生成自己的任务定义 JSON,从而在最终将托管环境变量注入到“environment”属性中JSON。

我第一次问这个问题时遇到的一个大问题是,每次部署时是否都必须更新这个 Dockerrun.aws.json 文件。我发现这归结为您要如何部署事物的选择。你可以,但你没有必要。如果您编写 Dockerrun.aws.json 文件以便“image”属性引用:latestDocker 映像,则无需更新该文件。您需要做的就是反弹 Beanstalk 实例(即重新启动环境),它会将任何Docker 映像拉到您的 Docker 存储库,然后使用 awscli 触发 Beanstalk 环境的重新启动,命令如下::latest从 Artifactory(或 ECR,或您发布图像的任何其他地方)可用的 Docker 图像。因此,构建管道需要做的就是发布:latest

$ aws elasticbeanstalk restart-app-server --region=us-east-1 --environment-name=myapp
Run Code Online (Sandbox Code Playgroud)

但是,这种方法有很多缺点。如果您有一个将:latest映像发布到同一存储库的 dev/unstable 分支,那么如果环境碰巧自行重新启动,您将面临部署该不稳定分支的风险。因此,我建议您对 Docker 标签进行版本控制,并且只部署版本标签。因此,不是指向my-image:latest,而是指向类似my-image:1.2.3. 这确实意味着您的构建过程必须在每次构建时更新 Dockerrun.aws.json 文件。然后你还需要做的不仅仅是一个简单的重启应用服务器。

在这种情况下,我编写了一些 bash 脚本,这些脚本利用jq 实用程序以编程方式更新 JSON 中的“图像”属性,用当前构建版本替换字符串“最新”。然后我将不得不调用 awsebcli 工具(请注意,这是与普通 awscli 工具不同的包)来更新环境,如下所示:

$ eb deploy myapp --label 1.2.3 --timeout 1 || true
Run Code Online (Sandbox Code Playgroud)

在这里,我正在做一些 hacky:eb deploy不幸的是,该命令需要 FOREVER。(这是我们切换到纯 ECS 的另一个原因;Beanstalk 慢得令人难以置信。)该命令在整个部署时间内挂起,在我们的例子中可能需要 30 分钟或更长时间。这对于构建过程来说是完全不合理的,所以我强制该过程在 1 分钟后超时(它实际上会继续部署;它只是断开我的 CLI 客户端的连接并向我返回一个失败代码,即使它随后可能会成功)。这|| true是一个 hack,它有效地告诉 Gitlab 忽略失败退出代码,并假装它成功了。这显然是有问题的,因为无法判断 Elastic Beanstalk 部署是否真的失败了;我们假设它永远不会。

关于使用的另一件事eb deploy:默认情况下,此工具将自动尝试压缩构建目录中的所有内容并将整个 ZIP 上传到 Beanstalk。你不需要那个;您只需要更新 Dockerrun.aws.json。为了做到这一点,我的构建步骤是这样的:

  • 用于使用最新版本标签jq更新Dockerrun.aws.json文件
  • 使用zip创建一个新的名为ZIP文件deploy.zip,并把Dockerrun.aws.json它里面
  • 确保调用的文件.elasticbeanstalk/config.yml到位(如下所述)
  • 运行eb deploy ...命令

然后你需要在 build 目录中.elasticbeanstalk/config.yml有一个文件,它看起来像这样:

deploy:
  artifact: deploy.zip
global:
  application_name: myapp
  default_region: us-east-1
  workspace_type: Application
Run Code Online (Sandbox Code Playgroud)

awsebcli 知道在您调用eb deploy. 这个特定文件说的是寻找一个名为 deploy.zip 的文件,而不是尝试压缩整个目录本身。

所以:latest部署方法是有问题的,因为你可能会部署一些不稳定的东西;版本化的部署方法是有问题的,因为部署脚本更复杂,并且因为除非您希望构建管道花费 30 分钟以上,否则部署有可能不会成功并且真的没有办法告诉(除了自己监控每个部署)。

无论如何,设置起来需要做更多的工作,但我建议您尽可能迁移到 ECS。(最好还是迁移到 EKS,尽管这需要做更多的工作。)Beanstalk 有很多问题。