見出し画像

CloudFormation+ECS で困っていたアプリコード変更検出をCDKで実施

はじめに

こんにちは、build サービスチームで Platform エンジニアをやっているt.g.です。
今日は、今まで CloudFormation + ECS を作ったときに地味に面倒と思っていたアプリコード変更検出について共有したいと思います。

CloudFormation + ECS の更新時に困っていたこと

ECS で動作するアプリケーションを作りたいとき、以下のような構成を行うことが多いです。
①インフラ部分を CloudFormationで定義
②アプリケーションソースコードは任意の言語で開発
③ インフラ定義とアプリソースコードを同一リポジトリに同居

この構成で、インフラ部分とアプリ部分を構築し、AWS CLIを使って ECS を更新するスクリプトを書くと、以下のような書き方になるかと思います

# アプリソースコードをビルド
docker build -t ${ECR_URI}:${SHA} .

# ECR へ新イメージを push
docker push ${ECR_URI}:${SHA}

# スタックごとECS を更新
# note: ecr_image_uri を使って taskdefinitionを更新。依存関係にある ECS Service も自動で更新
sam deploy \
  --stack-name ecs \
  --parameter-overrides \
    ECRImageURI=${ECR_URI}:${SHA}

この方法ですと、変更箇所に限らずイメージとインフラが更新されてしまいます。

というのも、Docker コンテナはビルドの度に異なる digest を持ってしまいます(気をつけて作れば同一 digest にすることは可能, 参考: [forum docker][How to digest a docker image])
これにより、ビルドの度に新しいコンテナイメージが作られるため、毎回インフラも更新されてしまいます。

もちろん、更新時にサービスのダウンタイムは発生しませんし、インフラ更新も最小限の部分しか発生しません。
それでも、変更箇所に応じてほしいなーとは思ってました。


案1: ecs.ContainerImage.fromAsset

CDK に用意されている ecs.ContainerImage.fromAsset を利用するのも手です。 こちらを使うと、cdk deploy の時点でアプリコードの変更を確認し、それに応じてインフラ・アプリ・またはその両方を更新してくれます。 変更箇所による挙動は、以下のようにまさに欲しかったものドンピシャでした。

  • アプリコードのみ変更時 -> コンテナイメージをbuild後、task-definitionとECS service を更新

  • インフラコードのみ変更時 -> 変更箇所のみ更新。ECSに関係ない変更なら、ECS/コンテナは無傷

  • アプリコード & インフラコード変更時 -> 上記両方を実施

CDK は以下のような記述になります

    /**
     * ECS task definition 設定
     * ../app の変更を検出すると、ECS task definition が変更され、新タスクが起動される
     */
    const container = taskDefinition.addContainer('Container', {
      image: ecs.ContainerImage.fromAsset('../app'),
      portMappings: [{
        containerPort: 3000,
        hostPort: 3000,
      }],
    });

なお、ecs.ContainerImage.fromAsset には以下のような挙動がありました

  • コンテナイメージの digest はアプリソースコードディレクトリの HASH 値を利用

    • アプリソースコードディレクトリのファイル構成が、以前に作成したことのあるものと同じ場合、コンテナイメージが流用される

fromAsset() 利用時の ECR

実は、fromAsset() にはこんなデメリットがあります。

  • コンテナイメージ保存先のECR を指定できない

    • `cdk bootstrap` で作成したリポジトリが問答無用で使用されます

  • コンテナイメージに自前のタグを振れない

    • (アプリディレクトリの) SHAが利用されます

自前タグは`cdk deploy` 後にスクリプトで対処できるでしょうが、ECRを指定できないのは運用上まずいことが起こりそうです。
例えば、複数 ECS service を利用した場合はどちらのイメージか判断がつかなくなります

Note: 上記画像のECR には、2個の異なるスタックからのイメージが混在しています。どちらがどちらのイメージなのか・・


案2: cdklabs/cdk-docker-image-deployment


fromAsset() の問題点に対処したのが、cdklabs/cdk-docker-image-deployment になります。

こちらを使うと、アプリソースコードの変更検知に加えて、コンテナイメージの保存先を指定しつつ、コンテナイメージに自前のタグを振ることができます

CDK コードは以下のようになります

    // Create Container Image and push to ECR
    new imagedeploy.DockerImageDeployment(this, 'Image', {
      source: imagedeploy.Source.directory('../app'),
      destination: imagedeploy.Destination.ecr(ecrRepo, {
        tag: 'myLatest'
      }),
    });

デメリットらしいデメリットは見つかりませんでしたが、以下のような挙動がありました

  • tag を指定しない場合、コンテナイメージのdigest にHASH値を利用

    • ただし、task-definition に同一の HASH を渡すメカニズムが必要

  • 付与できる tag は一個のみ

  • docker build は CodeBuild プロジェクト上で実施


まとめ


今回はECS 利用時のアプリソースコードの変更検知を行ってくれる機能について説明しました。
ざっとまとめると以下のようなメリ/デメがありますので、ECS + CDK 利用時はこちらを参考にしてみてください


CloudFormation + CLI デプロイ

メリット: 単純
デメリット: アプリコードの変更検出を自前で作成する必要

ecs.ContainerImage.fromAsset 利用

メリット: アプリコードの変更検出
デメリット: ECR, ContainerImageタグを変えられない

cdk-docker-image-deployment 利用

メリット: アプリコードの変更検出, 自前ECR/タグ可能
デメリット: 少し大掛かり(CodeBuildがバックで動作)


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!