header
HikariBlog
Webエンジニア向けブログ
インフラ

【AWS】CodeBuildからCloudFormationを操作してECSへのデプロイ!【スタックの更新】

operate-cfn-from-codebuild

今回はこのような質問をいただきました。

CloudFormationですべて管理してるみたいだけど、どうやってデプロイしてるの?

私がインフラを担当した案件で「インフラのすべての管理をCloudFormationで行う」という要件の案件がありました。

そこで、その案件ではどのようにデプロイを行っているのかという質問がきました。

今回はこちらの質問について解説していきます!

前提条件

  • CloudFormationでECSを構築していること
  • CloudFormationのパラメータでイメージタグを指定できること

インフラ構成

本題はどのようにCloudFormationを更新しているかという部分になりますが、その前にインフラ構成を載せておきます。

CloudFormationのパラメータでECSのイメージタグを管理しています。

そのため、デプロイするにはCloudFormationのパラメータを変更する必要があります。

また、CloudFormationテンプレートはS3に配置されているものとします。

インフラ構成図

流れとしては以下のようになっています。

  1. GitHub ActionsからECRへイメージをプッシュ
  2. ECRへプッシュされたことをトリガーにCodePipelineを実行
  3. CodePipelineでCodeBuildを実行
  4. CodeBuildからCloudFormationのパラメータを変更

正直、CodePipelineやCodeDeployからデプロイしたほうが遥かに簡単です。
CloudFormation縛りでもない限りはおすすめできないです…

CodeBuildからCloudFormationのパラメータを変更する

では今回のメインである「CodeBuildからCloudFormationのパラメータを変更」について解説していきます。

CodeBuildからということでbuildspec.ymlの解説になります。

他のデプロイステップはよく使われる方法なので今回は特に解説は行いません。

CodeBuildでは以下のことを行っています。

  1. AWS CLI v2のインストール
  2. ECRから最新のイメージタグを取得する
  3. CloudFormationのパラメータを作成する
  4. CloudFormationを更新する

各ステップはphasesで別れています。

  • ECRリポジトリ名:sample-repository
  • CloudFormationスタック名:sample-stack
  • CloudFormationテンプレート配置S3バケット名:sample-bucket
version: 0.2
phases:
  install:
    commands:
      # AWS CLI v2
      - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
      - unzip awscliv2.zip
      - ls -l /root/.pyenv/shims/aws
      - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
  pre_build:
    commands:
      # GET IMAGE TAG
      - IMAGE_DIGEST=$(aws ecr list-images --repository-name sample-repository --query "imageIds[?imageTag=='latest'] | [0].imageDigest")
      - IMAGE_DIGEST=`echo $IMAGE_DIGEST | sed "s/\"/'/g"`
      - IMAGE_TAG=$(aws ecr list-images --repository-name sample-repository --query "imageIds[?imageDigest==$IMAGE_DIGEST && imageTag!='latest'] | [0].imageTag")
  build:
    commands:
      # CREATE PARAMETERS
      - 'PARAMETERS=$(aws cloudformation describe-stacks --output json --stack-name sample-stack | jq ".Stacks[].Parameters  |= .+[{ \"ParameterKey\": \"ImageTag\", \"ParameterValue\": $IMAGE_TAG }]" | jq ".Stacks[].Parameters")'
      - 'INPUT="{ \"StackName\": \"sample-stack\", \"TemplateURL\": \"https://sample-bucket.s3-ap-northeast-1.amazonaws.com/cfn/application/master.yaml\", \"Parameters\": $PARAMETERS }"'
      - echo $INPUT > input.json
      - cat ./input.json | jq .
  post_build:
    # UPDATE
    commands:
      - aws cloudformation update-stack --cli-input-json file://./input.json

次に詳しく見ていきます。

AWS CLI v2のインストール

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

プラットフォームはAmazon Linux 2を使っていますが、AWS CLI v1しか入っていないのでAWS CLI v2をインストールします。

こちらの解説は特に不要ですね。

ECRから最新のイメージタグを取得する

aws ecr list-iamgesコマンドでECRのイメージ一覧を取得します。

実行すると以下のようなJSONを取得できます。

