aws-codepipeline - AWS CodePipeline 与 ECS 容器使用 CodeDeploy appspec.yml 文件
问题描述
我在 CodePipeline 中使用 ECS 容器和 CodeBuild 和 CodeDeploy(用于蓝/绿部署)阶段。我在应用程序代码的根目录中有一个 appspec.yml 文件,其中包含任务定义 arn 和容器名称。所有这些在单一环境场景中运行良好。在我的情况下,当我有一个单独的用于开发、测试和生产的 AWS 账户时,我需要 CodeDeploy 来根据环境上下文交换任务定义 arn。有没有办法像我们为 buildspec.yml 文件和自定义环境变量一样传递参数和修改 appspec.yml 文件?如果不是,那么使用 appspec.yml 文件进行跨账户部署的最佳解决方案是什么?
更新
感谢 Ronan Cunningham 的 Python 脚本——请参见下面的代码示例——它允许生成 appspec.json 文件作为 CodeBuild 阶段的工件并将其作为输入传递给 CodeDeploy 阶段。您可以从 buildspec.yml 文件调用脚本并将自定义环境变量作为脚本参数传递,这将根据 AWS 环境上下文定义您的 appspec.json。该脚本应与 buildspec.yml 和 Dockerfile 一起放置在应用程序的根目录中。
create_appspec_json.py 脚本:
#!/usr/bin/python
import json
from sys import argv
def print_obj_to_disk(obj, file_type):
if file_type == 'app_spec':
file_name = 'appspec.json'
if file_type == 'task_def':
file_name = 'taskdef.json'
print('Writing {}:'.format(file_name))
print(obj)
print(json.dumps(obj, indent=2))
with open(file_name, 'w') as outfile:
json.dump(obj, outfile, indent=2)
def return_appspec(container_name, container_port, task_definition_arn):
appsec_obj = {
"version": 0.0,
"Resources": [
{
"TargetService": {
"Type": "AWS::ECS::Service",
"Properties": {
"TaskDefinition": task_definition_arn,
"LoadBalancerInfo": {
"ContainerName": container_name,
"ContainerPort": container_port
},
}
}
}
]
}
return appsec_obj
appspec_obj = return_appspec(argv[1], argv[2], argv[3])
print_obj_to_disk(appspec_obj, 'app_spec')
从 buildspec.yml 调用脚本并将环境变量作为参数传递。
artifacts:
files:
- appspec.json
phases:
install:
commands:
- pip install boto3
build:
commands:
- python create_appspec_json.py $CONTAINER_NAME $CONTAINER_PORT $TASK_DEFINITION_ARN
...
post_build:
commands:
...
pre_build:
commands:
...
version: 0.2
解决方案
我的方法(听起来像是相同的管道设置)是在具有 CodeDeployToECS 操作的阶段之前有一个具有 CodeBuild 操作的阶段。前者的工作是以编程方式生成应用规范和任务定义,后者的 OutputArtifact 和 InputArtifact。
任何必需的参数都通过管道操作配置传递给 CodeBuild 项目。
更新:
基本方法如下所示:
codedeploy 之前的 codebuild 项目运行 python 脚本来生成 appspec 和任务定义,根据需要查找值并将它们作为 json 文件写入磁盘。这不是完整的脚本,只是 app_spec 部分作为示例。
def print_obj_to_disk(obj,type):
if type=='app_spec':
file_name='appspec.json'
if type=='task_def':
file_name='taskdef.json'
print('Writing {}:'.format(file_name))
print(obj)
print(json.dumps(obj, indent=2))
with open(file_name, 'w') as outfile:
json.dump(obj, outfile,indent=2)
def return_appspec(container_name,container_port,AfterAllowTestTrafficHookFunctionName):
appsec_obj= {
"version": 0.0,
"Resources": [
{
"TargetService": {
"Type": "AWS::ECS::Service",
"Properties": {
"TaskDefinition": "<TASK_DEFINITION>",
"LoadBalancerInfo": {
"ContainerName": container_name,
"ContainerPort": container_port
},
}
}
}
],
"Hooks": [
{
"AfterAllowTestTraffic": AfterAllowTestTrafficHookFunctionName
}
]
}
return appsec_obj
appspec_obj=return_appspec('my_container',8080,'FunctionName')
print_obj_to_disk(appspec_obj,'app_spec')
该项目处于下面管道定义示例部分的“PrepareCodeDeploy”阶段。json 文件存储为管道工件,然后成为下一阶段的输入。
- Name: PrepareCodeDeploy
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: "1"
Configuration:
ProjectName: !ImportValue CodeDeployPrepareProjectName
EnvironmentVariables: !Sub "[{\"name\":\"Environment\",\"value\":\"Testing\",\"type\":\"PLAINTEXT\"},{\"name\":\"Account_Id\",\"value\":\"${TestingAccountId}\",\"type\":\"PLAINTEXT\"},{\"name\":\"Region\",\"value\":\"${DeployRegion1}\",\"type\":\"PLAINTEXT\"},{\"name\":\"BucketKmsKey\",\"value\":\"${TestingAccountKmsKeyId}\",\"type\":\"PLAINTEXT\"}]"
InputArtifacts:
- Name: !Ref SourceArtifactName
OutputArtifacts:
- Name: PrepareCodeDeployOutputTesting
RunOrder: 3
- Name: BlueGreenDeploy
InputArtifacts:
- Name: BuildDockerOutput
- Name: PrepareCodeDeployOutputTesting
Region: !Ref DeployRegion1
ActionTypeId:
Category: Deploy
Owner: AWS
Version: '1'
Provider: CodeDeployToECS
RoleArn: !Sub arn:aws:iam::${TestingAccountId}:role/whatever/CrossAccountsDeploymentRole
Configuration:
AppSpecTemplateArtifact: PrepareCodeDeployOutputTesting
AppSpecTemplatePath: appspec.json
ApplicationName: !Ref ApplicationName
DeploymentGroupName: !Ref ApplicationName
TaskDefinitionTemplateArtifact: PrepareCodeDeployOutputTesting
TaskDefinitionTemplatePath: taskdef.json
Image1ArtifactName: BuildDockerOutput
Image1ContainerName: "IMAGE1_NAME"
RunOrder: 4
希望这会给您一些关于如何以适合您的方式工作的想法。我刚刚决定写一篇关于我的方法的博客文章,虽然并不完美,但确实有效,并遵循了一条以线性方式创建管道和所有必需组件的路径。官方文档似乎有很多方向,我看到很多人都在为此苦苦挣扎。
任何进一步的问题,只是问。
推荐阅读
- ios - 在 AlertController 中从 UIPickerView 填充文本字段时出现问题
- android - 关于Android Studio中Java中CheckBox的问题
- android - 从包中检索传递的参数时,为什么 Android Studio 会发出警告并建议使用 requireArguments() 而不是参数!!?
- jquery - 如何将格式更改为范围滑块的巴西标准
- powershell - 使用 Powershell 脚本将 .exe 从网络共享复制到远程计算机并安装 .exe 时出错
- scala - 如何将初始值从具有函数链接的过滤列表中传递给 foldLeft?
- python - 是否可以以编程方式从数据包中重新创建请求?
- r - Phyloseq sample_data() 和 sample_names() 无法将样本合并到 phyloseq 对象中
- python - 删除 pandas 中的重复项,但保存其他列 PANDAS 中的值
- c# - 部署 ASP.NET Core Docker 项目 - 收到 405 错误(在我的 IIS 本地,Web 请求有效)。如何解决?