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

Posted

技术标签:

【中文标题】如何设置 Dockerized 应用程序与 Elastic Beanstalk 的持续集成?【英文标题】:How can I set up Continuous Integration of a Dockerized application to Elastic Beanstalk? 【发布时间】:2018-08-25 13:48:45 【问题描述】:

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

顺便说一句,我还将使用 Gitlab 进行 CI/CD 而不是 Jenkins(由于我无法控制的组织原因),但从 Jenkins 到 Gitlab 的跳跃对我来说似乎很简单——当然比从直接部署 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 的网络研讨会,其中指出在我的 repo 的根目录中应该有一个 Dockerrun.aws.json 文件,它基本上定义了与 EB 的集成。但是,似乎 JSON 文件包含指向 ECR 中单个 Docker 映像的链接,这让我很失望。每次构建新图像时,该链接不会更改吗?我想象 CI 需要动态更新 repo 中的 JSON 文件......这对我来说几乎是一种反模式。

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

【问题讨论】:

你的问题解决了吗?如果可以,您可以发布答案吗? 是的,是的。自从我在 8 个月前发布这篇文章以来,我学到了很多东西,实际上我们已经从 Elastic Beanstalk 完全迁移到 ECS,现在我们正在再次迁移到 Kubernetes (EKS)。使用后,我强烈建议避免像瘟疫一样避免使用 Elastic Beanstalk。但我还是会写下我学到的东西。 太好了,我和你走的路一模一样。请在发布答案时标记我,顺便说一句 +1 在您的问题上,很快就会在您的答案上 @FedericoPiazza 我的答案贴在下面。 【参考方案1】:

自发布此问题以来的 8 个月中,我学到了很多东西,并且我们已经转向了不同且更好的技术。但我会发布我在回答我原来的问题时学到的知识。

Dockerrun.aws.json 文件与 ECS 任务定义几乎完全相同。使用 Beanstalk 的多 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"
        
      
    
  ]

如果您决定在未来将整个事物转换为 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"
      
    
  
]

这里的主要区别在于,对于 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,它将托管环境变量注入到最终的“环境”属性中JSON。

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

$ aws elasticbeanstalk restart-app-server --region=us-east-1 --environment-name=myapp

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

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

$ eb deploy myapp --label 1.2.3 --timeout 1 || true

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

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

使用jqDockerrun.aws.json 文件更新为最新版本标签 使用zip 创建一个名为deploy.zip 的新ZIP 文件并将Dockerrun.aws.json 放入其中 确保有一个名为 .elasticbeanstalk/config.yml 的文件已就位(如下所述) 运行eb deploy ... 命令

然后你需要一个位于.elasticbeanstalk/config.yml 的构建目录中的文件,如下所示:

deploy:
  artifact: deploy.zip
global:
  application_name: myapp
  default_region: us-east-1
  workspace_type: Application

当您调用 eb deploy 时,awsebcli 知道会自动查找此文件。这个特定文件的意思是寻找一个名为 deploy.zip 的文件,而不是尝试压缩整个目录本身。

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

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

【讨论】:

为什么觉得单容器不好?我正在考虑使用它。 @titus 因为那样你就永远把自己锁在只部署一个容器上。最好能够灵活地添加更多内容,而无需重新创建整个 EB 堆栈。即使不是今天,你也永远不知道什么时候需要一辆边车。

以上是关于如何设置 Dockerized 应用程序与 Elastic Beanstalk 的持续集成?的主要内容,如果未能解决你的问题,请参考以下文章

vuejs 应用程序不使用 dockerized npm 运行

如何让两个 dockerized 应用程序在给定端口上通信?

无法通过弹性搜索与 dockerized 进程进行通信,并显示“所有已配置的节点都不可用”

如何在 gitlab CI 中访问被测试的 dockerized 应用程序

如何将 -mem 传递给 dockerized Play 应用程序

使用python设置dockerized Couchdb