【登壇】AWS CDKのクロススタック参照で循環依存エラーが発生した時の解決方法

【登壇】AWS CDKのクロススタック参照で循環依存エラーが発生した時の解決方法

2025.11.18

こんにちは!
クラスメソッドのリテールアプリ共創部の戸田駿太です!

2025年11月12日に開催された「みんなのAWS CDK事情大公開スペシャル#4」で、AWS CDKにおけるクロススタック参照の循環依存問題について登壇しました!このブログでは、実際に遭遇したエラーと解決方法、そこから学んだ教訓をまとめます。

クロススタック参照とは

クロススタック参照とは、あるスタックで作成したリソースを、別のスタックから参照することです。

例えば、CloudWatchのロールをStack1で作成して、Stack2で参照するケースが該当します。

CDKの理想は1つのスタックで構成することですが、現実には様々な制約があります!

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

理想的には、CDKプロジェクトは1つのスタックで構成するのが最もシンプルです。小さいプロジェクトではこのアプローチで十分であり、デプロイも1つで完了し、依存関係の管理も不要です。

しかし、実践的には以下のような制約が存在します。

CloudFormationの500リソース制限

CloudFormationには最大500リソースという制限があります。Lambda、API Gateway、CloudWatchアラームなど、機能を追加していくとすぐにこの制限に達してしまいます。

既存CloudFormationからの移行

元々CloudFormationで管理されていたインフラをCDKに移行する際、既存のスタック分割構造を保ったまま移行することがあります。

チームの運用体制

スタックごとに責任者が異なる場合、スタックを分割して管理する方が運用しやすいケースがあります。

このような理由から、スタックを分割せざるを得ないシーンが多く存在します。

stack123.png

複雑になりすぎると、メンテナンスが難しくなります。

クロススタック参照の2つのパターン

パターン1:Propsによる参照

スタックのコンストラクタの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の型チェックが効く

パターン2:CfnOutputによる参照

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を明示的に使用

実際に遭遇した循環参照エラー

ある作業中に、以下のようなエラーが発生しました。

export Stack1:ExportsOutputTopic as it is in use by Stack2

エラーが発生していた構造

後から分析したところ、以下のような構造になっていました。

  • Stack1 → Stack2: Stack1でSNS Topicを作成し、propsでStack2に渡す
  • Stack2 → Stack1: Stack2で別のリソースを作成し、CfnOutputでStack1に参照できるようにする
  • その後、Stack1でTopicを削除・変更しようとする

意図せず循環参照が発生していました!!

循環参照の仕組み

  • Stack1 → Stack2: Propsで明示的に参照(CDKが自動でExport生成)
  • Stack2 → Stack1: CfnOutputで明示的にExport
  • お互いのリソースを参照してデッドロックが発生
  • 結果: 変更・削除がブロックされる

もし片方を編集しようとすると

  • Stack1がSNS Topicを削除しようとする
  • CloudFormationから該当のExportが削除される
  • Stack2がそのTopicを参照していた場合、ImportValueが参照できずにエラーが発生

逆も同じです。このような相互依存は、どちらのスタックも更新できない状況を招きます。

循環参照の解決方法

基本方針

循環参照を解決するには、どちらか一方の参照を切断する必要があります。

Stack1→Stack2→Stack1のループを断ち切る必要がありました。今回の環境ではStack1→Stack2の順番でデプロイされていたため、以下のアプローチをとりました。

解決ステップ

1. Stack1からStack2への参照を削除

// Stack1でダミーのExportを作成
new CfnOutput(this, 'DummyTopicArn', {
  value: 'arn:aws:sns:ap-northeast-1:123456789012:dummy-topic', // ←文字列に置き換え
  exportName: 'Stack1:ExportsOutputTopic',  // 同じExport名
});

2. 正しい状態に更新

Stack1、Stack2を修正して再デプロイ

なぜダミーのExportが必要か

いきなりTopicを削除するとエラーになってしまいます。

理由

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

そのため、一度ダミーの値をExportして、参照が切れていない状態を保ちながら段階的に修正する必要があります。

失敗から学んだ教訓

教訓1:CDKのラッピングによる理解不足

既存のコードベースには、CDKをラップした便利な関数が存在していました。例えば createLambda() のような関数は、Lambda関数を作成するだけでなく、API Gatewayも設定し、CloudWatchアラームも設定してくれるものでした。

複数のリソースを一度に作成できる便利な仕組みでしたが、内部で何をしているか十分に理解せずに使用してしまいました。

とりあえずこの関数使えばいいや!」という気持ちで利用したため、内部でクロススタック参照が起きるコードを知らず知らずのうちに利用していることに気づきました。

教訓

  • 使う前にラップしている内部のコードをよく理解する
  • 便利な抽象化も、理解なしに使うと危険
  • 特にスタックを跨ぐリソースを扱う場合は注意が必要
  • 必要に応じてラッパーを使わず、CDKをそのまま使う

教訓2:エラー解析のアプローチミス

最初は以下のアプローチでエラーに対応していました。

  1. エラーメッセージを読む
  2. CDKのコードを見る
  3. どこで参照が起きているか推測
  4. コードを修正
  5. デプロイ → また失敗
  6. ループ...

この方法が失敗した理由

  • CDKコードが複雑で追いきれない
  • ラッパークラスで隠蔽されている
  • 自動生成されるリソースが見えない
  • 推測だけで修正しても当たらない

効果的だったアプローチ

  1. エラーメッセージを読む
    → どのリソース間で循環参照が起きているか確認
  2. CloudFormationテンプレートを直接読む
  3. テンプレートからImport/Exportを確認
  4. 実際にどのリソースが何を参照しているか特定
  5. 根本原因を理解してから修正

CloudFormationテンプレートはシンプルで正しい情報です。CDKコードで特定しづらいときには非常に有効です。

確認方法

  • AWS マネジメントコンソール → CloudFormation → スタック → Stack名 → テンプレート

まとめ

できることならクロススタック参照をせずに開発がしたいというのが本音です。

しかし、クロススタック参照を使わざるを得ない場合は、リソース間の参照をよく理解して利用することが重要です。

特に以下の点に注意

  • 循環参照の構造を理解する
  • ラッパー関数の内部動作を把握する
  • トラブル時はCloudFormationテンプレートを活用する
  • 可能な限り一方向の依存関係を保つ

AWS CDKはとても便利なIaCツールですが、内部の仕組みを理解することで、より堅牢なインフラストラクチャを構築できます。


他の方の登壇資料
https://dev.classmethod.jp/articles/aws-cdk-4-cm_cdk_special/

参考資料

この記事をシェアする

FacebookHatena blogX

関連記事