Slide 1

Slide 1 text

CDK クロススタック参照問題と起こった時の解決法 2025/11/12 【ジュニチャン大盛】みんなのAWS CDK事情大公開スペシャル#4 クラスメソッド株式会社 リテールアプリ共創部 戸田 駿太

Slide 2

Slide 2 text

名前 戸田 駿太 所属 クラスメソッド株式会社 リテールアプリ共創部 趣味 料理 キャンプ サッカー バイク SNS X: @shuntemskills GitHub: @ShuntaToda 自己紹介 2

Slide 3

Slide 3 text

1. クロススタック参照とは なぜ必要か、クロススタック参照の2つのパターン 2. 実際に遭遇したエラー 循環参照の仕組みと解決方法 3. 失敗から学ぶ教訓 CDKのラッパーの理解不足とエラー解析アプローチ 4. まとめ 目次 3

Slide 4

Slide 4 text

あるスタックで作成したリソースを、別のスタックから参照すること 例: CloudWatchのロールをStack1で作成してStack2で参照するなど クロススタック参照をしすぎると複雑すぎてメンテナンスが難しくなる クロススタック参照とは 4

Slide 5

Slide 5 text

なぜクロススタック参照が必要なのか

Slide 6

Slide 6 text

理想論 CDKでは1つのスタックで構成するのが最もシンプル 小さいプロジェクトではこれでOK デプロイもStack1つで完了 依存関係の管理が不要 リソース間の参照がスタック内で完結 トラブル時のロールバック・障害切り戻しが容易 しかし現実は... CDKの理想 6

Slide 7

Slide 7 text

CloudFormationの500リソース制限 Lambda/API Gatewayなど、機能に応じてリソースが増える構成でリソースが足りなくなる 既存のCloudFormation(YAML)からの移行時の制約 元々CloudFormationで作成 Cfnではスタック分割する方が扱いやすかった これを移行するときにそのままスタック分割して移行 チームの運用体制(スタックごとに責任者が異なるなど) → スタックを分割せざるを得ない 必要で使うパターンもある 1. VPC、サブネット 2. CognitoだけのStackに分けたい しかし現実は... 7

Slide 8

Slide 8 text

クロススタック参照の2つのパターン 実際は他にもありますが、今回はこの2つのパターンを紹介します。

Slide 9

Slide 9 text

スタックのコンストラクタのpropsを通じてリソースを渡す // Stack1でリソースを作成 export class Stack1 extends Stack { public readonly topic: sns.Topic; constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); this.topic = new sns.Topic(this, 'MyTopic'); } } // Stack2でpropsで受け取る const stack1 = new Stack1(app, 'Stack1'); const stack2 = new Stack2(app, 'Stack2', { topic: stack1.topic, // ← propsで渡す }); 特徴 CDKが自動的に依存関係を管理(Export/Importを自動生成) TypeScriptの型チェックが効く パターン1:propsによる参照 9

Slide 10

Slide 10 text

CfnOutputでExportし、Fn.importValueでImportする // Stack1でCfnOutputを使ってExport export class Stack1 extends Stack { constructor(scope: Construct, id: string, props: StackProps) { super(scope, id, props); const topic = new sns.Topic(this, 'MyTopic'); new CfnOutput(this, 'TopicArn', { value: topic.topicArn, exportName: 'Stack1:TopicArn', // ← Export名 }); } } // Stack2でImportValueを使って参照 const topicArn = Fn.importValue('Stack1:TopicArn'); 特徴 CloudFormationのExport/Importを明示的に使用 パターン2:CfnOutputによる参照 10

Slide 11

Slide 11 text

体験したクロススタック参照によるエラー

Slide 12

Slide 12 text

ある作業をしていた時、以下のようなエラーが発生しました エラー内容 export Stack1:ExportsOutputTopic as it is in use by Stack2 後からわかったエラーの理由 Stack1でSNS Topicを作成し、propsでStack2に渡す Stack2で別のリソースを作成し、CfnOutputでStack1に渡す 後からStack1でTopicを削除・変更しようとする → 意図せず循環参照が発生していた! 作成したリソースを編集するときに参照エラー 12

Slide 13

Slide 13 text

依存関係の図 循環参照の仕組み Stack1 → Stack2(CfnOutputで明示的にExport) Stack2 → Stack1(propsで渡す = CDKが自動でExport生成) お互いのリソースを参照してデッドロックが発生 結果: 変更・削除がブロックされる 循環参照 13

Slide 14

Slide 14 text

