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

时间:2018-03-16 13:13:04

标签: amazon-web-services docker jenkins elastic-beanstalk gitlab-ci-runner

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

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

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

我刚刚观看了亚马逊的一个名为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文件是否需要在每次构建时更改?或者我是否以错误的方式思考这个问题?

1 个答案:

答案 0 :(得分:2)

自从我发布此问题以来的8个月里,我学到了很多东西,并且我们已经开始采用其他更好的技术。但是我将发布我对最初问题的回答所学到的知识。

Dockerrun.aws.json 文件与ECS任务定义几乎完全相同。即使您仅部署单个容器,使用Beanstalk的Multi-Docker容器部署版本(而不是单个容器)也很重要。 IMO,他们应该摆脱Beanstalk的单一容器平台,因为它毫无用处。但是,假设您已将Beanstalk设置为Multi-Container 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,需要将它们直接注入到Task Definition JSON中。在Beanstalk的世界中,您不需要将环境变量放入Dockerrun.aws.json文件中,因为Beanstalk在控制台中具有用于管理环境变量的单独工具。

事实上,Dockerrun.aws.json文件实际上应该被认为是模板。由于Beanstalk上的Docker在后台使用ECS,因此只需将Dockerrun.aws.json作为模板,并使用它生成自己的Task Definition JSON,即可将托管环境变量注入到最终的“ environment”属性中。 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

但是,这种方法有很多缺点。如果您有一个将:latest映像发布到同一存储库的dev / unstable分支,则当环境碰巧自行重启时,您就有部署该不稳定分支的风险。因此,我建议对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是一种可有效告诉Gitlab忽略失败退出代码并假装成功的黑客。这显然是有问题的,因为无法确定Elastic Beanstalk部署是否确实失败了。我们以为它永远不会。

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

  • 使用jq使用最新版本标签更新Dockerrun.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的文件,而不是尝试将整个目录本身压缩为ZIP。

因此,:latest部署方法存在问题,因为您可能会部署不稳定的内容;版本化的部署方法存在问题,因为部署脚本更加复杂,并且因为除非您希望构建管道花费30分钟以上的时间,否则部署可能不会成功,并且实际上没有办法(自己监视每个部署。)

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