AWS CDKでスタックを分割設計する方法|環境差分をContextとStack Propsで持たせる実践パターン

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)AWS(Amazon Linux) > AWS CDKでスタックを分割設計する方法|環境差分をContextとStack Propsで持たせる実践パターン
「CDKのスタックが肥大化してデプロイに10分かかる」「dev・stg・prodの環境差分をコードに直書きしていて、環境の増減のたびに修正が発生している」

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で上書き


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜ「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

実行環境:Amazon Linux 2023 / AWS CDK v2.156.0 / Node.js 20.x / TypeScript 5.x で動作確認済み。

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

スタック内にS3バケットやDynamoDBテーブルなど残留するリソースがある場合、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()で取得
スタック分割の基本はライフサイクル(変更頻度)で切ることです。VPC・セキュリティ・アプリの3層を独立させることで、アプリの変更がネットワーク層に波及しなくなり、デプロイの安全性と速度が大幅に改善されます。スタック間のリソース参照にはStack Propsを使うことで型チェックが効き、環境差分はContextに一元化することでコードのif/else分岐をなくすことができます。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。