片方がSNSトピックを削除してデプロイすると、もう片方が参照できずにエラーが発生 もちろん逆も同じ もし片方を編集すると 14

Slide 15

Slide 15 text

基本方針:どちらか一方の参照を切断する 循環参照を解決するには、Stack1 → Stack2 → Stack1のループを断ち切る必要がある 今回の場合はStack1→Stack2の順番でデプロイされる環境でした 解決方法 1. Stack1からStack2への参照を削除 Stack1のTopicを削除 擬似的にCfnOutputする→あとで説明 再デプロイ 2. 正しい状態に更新 Stack1,2を修正 再デプロイ 循環参照の解決方法 15

Slide 16

Slide 16 text

いきなりTopicを削除するとエラーになる Stack2はCfnOutput(ImportValue)でTopicを受け取っている Stack1でTopicを削除すると、Exportがなくなる Stack2をデプロイしようとすると、ImportValueが参照できずにエラー 擬似的にCfnOutputする理由 16

Slide 17

Slide 17 text

擬似的にStack2にCfnOutputを作成して、エラーにならないようにする // Stack1でダミーのExportを作成 new CfnOutput(this, 'DummyTopicArn', { value: 'arn:aws:sns:ap-northeast-1:123456789012:dummy-topic', // ←ここを文字列に置き換え exportName: 'Stack1:ExportsOutputTopic', // 同じExport名 }); CDK は「参照できる」と認識し、エラーなくデプロイできる ↓この記事が参考になりました。 https://qiita.com/ufoo68/items/9d98e70fbb8f021b57f4 擬似的な値をCfnOutputで用意 17

Slide 18

Slide 18 text

循環参照を避けるには

Slide 19

Slide 19 text

すみません!!🙏🙏🙏 今回は省きますが、いろいろな方法があります。 パラメーターストアで参照する クロススタック参照をそのまま使う 命名規則の運用ルール クラスメソッドの金城さんが書いた記事が参考になり ます!!! https://dev.classmethod.jp/articles/best-way-to- reference-parameters-in-cdk/ 循環参照を避けるには 19

Slide 20

Slide 20 text

実際の失敗から学ぶ教訓

Slide 21

Slide 21 text

状況 既存のコードベースに、CDKのコードをラップした関数が存在 複数のリソースを一度に作成する便利な仕組み 内部で何をしているか十分に理解せずに使用してしまっていた 何が起きたか 「とりあえずこの関数使えばいいや!」 これが原因で内部でクロススタック参照が起きるコードを利用 いつの間にかデプロイ時に参照周りでハマっていた 教訓1:CDKのラッピングによる理解不足 21

Slide 22

Slide 22 text

問題点 ラッパーが内部でどんな依存関係を作っているか見えない(依存を作っていないのが一番) 便利だが、ブラックボックス化した状態で使ってしまう可能性 教訓 使う前にラップしている内部のコードをよく理解する 便利な抽象化も、理解なしに使うと危険 特に、スタックを跨ぐリソースを扱う場合は注意 シンプルにその関数に渡す引数から予想するといいかも 必要に応じてラッパーを使わず、CDKをそのまま使う ラッパーを使う時の問題点と教訓 22

Slide 23

Slide 23 text

最初のアプローチ(うまくいかなかった) 1. エラーメッセージを読む 2. CDKのコードを見る 3. どこで参照が起きているか推測 4. コードを修正 5. デプロイ → また失敗 問題点 CDKコードが複雑で追いきれない ラッパークラスで隠蔽されている 自動生成されるリソースが見えない 推測だけで修正しても当たらない 教訓2:エラー解析のアプローチミス 23

Slide 24

Slide 24 text

1. エラーメッセージを読む → どのリソース間で循環参照が起きているか確認 2. CloudFormationテンプレートを直接読む 3. テンプレートからImport/Exportを確認 4. 実際にどのリソースが何を参照しているか特定 5. 根本原因を理解してから修正 CloudFormationテンプレートは シンプルで正しい情報です CDKコードで特定しづらいときにはおすすめです マネジメントコンソールでの場所 CloudFormation→スタック→Stack名→テンプレート 試すといいアプローチ 24

Slide 25

Slide 25 text

できることならクロススタック参照をせずに開発がしたい! ↓ クロススタック参照を使わざるを得ない場合は、 リソース間の参照をよく理解して利用しましょう! まとめ 25

Slide 26

Slide 26 text

ご清聴ありがとうございました!! クラスメソッド株式会社 リテールアプリ共創部 戸田 駿太