KINTO Tech Blog
AWS

CloudFormation ネストスタックが DELETE_COMPLETE 状態で更新不能になった問題の解決記録

Cover Image for CloudFormation ネストスタックが DELETE_COMPLETE 状態で更新不能になった問題の解決記録

はじめに

こんにちは、 Cloud Infrastructure G の山中です!
AWS Amplify Gen 2 + CDK で構築した dev 環境で、CloudFormation のデプロイが失敗し続けるという問題に遭遇しました。エラーメッセージは以下の通りです:

Stack:arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/amplify-XXXXX-CloudWatchLogsToS3Stack108915EF-XXXXX/XXXXX is in DELETE_COMPLETE state and can not be updated.

本記事では、この問題の原因究明から AWS サポートへの問い合わせ、そして最終的な解決までのプロセスを共有します。

背景

実施したリファクタリング

CDK コードで、あるリソース群を cdk.Stack(独立したネストスタック)から Construct(親スタック内のリソースグループ)へ変更するリファクタリングを行いました。

この変更を行った理由は、クロススタック参照の問題を解消するためです。

クロススタック参照とは?
CloudFormation で複数のスタック間でリソース(ARN など)を参照し合う仕組みです。便利ですが、参照元・参照先の削除順序によってはエラーが発生することがあります。

変更前: 独立したネストスタックとして定義

export class CloudWatchLogsToS3Stack extends cdk.Stack {
  // 独立したスタックとしてデプロイされる
}

変更後: 親スタック内の Construct として定義

export class CloudWatchLogsToS3Stack extends Construct {
  // 親スタックの一部としてデプロイされる
}

一見シンプルな変更ですが、これが思わぬ問題を引き起こしました。

発生した問題

問題の発生メカニズム

StackConstruct への変更により、CDK の Construct 階層が変わり、CloudFormation の論理 ID(CloudFormation がリソースを識別するための内部的な名前)が変化しました。

【変更前の構造】

CloudWatchLogsToS3Stack (Stack)
├── Firehose0
├── FirehoseRole0
└── ...

【変更後の構造】

CloudWatchLogsToS3Stack (Amplify Stack)
└── CloudWatchLogsToS3StackResource (Construct)
    ├── Firehose0
    ├── FirehoseRole0
    └── ...

この結果、以下の連鎖的な問題が発生しました:

  1. CloudFormation は論理 ID が変わったため「新規リソース」として作成を試みる
  2. しかし物理名(IAM ロール名、ロググループ名など)は既存のものと同じ
  3. 「リソースが既に存在する」エラーで CREATE_FAILED
  4. 作成に失敗したネストスタックが DELETE_COMPLETE 状態になる
  5. 親スタックが、削除済みネストスタックの ARN を参照し続ける(孤立した参照)
  6. 以降のデプロイで「DELETE_COMPLETE 状態のスタックは更新できない」エラーが発生

特に厄介なのは手順 5 の状態です。親スタックのテンプレートに「存在しないネストスタック」への参照が残り続けるため、何をしてもデプロイが失敗するようになります。

エラーの詳細

CloudWatchLogsToS3Stack108915EF:
Stack:arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/amplify-XXXXX-CloudWatchLogsToS3Stack108915EF-XXXXX/XXXXX is in DELETE_COMPLETE state and can not be updated.

試した対応(すべて失敗)

自力で以下の対応を試みましたが、いずれも解決には至りませんでした。

試した対応 結果
重複リソース(IAM ロール、ロググループ等)の手動削除 リソースは削除できたが、デプロイは失敗
DELETE_COMPLETE 状態のネストスタックを AWS コンソールから削除 既に削除済みのため操作不可
AWS CLI でルートスタックのテンプレートから参照を除去して update-stack 同じエラーで失敗
continue-update-rollback コマンドで問題リソースをスキップ スタック状態が UPDATE_ROLLBACK_COMPLETE のため使用不可
CDK コードで論理 ID を変更して新規作成 古い参照がルートスタックに残っているため失敗

どの方法でも、親スタックが削除済みのネストスタック ARN を参照し続けているという根本問題を解決できませんでした。

AWS サポートへの問い合わせ

自力での解決が困難と判断し、AWS サポートに問い合わせました。

問い合わせ内容(要約)

  • ルートスタックが DELETE_COMPLETE 状態のネストスタック ARN を参照し続けている
  • テンプレート更新、continue-update-rollback、論理 ID 変更など試したが解決せず
  • ルートスタックから古いネストスタック参照を除去していただくことは可能か

