CodePipelineとCodeDeployを使って、ECSへBlue/Green Deploymentをする。ただし2つ以上のDockerイメージを持ったタスク定義の場合、少しドキュメントとは異なった手法を取らなければならない。今回はTerraformのサンプルも交えつつ紹介する。
結論から言うと、imageDetail.json
は使わず、taskdef.json
をsedコマンドで書き換えて渡す。
CodeBuildで行う処理
buildspec.yml
は以下のようにする。
version: 0.2
phases:
install:
commands:
- pip3 install awscli --upgrade --user
pre_build:
commands:
- $(aws ecr get-login --no-include-email --region ap-northeast-1)
- IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION
- RP_IMAGE_URI=123456.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-rp
- WEB_IMAGE_URI=123456.dkr.ecr.ap-northeast-1.amazonaws.com/my-app-web
build:
commands:
- docker build -t $WEB_IMAGE_URI:$IMAGE_TAG -f Dockerfile_web .
- docker build -t $RP_IMAGE_URI:$IMAGE_TAG -f Dockerfile_rp .
post_build:
commands:
- docker push $RP_IMAGE_URI:$IMAGE_TAG
- docker push $WEB_IMAGE_URI:$IMAGE_TAG
- cat taskdef-production-template.json | sed -e s@\<IMAGE_RP_NAME\>@$RP_IMAGE_URI:$IMAGE_TAG@ -e s@\<IMAGE_WEB_NAME\>@$WEB_IMAGE_URI:$IMAGE_TAG@ > taskdef.json
artifacts:
files:
- appspec.yml
- taskdef.json
特別なことをやっているのはpost_buildの最後の行ぐらい。ここは用意したtaskdef-production-template.json
の中にある<IMAGE_RP_NAME>
と<IMAGE_WEB_NAM>
をsedコマンドによって新しいイメージURIに書き換えている。そしてそのまま新しくtaskdef.json
としてartifactsで出力し、CodeDeployへ渡している。
taskdef-production-template.json
は自分で作成したタスク定義の中身をそのままコピーし、image部分だけ"image": "<IMAGE_RP_NAME>"
のように書き換えておく。
appspec.yml
は以下のようにロードバランサからのリクエストを受け取るイメージ名とポートを指定しておく。
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "<TASK_DEFINITION>"
LoadBalancerInfo:
ContainerName: "rp"
ContainerPort: "80"
これを毎回CodeBuildのartifactsを渡している理由は、taskdef.json を含むアーティファクトのサイズを 3MB 未満にする必要があるからである。
CodePipelineのTerraformも参考程度に載せておく。
resource "aws_codebuild_project" "production" {
count = "${local.is_prod ? 1 : 0}"
name = "airconkingdom-com-application-production"
build_timeout = 10
service_role = "${data.terraform_remote_state.samurai_iam_user.codebuild_role_arn}"
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/golang:1.11"
type = "LINUX_CONTAINER"
privileged_mode = true
environment_variable {
"name" = "DOCKER_BUILDKIT"
"value" = "1"
}
}
source {
type = "CODEPIPELINE"
buildspec = "buildspec-production.yml"
}
}
#####################################
# CodePipeline
#####################################
resource "aws_codepipeline" "example" {
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["app-source"]
configuration {
RepositoryName = "my-app"
BranchName = "master"
}
}
}
stage {
name = "ProductionBuild"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
version = "1"
input_artifacts = ["app-source"]
output_artifacts = ["production-artifacts"]
configuration {
ProjectName = "my-app-production-build"
}
}
}
stage {
name = "ProductionDeploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CodeDeployToECS"
input_artifacts = ["production-artifacts"]
version = "1"
configuration {
ApplicationName = "my-app-production"
DeploymentGroupName = "my-app-production"
TaskDefinitionTemplateArtifact = "production-artifacts"
TaskDefinitionTemplatePath = "taskdef.json"
AppSpecTemplateArtifact = "production-artifacts"
AppSpecTemplatePath = "appspec.yml"
}
}
}
}