CDKのステージを活用して、効率的にマルチステージデプロイを実現する方法

# 実現したこと
dev,stg,prdなどの環境のリソースについて、共通のスタックをステージで包括してそれぞれのパラメータを持たせつつ独立して管理できるようにしました。

![stgの図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3480176/85887049-5c6c-4e5f-9506-94d1f16da57e.png)




# CDKにおけるステージとは

https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/stages.html#:~:text=An%20AWS%20Cloud%20Development%20Kit,%E3%82%B0%E3%83%AB%E3%83%BC%E3%83%97%E3%82%92%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

> An AWS Cloud Development Kit (AWS CDK) stage は、一緒にデプロイするように設定された 1 つ以上の CDK スタックのグループを表します。ステージを使用し、開発、テスト、本番稼働などの複数の環境にスタックの同じグループをデプロイします

上記の公式読むとなんとなくわかると思います。コンストラクトを包括できる上位概念です。



# 実際のコードと挙動
このようなディレクトリ構成になっています

```
.
├── bin
│   └── infra.ts                      # CDKアプリのエントリーポイント
├── lib
│   ├── constructs
│   │   └── cloudtrail.ts            # Construct定義(全環境で共通のコンストラクト)
│   ├── stack
│   │   └── infra-stack.ts           # Stack定義(全環境で共通のスタック)
│   └── stage
│       ├── dev-stage.ts             # Stage定義
│       ├── prd-stage.ts             # Stage定義
│       └── stg-stage.ts             # Stage定義
├── test                             
├── cdk.json                          # CDK設定ファイル
├── parameter.ts                     # 各種パラメータ管理
~~
```


## 各環境のパラメータを保持するparameter.ts

型や変数を定義しています。それぞれのファイルでimportして使っています。

```parameter.ts
// 誤デプロイを防ぐための設定。こちらで設定したアカウント以外でdeployできないようにしています。
export const devEnv = {
  account: '123456789012', 
  region: 'ap-northeast-1'
};

export const stgEnv = {
  account: '234567890123',
  region: 'ap-northeast-1'
};

export const prdEnv = {
  account: '345678901234',
  region: 'ap-northeast-1'
};


// 環境ごとのパラメータ
export interface StackParameter {
  envName: string;
};


export const devStackParameter: StackParameter = {
  envName: 'development',
};

export const stgStackParameter: StackParameter = {
  envName: 'staging',
};

export const prdStackParameter: StackParameter = {
  envName: 'production',
};

```


## エントリーポイントのinfra.ts

ここでステージをインスタンス化します。実際にそれぞれのステージごとのパラメータを渡しているのはこの部分。

``` bin/infra.ts
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { DevStage } from '../lib/stage/dev-stage';
import { StgStage } from '../lib/stage/stg-stage';
import { PrdStage } from '../lib/stage/prd-stage';
import { devStackParameter } from '../parameter';
import { stgStackParameter } from '../parameter';
import { prdStackParameter } from '../parameter';

const app = new cdk.App();
new DevStage(app, "DevStage", {config: devStackParameter}); // 実際にパラメータを渡しているのはここ。
new StgStage(app, "StgStage", {config: stgStackParameter}); // cdk deploy "StgStage/*" コマンドで手動デプロイ
new PrdStage(app, "PrdStage", {config: prdStackParameter}); // cdk deploy "PrdStage/*" コマンドで手動デプロイ

```

コメントにも記載がありますが、デプロイは

``` terminal
cdk deploy "DevStage/*" 
```

のようなコマンドで実行することができます。
あくまでステージ配下のスタックを全てデプロイしますって感じです。



## 一番上位概念のステージ

ここでスタックにそれぞれのパラメータを渡してインスタンス化します。

```infra/lib/stage/dev-stage.ts
import * as cdk from 'aws-cdk-lib';
import { InfraStack } from '../stack/infra-stack';
import { devEnv } from '../../parameter';
import { StackParameter } from '../../parameter';

export class DevStage extends cdk.Stage {
  constructor(
    scope: cdk.App,
    id: string,
    props: cdk.StageProps&{
      config: StackParameter // ポイント。先ほど解説したinfra.tsの第3引数の部分の{config: devStackParameter}の型を指定しています。
      }) {
    super(scope, id, {
      env: devEnv, // ポイント。デプロイ先のガードレールを設定できる。
    });

    new InfraStack(this, "InfraStack", { // 実際にデプロイするスタックをインスタンス化
      config: props.config // props.config = devStackParameter。 infra.tsの第3引数の部分の{config: devStackParameter}の部分です。つまり、共通のスタックに専用のパラメータを渡すことができています。
    });
  }
}
```

### ガードレールについて補足
parameter.tsで定義した環境を定義


```parameter.ts
// 誤デプロイを防ぐための設定。こちらで設定したアカウント以外でdeployできないようにしています。
export const devEnv = {
  account: '123456789012', 
  region: 'ap-northeast-1'
};

```

このようにすることで、デプロイしようとした際に誤って異なる環境にデプロイしようとするとエラーにすることができます。

```dev-stage.ts
import { devEnv } from '../../parameter';

~~~
super(scope, id, {
      env: devEnv, // ポイント。デプロイ先のガードレールを設定できる。
    });
```




## 全環境共通のinfra-stack.ts

ステージで専用のパラメータを受け取ってインスタンス化されます。それぞれのコンストラクトにそれぞれ必要な変数を与えてデプロイすることができます。

``` lib/stack/infra-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { CloudTrail } from '../constructs/cloudtrail';
import { StackParameter } from '../../parameter';

export class InfraStack extends cdk.Stack {
  constructor(
    scope: Construct, 
    id: string, 
    props: cdk.StackProps&{
      config: StackParameter
    }) {
    super(scope, id, props);

    const { envName } = props.config; // 渡されたdevStackParameterから必要な値を変数として定義して、必要なコンストラクトに渡す。

    cdk.Tags.of(this).add('Env', envName); // おまけ、スタック配下の全てのコンストラクトのリソースにタグを追加できます。
    cdk.Tags.of(this).add('ManagedBy', 'cdk');

    new CloudTrail(this, 'CloudTrail', envName); // 定義した変数(パラメータ)を必要なコンストラクトに渡す。
    
  }
}

```