AWS CDKでインフラコードを書き始めると、最初はVPCもEC2もRDSも1つのStackにまとめてしまいがちです。しかしリソースが増えるにつれ、アプリの小さな変更でもネットワーク層まで全体が更新対象になり、デプロイ時間の膨張とロールバック影響範囲の拡大が深刻になってきます。
この記事では、AWS CDKのスタック分割設計を実践的に解説します。VPC層・セキュリティ層・アプリ層の3層分割の判断基準、Stack Propsによる型安全なスタック間リソース受け渡し、CDK Contextを使ったdev・stg・prod環境差分の持たせ方まで、TypeScriptのコード例と
cdk deployコマンドの実行結果を交えて解説します。この記事のポイント
・1スタックにすべて詰め込むとデプロイ遅延とBlast Radiusが拡大する
・VPC層・セキュリティ層・アプリ層の3層分割がスタック設計の基本型
・スタック間のリソース参照はStack Propsで型安全に渡す
・環境差分(AZ/CIDR)はcdk.jsonのContextに定義して--contextで上書き
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ「1スタック設計」が現場で問題になるのか
CDKアプリを単一Stackで管理していると、規模が大きくなるにつれて3つの問題が顕在化します。・デプロイの遅延:CloudFormationは全リソースを差分評価するため、数百リソース規模では10分以上かかることがある
・Blast Radiusの拡大:アプリコードの変更でもVPCやIAMリソースまでデプロイ対象になってしまう
・CloudFormationの500リソース上限:1スタックのリソース数上限は500。スタックを分割しないと到達しやすい
スタックを適切に分割することで、「この変更はAppStackだけに影響する」と確信を持ってデプロイでき、デプロイ速度とシステムの安全性が同時に向上します。
スタック分割の3つの判断基準
どこでスタックを切るかは、以下の3つの軸で判断します。1. ライフサイクルで分ける(変更頻度の違い)
最も実務で使われる分割基準です。変更頻度の低い「安定層」と、頻繁に変わる「アプリ層」を分けます。・NetworkStack(安定層):VPC・サブネット・インターネットゲートウェイ・NATゲートウェイ。プロビジョニングしたら滅多に変更しない
・SecurityStack(安定層):セキュリティグループ・IAMロール・KMSキー。セキュリティポリシーの変更は慎重に行うため頻度が低い
・AppStack(変動層):EC2・ECS・Lambda・RDS・ALB。機能追加のたびに変更が発生する
この分割により、アプリの新機能デプロイ時にNetworkStackとSecurityStackを触らずに済み、デプロイ時間が大幅に短縮されます。
2. チームの境界で分ける(オーナーシップ分離)
インフラチームとアプリチームが分かれている場合、スタックの境界をチームの責任範囲に合わせると権限管理が明確になります。インフラチームがNetworkStackとSecurityStackを管理し、アプリチームがAppStackをデプロイするという分離が可能になります。IAMポリシーでスタック別のデプロイ権限を付与する際にも、スタックが分割されていることが前提になります。3. 環境横断リソースと環境固有リソースで分ける
マルチアカウント設計では、AWSアカウントをまたいで共有するリソース(Transit Gateway・RAM共有サブネット等)を独立したSharedInfraStackに切り出し、各環境固有のアプリリソースとは切り分けます。AWSのLinuxサーバー環境構築をAWS CDKで自動化したい場合のEC2・ELB設計については、AWSでAmazon Linuxサーバーを構築する実践手順も参考にしてください。Stack Propsでスタック間のリソースを型安全に渡す
CDKでスタックを分割する際、スタック間のリソース参照はStack Propsパターンを使うのが定石です。CloudFormationのOutputsエクスポートを使う方法(CfnOutput + Fn.importValue)よりも型安全で、「Export cannot be updated as it is in use by another stack」エラーも発生しません。1. NetworkStackでVPCを定義して外部に公開する
// lib/network-stack.ts import { Stack, StackProps } from 'aws-cdk-lib'; import { Vpc, SubnetType, IpAddresses } from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; export class NetworkStack extends Stack { // publicプロパティとして外部スタックに公開する readonly vpc: Vpc; constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); this.vpc = new Vpc(this, 'MainVpc', { ipAddresses: IpAddresses.cidr('10.0.0.0/16'), maxAzs: 2, subnetConfiguration: [ { name: 'Public', subnetType: SubnetType.PUBLIC, cidrMask: 24 }, { name: 'Private', subnetType: SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 }, ], }); } }
2. AppStackでStack Propsを通じてVPCを受け取る
// lib/app-stack.ts import { Stack, StackProps } from 'aws-cdk-lib'; import { Vpc, SecurityGroup, Port } from 'aws-cdk-lib/aws-ec2'; import { Construct } from 'constructs'; // NetworkStackから受け取るPropsを型定義する interface AppStackProps extends StackProps { vpc: Vpc; } export class AppStack extends Stack { constructor(scope: Construct, id: string, props: AppStackProps) { super(scope, id, props); // props.vpc でNetworkStackのVPCを型安全に参照できる const appSg = new SecurityGroup(this, 'AppSg', { vpc: props.vpc, description: 'App server security group', }); appSg.addIngressRule(appSg, Port.tcp(8080), 'App internal'); } }
3. エントリーポイントでスタックを組み立てる
// bin/app.ts import { App } from 'aws-cdk-lib'; import { NetworkStack } from '../lib/network-stack'; import { AppStack } from '../lib/app-stack'; const app = new App(); const env = { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }; // NetworkStackを先にインスタンス化してVPCを取り出す const networkStack = new NetworkStack(app, 'NetworkStack', { env }); // AppStackにnetworkStack.vpcを直接渡す(型安全) new AppStack(app, 'AppStack', { vpc: networkStack.vpc, env, });
cdk deploy NetworkStack AppStackでデプロイ順序が自動解決され、AppStackがNetworkStackのVPCを参照できます。スタック間でループしない一方向の依存関係が保たれるため、CDKが依存グラフを正しく解析できます。CDK Contextで環境差分(dev/stg/prod)を持たせる
AZの数・CIDRレンジ・インスタンスタイプといった環境差分は、CDK Contextを使ってcdk.jsonに定義し、コードからthis.node.tryGetContext()で取り出す設計が実務の標準パターンです。環境変数やif/elseの直書きより保守性が高く、CIパイプラインとの相性も良好です。1. cdk.jsonに環境ごとのContextを定義する
// cdk.json(プロジェクトルートに配置) { "app": "npx ts-node --prefer-ts-exts bin/app.ts", "context": { "dev": { "vpcCidr": "10.0.0.0/16", "maxAzs": 2, "instanceType": "t3.small" }, "stg": { "vpcCidr": "10.1.0.0/16", "maxAzs": 2, "instanceType": "t3.medium" }, "prod": { "vpcCidr": "10.2.0.0/16", "maxAzs": 3, "instanceType": "m6i.large" } } }
2. スタック内でContextを読み取る
// lib/network-stack.ts(Context読み取り版) export class NetworkStack extends Stack { readonly vpc: Vpc; constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // --context envName=prod で切り替え。デフォルトは 'dev' const envName = (this.node.tryGetContext('envName') as string) ?? 'dev'; const envConfig = this.node.tryGetContext(envName) as { vpcCidr: string; maxAzs: number; }; this.vpc = new Vpc(this, 'MainVpc', { ipAddresses: IpAddresses.cidr(envConfig.vpcCidr), maxAzs: envConfig.maxAzs, subnetConfiguration: [ { name: 'Public', subnetType: SubnetType.PUBLIC, cidrMask: 24 }, { name: 'Private', subnetType: SubnetType.PRIVATE_WITH_EGRESS, cidrMask: 24 }, ], }); } }
3. 環境を指定してデプロイする
# devにデプロイ(cdk.jsonのデフォルト値を使用) cdk deploy NetworkStack AppStack # prodにデプロイ(--contextでenvNameを上書き) cdk deploy NetworkStack AppStack --context envName=prod # 変更差分を確認する(実際のデプロイは行わない) cdk diff --context envName=prod # デプロイするスタック一覧を確認する cdk list
CDKスタック設計でよく起きるエラーと対処法
「Stack is in state ROLLBACK_COMPLETE」が出たとき
初回デプロイが途中で失敗すると、スタックがROLLBACK_COMPLETE状態になります。この状態では再デプロイができないため、一度スタックを削除してから再作成する必要があります。# ROLLBACK_COMPLETEになったスタックを削除する cdk destroy NetworkStack # CloudFormationコンソールで確認(スタックが消えていることを確認) aws cloudformation describe-stacks --stack-name NetworkStack 2>&1 # An error occurred (ValidationError) ... Stack with id NetworkStack does not exist # 削除後に再デプロイ cdk deploy NetworkStack AppStack
cdk destroyだけでは削除できないことがあります。その際はAWSコンソールのCloudFormationからスタックを手動削除するか、aws cloudformation delete-stack --stack-name NetworkStackを実行してください。「Export cannot be updated as it is in use by another stack」が出たとき
CloudFormationのOutputエクスポート(CfnOutput + Fn.importValue)を使っている場合、参照元スタックが存在する間はエクスポートを変更できません。このエラーが繰り返し発生するなら、本記事で解説したStack Propsパターンへの移行を検討してください。Stack PropsはCDKコード内での直接参照なので、CloudFormationレベルのExport/Importを作成せず、このエラーが原理的に発生しません。スタック間で循環依存が発生したとき
スタックAがスタックBを参照し、スタックBもスタックAを参照すると、CDKがError: Circular dependency between stacks: NetworkStack <-> AppStackを報告してデプロイできません。解決策は共通リソースを第3のスタック(SharedStackなど)に切り出し、両スタックがSharedStackだけを参照する一方向の依存関係に変更することです。
CDKのスタック設計は、AWSインフラの「型」を知っていれば迷わない
Stack Propsの書き方はドキュメントで調べれば分かります。でも「どこでスタックを切るか」「Contextで持つべき値とProps経由で渡すべき値の違いは何か」を自分で判断できますか?
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、AWSを含む現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
本記事のまとめ
AWS CDKスタック分割と環境差分管理のポイントをまとめます。| やりたいこと | コマンド・方法 |
|---|---|
| 複数スタックをまとめてデプロイする | cdk deploy NetworkStack AppStack |
| 環境を指定してデプロイする | cdk deploy NetworkStack AppStack --context envName=prod |
| 変更差分を確認する | cdk diff --context envName=prod |
| スタック一覧を確認する | cdk list |
| CloudFormationテンプレートを出力する | cdk synth NetworkStack |
| ROLLBACK_COMPLETEのスタックを削除する | cdk destroy NetworkStack |
| スタック間リソース参照(型安全) | Stack Propsパターン(AppStackProps extends StackProps) |
| 環境差分の管理 | cdk.jsonのContextに定義し、this.node.tryGetContext()で取得 |
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:AWS WAFをALB・CloudFrontの前段に置く多層防御設計|マネージドルールの選定と誤検知の調整
- この記事の属するカテゴリ:AWS(Amazon Linux)へ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます