CircleCIでAWS CDKのデプロイパイプラインを作る

# はじめに
この記事は[CircleCI Advent Calendar 2021](https://qiita.com/advent-calendar/2021/circleci)の19日目の記事です。

自分のチームではAWS CDKを使って、インフラ管理をしています。
下記記事でAWS SAM CLIのデプロイパイプラインを構築しましたが、AWS CDKでも同様のものを作りました。

https://qiita.com/juve_534/items/244ddff045ede56cedd4

今回もCircleCIでAWS CDKのデプロイパイプラインを作っていきます。

# 全体図
デプロイフローは下記になります。
GitHubでリリースタグを作成したら、CircleCIがデプロイを動かす仕組みです。

```plantuml
@startuml
actor developer

developer -> GitHub
activate developer
activate GitHub
GitHub --> developer
deactivate developer

CircleCI -> GitHub: タグの生成を検知
activate CircleCI
deactivate GitHub

CircleCI -> CircleCI: 依存解決

CircleCI -> AWS: AWS CDKで変更セット作成
activate AWS
AWS --> CircleCI
deactivate AWS

CircleCI -> Slack: 変更セットを通知
activate Slack
Slack --> CircleCI
deactivate Slack
deactivate CircleCI

actor reviewer

reviewer -> CircleCI: 変更セット確認と承認
activate CircleCI
activate reviewer
CircleCI --> reviewer
deactivate reviewer
deactivate CircleCI

CircleCI -> AWS: AWS CDK実行
activate CircleCI
activate AWS
AWS --> CircleCI
deactivate CircleCI
deactivate AWS

@enduml
```

# 意識したポイント
## AWS CDK
### Stackの依存関係を明示する
人力でやる場合は、構築順番を意識することができますが、CIツールで実行するにはそうもいきません。
下記の記事にあるように依存関係は `addDependency` を使って明示していきます。

https://dev.classmethod.jp/articles/explicitly-define-dependencies-between-resources-in-aws-cdk-with-adddependson/

```ts
// AWS NAT Gatewayを作るStackはVPCを作成するStackとElastic IPを作成するStackに依存する
const natGateway = new NatGateway()
natGateway.node.addDependency(vpc)
natGateway.node.addDependency(eip)
```

このように依存関係を明示し、プログラムで順番通りに実行されるようにします。

### 環境で切り替える値を切り出す

環境ごとに値を切り替えたい時があると思います。

```
例えば、Amazon Route 53のプライベートゾーンに登録する関連システムのドメイン
```

こういった機微情報でないものは、cdk.jsonに切り出すようにしました。
下記のように環境ごとに構造化をし、プログラム上で読み出すようにしました。

```json:cdk.json
{
  "context": {
    "staging": {
      "hoge": {
        "fuga": "hoge"
      }
    },
    "production": {
      "hoge": {
        "fuga": "hogefuga"
      }
    }
  }
}
```

設定した値は `tryGetContext` で取得します。
パラメータは実行時に `-c env=環境名` を渡します。

```ts
const app = new cdk.App();

// 実行時に -c env=環境名 を渡す
const env: string = app.node.tryGetContext('env')

// 渡されたenvに該当する値を拾ってくる
const context = app.node.tryGetContext(env)
```

こうして、環境ごとに値を切り替える手間を省きました。

## CircleCI
### Jobをまとめる

本番環境以外に検証用の環境も用意しています。
それらの環境へのデプロイもCircleCIでやるため、ファットにならないようにJobをまとめます。

下記のようにJobは環境名を受け取るようにしておき、環境変数はWorkflowのcontextで渡すようにしました。
こうすることで同じようなJobを何度も書かなくて良くなりました。

```yaml
jobs:
  deploy:
    (中略)
      - run:
          name: deploy
          command: yarn cdk deploy "*" -c env=${APP_ENV}

workflows:
  version: 2
  default:
    jobs:
       (中略)
  deploy-staging:
    jobs:
       (中略)
      - deploy:
          context: staging
  deploy-prd:
    jobs:
      (中略)
      - deploy:
          context: production
```

### 変更セットを確認できるようにする
レビュアーに何が変更されるかわかりやすくするため、AWS CDKで変更セットを確認できるようにします。
AWS CloudFormationやAWS SAM CLIでは `--no-execute-changeset` を付け加えると、事前に変更セットの確認ができました。

AWS CDKではdiffコマンドが用意されているので、下記を実行して差分確認ができました。

```
yarn cdk diff "*"  || :
```

*余談*
自分の環境だけかもしれませんが、差分があるときに `exit 1` が返ってきて、CircleCIが落ちてしました。
※ `--fail` をつけているときは上記挙動だがつけていなくても起きました。

差分があること自体は期待値なので、 `|| :` で握りつぶすようにしました…。
この辺はモヤモヤしているので時間を見つけて調べます。

# まとめ
いかがだったでしょうか？
使い慣れているCircleCIで、AWS CDKのデプロイパイプラインを作ってみました。
参考になれば幸いです。

以上、最後まで読んでいただきありがとうございました。

# 参考資料
* [CircleCIでLambdaのデプロイを改善した話](https://qiita.com/juve_534/items/244ddff045ede56cedd4)
* [AWS CDKでリソース間の依存性をaddDependsOnで明示的に定義する](https://dev.classmethod.jp/articles/explicitly-define-dependencies-between-resources-in-aws-cdk-with-adddependson/)