AWS サポートからの回答

原則として、AWS 側にてお客様のスタックを操作することは行なっておりません。

お問い合わせのエラーを解消いただくには、親スタックにて管理されているリソースから子スタックを削除いただいた後に、再度子スタックを作成いただく必要がございます。

手順1: 親スタックの CDK コードより既に削除されている子スタックを作成されている処理をコメントアウトいただき、CDK コードをデプロイすることで、親スタックにて管理されているリソースから子スタックを削除いただく。

手順2: 手順1におけるコメントアウトを外し、再度 CDK コードをデプロイいただく。

つまり、2段階のデプロイで解決できるとのことでした。

解決手順

手順1: 問題のスタック作成処理をコメントアウトしてデプロイ

以下をコメントアウトしました:

// CloudWatch Logs → S3エクスポート設定
// ============================================================
// 【手順1】DELETE_COMPLETE 状態のネストスタック参照を削除するため、
// 一時的にコメントアウトしています。
// ============================================================
// const logsExportStack = backend.createStack("CloudWatchLogsToS3Stack");
// const logsExportStackInstance = new CloudWatchLogsToS3Stack(...);
// logsExportStack.addDependency(logsBucketStack);

// RumConstruct の Firehose 連携も無効化
const rumStackInstance = new RumConstruct(rumStack, "RumStackResource", {
  // ...
  enableSubscriptionFilter: false,  // true → false
  // firehoseStreamArn: logsExportStackInstance.firehoseStreamArns.get('rum')!,
});

// rumStack.addDependency(logsExportStack);

// SubscriptionFiltersStack もコメントアウト
// if (appSyncApiId) {
//   const subscriptionFiltersStack = backend.createStack("SubscriptionFiltersStack");
//   ...
// }

デプロイ後の確認:

aws cloudformation list-stack-resources \
  --stack-name amplify-XXXXX \
  --output json | jq -r '.StackResourceSummaries[] | select(.LogicalResourceId | contains("CloudWatchLogsToS3") or contains("SubscriptionFilters"))'

→ 出力なし = 親スタックから参照が削除された ✅

手順2: コメントアウトを解除して再デプロイ

コメントアウトを全て解除し、元の状態に戻してデプロイしました。

結果:

aws cloudformation describe-stacks --stack-name amplify-XXXXX \
  --query "Stacks[0].StackStatus"
# => "UPDATE_COMPLETE"

→ デプロイ成功 ✅

学んだこと

1. CDK の Construct 階層変更は要注意

StackConstruct への変更のような、一見シンプルなリファクタリングでも、CloudFormation の論理 ID が変わる可能性があります。

2. 「孤立した参照」問題は厄介

ネストスタックが DELETE_COMPLETE 状態になると、親スタック側にそのスタックへの参照が残り続けます。
その結果、CloudFormation は削除済みスタックを更新しようとして失敗し、通常の更新操作では復旧できない状態に陥ることがあります。

3. 2段階デプロイが有効

このような孤立した参照問題には、「問題箇所をコメントアウト → デプロイ → 解除 → 再デプロイ」という2段階の手順が有効です。1回目のデプロイで親スタックから参照を削除し、2回目で新規作成するという流れです。

4. 困ったら AWS サポートへ

自力で解決できない問題に遭遇した場合、AWS サポートへの問い合わせが有効です。今回は「AWS 側での直接操作はできない」という回答でしたが、代わりに的確な回避策を教えていただきました。

まとめ

CloudFormation のネストスタックが DELETE_COMPLETE 状態で更新不能になる問題は、以下の手順で解決できました:

  1. 手順1: 問題のスタック作成処理をコメントアウトしてデプロイ(親スタックから参照を削除)
  2. 手順2: コメントアウトを解除して再デプロイ(スタックを新規作成)

CDK/CloudFormation を使用している方で同様の問題に遭遇した場合、この記事が参考になれば幸いです。

参考リンク

Facebook

関連記事 | Related Posts

We are hiring!

【クラウドエンジニア】Cloud Infrastructure G/東京・大阪・福岡

KINTO Tech BlogWantedlyストーリーCloud InfrastructureグループについてAWSを主としたクラウドインフラの設計、構築、運用を主に担当しています。

【カスタマーサクセスエンジニア】プラットフォームG/東京・大阪・福岡

プラットフォームグループについてAWS を中心とするインフラ上で稼働するアプリケーション運用改善のサポートを担当しています。

イベント情報

CO-LAB Tech Night vol.8 届けるためのエンジニアリング~共創と巻き込みの実践~