[AWS CDK] コンテナイメージもまとめてデプロイ！？DockerImageAssetの動作確認をしてみた

# AWS CDKとは

AWSリソースをアプリケーションコード (TypeScript / Python / Java / C#) で定義できる，AWS公式のInfrastructre as a Code (IaC)ツールです．

構造化ファイル(YAML / JSON)を使ってリソース定義を行うCloudFormationと比較すると，

- 制御構文や抽象化を使うことで，比較的少ないコード量でリソース定義しやすい
- エディタによるエラーチェック・サジェスチョン・ドキュメント参照がしやすい

等のメリットがあります．
概要・特色を知りたい方は，[Black Belt Online Seminarの資料](https://aws.amazon.com/jp/blogs/news/webinar-bb-aws-cloud-development-kit-cdk-2020/)がオススメです．
また，試してみたい方は，[CDK Workshop](https://cdkworkshop.com/)から入るのがオススメです．

# CDK Docker Image Assetsとは

CDKでは，各種AWSリソースとともに，Dockerfileからコンテナイメージをビルドして，`cdk deploy`コマンドで一緒にデプロイすることができます．
これを用いると，例えば，ECS環境とアプリケーションのコンテナイメージを１つのリポジトリで管理でき，負担軽減される場面もあるかも？と思います．

ちょうど業務でそのような場面があり使ってみた，のですが．．．
[API Reference](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html)を読むと，それらしきクラスやメソッドが色々なところに存在し，ぱっと見どれを使えば良いか分からない！どのリポジトリにpushされるかもよく分からない！
そこで，簡単なテストスタックを作って，どのようにコンテナイメージがpushされるか確認してみました．

# 検証環境

- CDK 1.59.0 (2020/08/17時点での最新バージョン[^1])
- TypeScriptで実装

> CDKは頻繁に[アップデート](https://github.com/aws/aws-cdk/releases)が行われ，2,3日でバージョンが上がることもザラにあります．この記事に関わる部分も破壊的変更が行われる可能性もあり，こまめに最新情報をチェックするのをオススメします．

# 方法1 [aws-ecr-assets module](https://docs.aws.amazon.com/cdk/api/latest/docs/aws-ecr-assets-readme.html)を使う

はじめに見つけた方法です．下記のコード抜粋のように，Dockerfileがあるディレクトリを指定して，DockerImageAssetを作ってあげるだけでOKです．

```ts
import * as cdk from '@aws-cdk/core';
import assets = require('@aws-cdk/aws-ecr-assets');

export class AssetsStack extends cdk.Stack {

    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        
        new assets.DockerImageAsset(this, `docker-image`, {
          directory: path.join(__dirname, 'containers'), // Dockerfileがあるディレクトリを指定
        });
    }
}
```

しかし，この方法，ECRリポジトリやコンテナイメージのタグを定義してないけど，どこへpushされるのだろう？？？

スタックをデプロイすると，`aws-cdk/assets`というECRリポジトリが自動的に作成され，その中にビルドしたイメージがpushされていました．イメージタグはCDKが自動的に採番するようです．

![ecr-repos-wo-specify.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495439/3a6e080c-c249-7250-ae67-0307ca1a032d.jpeg)

お手軽で良いのだけど，特定のECRリポジトリにpushしたい場合や，イメージタグを指定したい場合はどうするのだろう…？ [DockerImageAsset](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-ecr-assets.DockerImageAsset.html)のコンストラクタには，

- repositoryName (push先のリポジトリを指定)
- extraHash (イメージタグを指定)

という引数が存在するのですが，既に非推奨となっており，将来的に削除される可能性があります．
また，本検証では，これらの引数を指定すると，イメージがpushされませんでした．
ということで，push先のリポジトリ・イメージタグを指定するには，方法2を使う必要があります．

# 方法2 [StackSynthesizer](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.IStackSynthesizer.html)を使う

下記のコード抜粋のように，`Stack.synthesizer.addDockerImageAsset`を使って定義します．

```ts
import * as cdk from '@aws-cdk/core';
import assets = require('@aws-cdk/aws-ecr-assets');

export class AssetsStack extends cdk.Stack {

    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        
        this.synthesizer.addDockerImageAsset({
            directoryName: path.join(__dirname, 'app1'), // Dockerfileがあるディレクトリを指定,
            repositoryName: "my-ecr-repos", // ECRリポジトリ名を指定
            sourceHash: "image-tag", // イメージタグを指定.
        });
}
```

所望のリポジトリにコンテナイメージがpushされています．

![ecr-repos.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/495439/8598e826-6f5d-b9de-2e39-8ef9092b8fe3.jpeg)

## 方法2の注意点

- Dockerfileを書き換えても，sourceHashを変更しなければ，同じタグのイメージが新たなイメージで上書きされるようです．前のバージョンも残しておきたい場合は，都度sourceHashを変更する必要があります．私は，gitのコミットハッシュからイメージタグを決定し，CDK context valueを通してsourceHashを与えるようにしました．

- 同一スタック内で複数回`addDockerImageAsset`を行った場合，最後に行った指定のみが有効になりました．複数のリポジトリに色々なコンテナイメージをpushしたい場合に注意が必要です．

```ts
import * as cdk from '@aws-cdk/core';
import assets = require('@aws-cdk/aws-ecr-assets');

export class AssetsStack extends cdk.Stack {

    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        
        // １つ目のイメージ．./app1/Dockerfileを使って，my-repos1へtestタグでpushしたい．
        this.synthesizer.addDockerImageAsset({
            directoryName: path.join(__dirname, 'app1'),
            repositoryName: "my-repos1",
            sourceHash: "test",
        });

        // ２つ目のイメージ．./app2/Dockerfileを使って，my-repos2へtestタグでpushしたい．
        this.synthesizer.addDockerImageAsset({
            directoryName: path.join(__dirname, 'app2'),
            repositoryName: "my-repos2",
            sourceHash: "test",
        });

       // この場合，２つ目のイメージだけがpushされます．
       // 両方ともpushしたい場合，スタックを分ければOK．
       // 同一スタック内での複数イメージpushは未検証 (イメージ1, 2でsourceHashを変えればよいのか…？）
}
```

# ソースコード

- [GitHub | 検証スタックのCDKコード](https://github.com/nasubeee/cdkstudy-docker-assets-test)
- [GitHub | 検証スタックのデプロイパイプライン](https://github.com/nasubeee/cdkstudy-docker-assets-test-pipeline)

# パイプライン(CodeBuild)でデプロイする場合の注意点

[CodeBuild](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2017-aws-code-services-codecommit-codebuild)では，都度ビルド用コンテナ環境が作成され，その中で`buildspec.yml`に記述されたコマンドが実行されます．コンテナイメージのビルドでは，`docker-in-docker`を行うため，[Privilegedの有効化](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/change-project.html)が必要です．

# まとめ

方法1 (aws-ecr-assets module)は，現状`EXPERIMENTAL`指定となっており，非推奨機能も多いようです．
今後は，方法2 (Stack.synthesizer)に移行していきそうな雰囲気がありました．


[^1]: 2020/08/22時点では，ver1.60.0がリリースされていました．