{
    "imageIds": [
        {
            "imageDigest": "sha256:99c6fb4377e9a420a1eb3b410a951c9f464eff3b7dbc76c65e434e39b94b6570",
            "imageTag": "latest"
        },
        {
            "imageDigest": "sha256:99c6fb4377e9a420a1eb3b410a951c9f464eff3b7dbc76c65e434e39b94b6570",
            "imageTag": "v1.13.8"
        },
        {
            "imageDigest": "sha256:99c6fb4377e9a420a1eb3b410a951c9f464eff3b7dbc76c65e434e39b94b6570",
            "imageTag": "v1.13.7"
        },
        {
            "imageDigest": "sha256:4a1c6567c38904384ebc64e35b7eeddd8451110c299e3368d2210066487d97e5",
            "imageTag": "v1.13.6"
        }
    ]
}

このJSONから最新のイメージタグを取得します。

一般的には最新のイメージタグはlatestとvX.X.Xの2つが存在すると思います。

そのうちのlatestではないほうを取得します。

このサンプルJSONの場合はv1.13.8となります。

      - IMAGE_DIGEST=$(aws ecr list-images --repository-name sample-repository --query "imageIds[?imageTag=='latest'] | [0].imageDigest")

1回でイメージタグを取得するのは困難なため、まずは最新イメージのimageDigestを取得します。

iamgeTag==‘latest’のイメージのimageDigestを取得します。

      - IMAGE_DIGEST=`echo $IMAGE_DIGEST | sed "s/\"/'/g"`

取得した値の前後にダブルクォートが含まれてしまっているのでsedコマンドで取り除きます。

      - IMAGE_TAG=$(aws ecr list-images --repository-name sample-repository --query "imageIds[?imageDigest==$IMAGE_DIGEST && imageTag!='latest'] | [0].imageTag")

最後にimageDigest==[取得したimageDigest]、imageTag!=’latest’のイメージのimageTagを取得します。

CloudFormationのパラメータを作成する

aws cloudformation describe-stacksコマンドでCloudFormationのスタック情報を取得して、その中からパラメータの情報を抽出します。

それと同時に先ほど取得したイメージタグの情報をパラメータにセットします。

その後、すべてのパラメータをJSONファイルに出力しておきます。

      - 'PARAMETERS=$(aws cloudformation describe-stacks --output json --stack-name sample-stack | jq ".Stacks[].Parameters  |= .+[{ \"ParameterKey\": \"ImageTag\", \"ParameterValue\": $IMAGE_TAG }]" | jq ".Stacks[].Parameters")'

スタック名を指定してスタック情報からパラメータ情報を抽出します。

取得したパラメータ情報に先ほど取得したイメージタグの情報を追加します。

作成したパラメータ情報には、もともと存在したイメージタグの情報と新たに追加したイメージタグの情報の2つが存在することになりますが、後勝ちとなるので追加したイメージタグの情報が優先されます。

そのため、もともと存在したイメージタグの情報の削除は不要です。

      - 'INPUT="{ \"StackName\": \"sample-stack\", \"TemplateURL\": \"https://sample-bucket.s3-ap-northeast-1.amazonaws.com/cfn/application/master.yaml\", \"Parameters\": $PARAMETERS }"'

取得したパラメータ情報に、スタック名、テンプレートURL(テンプレートの配置先)を追加してCloudFormation更新用のパラメータを作成します。

      - echo $INPUT > input.json

取得したJSONをinput.jsonに書き込みます。

      - cat ./input.json | jq .

エラーが起きたときのためにパラメータをログを出力しておきます。

CloudFormationを更新する

      - aws cloudformation update-stack --cli-input-json file://./input.json

最後に先ほど出力したJSONを指定してCloudFormationを更新します。

これでCloudFormationのスタック更新が始まるのでCloudFormationが正常であればデプロイが完了します。

まとめ

デプロイ全体の流れは以下のようになります。

  1. GitHub ActionsからECRへイメージをプッシュ
  2. ECRへプッシュされたことをトリガーにCodePipelineを実行
  3. CodePipelineでCodeBuildを実行
  4. CodeBuildからCloudFormationのパラメータを変更

また、CodeBuildでは以下のようなことを行っています。

  1. AWS CLI v2のインストール
  2. ECRから最新のイメージタグを取得する
  3. CloudFormationのパラメータを作成する
  4. CloudFormationを更新する

あとがき

CloudFormationでイメージタグの管理まで行うは少し大変ですね。

環境構築までをCloudFormationに任せて、デプロイはCodePipeline、CodeDeployに任せたいです。

活用する場面は少ないと思いますが、機会があれば参考にしてみてください!