自发布此问题以来的 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。为了做到这一点,我的构建步骤是这样的:
- 使用
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 的文件,而不是尝试压缩整个目录本身。
所以:latest 的部署方法是有问题的,因为你可能会部署一些不稳定的东西;版本化的部署方法是有问题的,因为部署脚本更复杂,并且因为除非您希望构建管道花费 30 多分钟,否则部署可能不会成功并且真的没有办法告诉(除了自己监控每个部署)。
无论如何,设置工作量更大,但我建议您尽可能迁移到 ECS。 (最好还是迁移到 EKS,尽管这需要更多的工作。)Beanstalk 有很多问题。