🫥

AWS CDKにおけるStageの活用

2025/02/16に公開

どうも河越です。

CDKにおけるStageの活用について
実際に自分がどう使っているか含めて、書いていこうと思います。

クライヤーさんがまとめてくださってるのもわかりやすいのでぜひご一緒にご覧ください

https://nixieminton.hatenablog.com/entry/2024/10/24/164512

Stageとは

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

https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/stages.html

上記、公式ドキュメントが全てなのですがもう少し噛み砕いて説明すると、Stageとは複数のStackを抽象化して扱うための概念です。
下記のように複数のリージョンのStackをまとめて管理するなどが可能です。

class MyStage extends cdk.Stage {
  constructor(scope: cdk.App, id: string, props?: cdk.StageProps) {
    super(scope, id, props);

    new cdk.Stack(this, "MyStack1", {
      env: {
        region: "us-east-1",
      },
    });

    new cdk.Stack(this, "MyStack2", {
      env: {
        region: "ap-northeast-1",
      },
    });
  }
}

なお、Stage内Stack間の依存関係は、自動で解決されます。

一方で、Stageを跨いでの依存関係を持つことはできないので、Stage間で値の参照などを行いたい場合には、SSM Parameterなど外部に保存する必要がある点、デプロイや変更時には順序性を考慮しないといけない点にも注意が必要です。

デプロイなどを実行する時は下記のように利用します

# 全部のStage、全てのStackをデプロイ
cdk deploy "**"
cdk deploy "*/*"

# 特定のStageのみをデプロイする
cdk deploy "{StageのConsturct ID}/*"

仕組みはよく分かってませんが下記は効きませんでした。
"/"が大事なんだと思います。

cdk deploy "{StageのConsturct ID}*"

具体的な利用ユースケース

複数のリージョンに跨ったStackを管理したいとき

CloudFrontを国内リージョン(ap-northeast-1)で作成、WAFを海外リージョン(us-east-1)で作成するようなケースです。

export class Waf extends cdk.Stack {
  public readonly waf: CfnWebACL;
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
  }
}

interface CloudFrontProps extends cdk.StackProps {
  waf: CfnWebACL;
}
class CloudFront extends cdk.Stack {
  constructor(scope: Construct, id: string, props: CloudFrontProps) {
    super(scope, id, props);
  }
}

const app = new cdk.App();

class Workload extends cdk.Stage {
  constructor(scope: cdk.App, id: string, props: cdk.StageProps) {
    super(scope, id, props);

    const waf = new Waf(this, "Waf", {
      crossRegionReferences: true,
      env: {
        region: "us-east-1",
      },
    });

    new CloudFront(this, "CloudFront", {
      crossRegionReferences: true,
      waf: waf.waf,
      env: {
        region: "ap-northeast-1",
      },
    });
  }
}

new Workload(app, "Workload", {
  env: {
    account: "012345678901",
  },
});

上の例では、WAFとCloudFrontの紐付けを行うためにcrossRegionReferencesを有効化しています。

別アカウントにデプロイしたいときはWorkloadのStageをアカウントの数分だけ初期化すればいいので、複数Stackだけでなく、複数のアカウント分を管理するのも容易です。

こんな感じです。

new Workload(app, "Dev", {
  env: {
    account: "012345678901",
  },
});
new Workload(app, "Stg", {
  env: {
    account: "123456789012",
  },
});
new Workload(app, "Prod", {
  env: {
    account: "234567890123",
  },
});

1つのアカウントに複数の環境を構築したい時

ごく稀に、諸々のテストの効率化のためだったり、環境占有するような試験をするためだったり、
「もう1つ環境があればな・・・」という場面はないでしょうか。私はよくあります。

本来であればアカウントをもう1つ作れれば良いのですが、
アカウント作成まで時間がかかったり、利用期間が短かったり・・・諸々の事情で1つのアカウントに複数の環境を詰め込まないとダメな場面が存在するかもしれません。

この時に気になるのが、1アカウントに1つしか作らない・作りたくないリソースが存在することです。
セキュリティ系や監査系のリソースなどがそれにあたります。

こういった場合に備えて、私は1アカウントに1つしか作らないリソースのStageと、1アカウントに複数作るであろうリソースのStageをあらかじめ明確に分けておくことで、1つのアカウント内に複数の環境を作りたい時は、後者のStageをnewするだけで簡単に増やせるようにしています。

class Baseline extends Stage {
  constructor(scope: Stage, id: string, props?: StageProps) {
    super(scope, id);
  }
}

class Workload extends Stage {
  constructor(scope: Stage, id: string, props?: StageProps) {
    super(scope, id);
  }
}

const env = {
  account: "898207152345",
};
const app = new App();
new Baseline(app, "Baseline", {
  env,
});
new Workload(app, "Dev1", {
  env,
});
new Workload(app, "Dev2", { // ここだけ増やす
  env,
});

こうすることで、万が一 炎え上がった時に 複数の環境が急遽必要になった場合にも
少ない作業時間で環境が増やせるように、CDKの書き方を工夫したりします。

最後に

実際の業務ではnpm scriptで各Stageごとのcdkコマンドを管理していたり、configをいじるだけでStageを初期化できるようにしていますが、上記のようにStageをうまく活用して、簡単に環境の管理ができるようにしています。

誰かのお役に立てば幸いです!

Discussion