AWS ECSのCodePipeline(blue/green CodeDeploy)におけるtaskdef.jsonをcodebuild内で作成する。

クラウド事業部の原口です。
さて、今回はblue/greenデプロイにおけるタスク定義の管理方法について少し考えてみました。

AWS ECSのCI/CD環境としてはCodePipeline(CodeCommit -> CodeBuild -> CodeDeploy )を使った自動化を利用していますが、taskdef.jsonの管理に困っていました。

困ってたこと

・インフラとアプリのリポジトリは別でtaskdef.jsonをどちらで管理するのがよいか悩んでいた
・というか、タスク定義は緊急でマネージメントコンソールからぽちぽちして修正される時があったり(!)
・stageやproductionなどの環境の違いによってどう管理していけばよいか難しい
・正直 image部分くらいしか書き換える事ほとんど無いのにタスク定義の全文を持ってないといけない

方針

以上の事から、CodeBuild内でtaskdef.jsonを生成してしまえばいいのではないかと思いました。

・codebuildにて現在のECSタスク定義の最新の定義をjsonで取得
・jqコマンドでごにょごにょして整形したり、データを置換したり、環境変数によって値を変えたり
・ビルドアーティファクトとしてtaskdef.jsonを生成してCodeDeployに渡す

アプリ側のデプロイフローにおけるタスク定義は現在のECSタスク定義の最新からimageを変更したものを利用するという形にしてしまおうという感じです。

やってみた

buildspec.ymlを以下のように設定します

version: 0.2

env:
  variables:
    AWS_REGION: "ap-northeast-1"
    AWS_ECR_DOMAIN: "xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com"
    AWS_ECR_REPO: "sample-app"
phases:
  pre_build:
    commands:
      - REPOSITORY_URI=$AWS_ECR_DOMAIN/$AWS_ECR_REPO
      # ECRにログイン
      - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ECR_DOMAIN
      # 次のECRイメージタグをCOMMIT_HASHから決定する
      - REPOSITORY_URI=$AWS_ECR_DOMAIN/$AWS_ECR_REPO
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest} 
     # AWS CLI v2 をインストール
      - curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
      - unzip -q awscliv2.zip
      - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
  build:
    commands:
      - docker build -t $AWS_ECR_REPO:app-latest -f docker/app/Dockerfile .
  post_build:
    commands:
      # push image
      - docker tag $AWS_ECR_REPO:app-latest $REPOSITORY_URI:$IMAGE_TAG
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      # Codedeployのためのbuildアーティファクトを作成する
      - IMAGE=$REPOSITORY_URI:$IMAGE_TAG
      - aws ecs describe-task-definition --task-definition sample-app-task | jq '.taskDefinition | del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities)' | jq --arg image $IMAGE '.containerDefinitions[].image=$image' > taskdef.json
artifacts:
  files:
    - taskdef.json
    - appspec.yaml

解説

codebuildのaws cliを最新にする
codebuildのaws cliはバージョンが低い事があるので最新にします。これをしていないとタスク定義のjsonに一部抜けがあったりしてうまく設定できません。

横道に逸れますがARM64のタスク定義指定ぶぶんがごっそり抜け落ちてて、x86になっており、タスクが以下のようなエラーで落ちる状態になった事で気づきました。以下のエラーはimageがbuildされたアーキテクチャとタスク定義のアーキテクチャが異なる時に出るエラーですので覚えておくといいかもしれないですね。

exec user process caused “exec format error”

こちらについては弊社の一つ前の記事をご参照ください
AWS Codebuildのコンテナイメージ”ARM64″を利用する際にAWS CLIの注意点

      - curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
      - unzip -q awscliv2.zip
      - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update

最新のタスク定義を取得してjqでごにょごにょする

aws ecs describe-task-definition --task-definition sample-app-task \
| jq '.taskDefinition \
| del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities)' \
| jq --arg image $IMAGE '.containerDefinitions[].image=$image' \
 > taskdef.json

上記のコマンドでtaskdef.jsonを生成していますが、
少し分解しながら見ていきます

aws ecs describe-task-definition --task-definition sample-app-task

aws ecs describe-task-definitionコマンドでタスク定義を取得してきます。
ですのでcodebuildのサービスロールへ上記の権限ポリシーを追加してください。
上記コマンドで最新のタスク定義がjsonで取得できるのですが、不要な項目を削除していきます。

参考 :
Amazon ECSで、サービスが参照するタスク定義のリビジョンのみを更新する

| jq '.taskDefinition \
| del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities)' \

最後に image のところに定義されている image URLを最新にpushされたimageのものに変更します。jqで環境変数を使う場合は以下のように –argで指定しないといけないみたいですね。

| jq --arg image $IMAGE '.containerDefinitions[].image=$image'

当然、image URLを直で設定しているので IMAGE1_NAME みたいな codepupelineにおけるプレースホルダを持たせる必要がありません。
同時にimageDetail.jsonの生成も不要になります。

まとめ

taskdef.jsonをcodebuildで作成する手順でした。
ここではやりませんでしたが、codebuildに与えた環境変数でtaskdef.jsonの中の値を変える事ができるようになりますので、stageやprodといった複数環境での取り回しも応用的にやっていけるのではないかと思います。

では!