terraform destroy が失敗するとき — DependencyViolation と消えないリソース

読了 5分

実験用のスタックを消そうと terraform destroy を回したら途中で止まる、ということは思ったよりよくあります。結論を先に書くと、destroy の失敗は 3 つの枝に分かれます。Terraform の外で作られたものが掴んでいるか(DependencyViolation)、リソースが空になっていないか、わざと掛けておいた安全装置が作動したかです。 それぞれ診断する場所と解除の手順が違い、特に安全装置の解除には「コードを直して apply が先」という順序があります。安全装置を掛ける側の設計は基礎 #7 リソースライフサイクル制御で扱ったので、この記事は掛かったものを外す側です。

一目でわかる失敗類型 #

類型典型エラー原因対処
外部の依存物が残存DependencyViolationTerraform の外で作られたリソースが参照中依存物を探して先に除去
空になっていないBucketNotEmpty などS3 オブジェクト、ECR イメージが残存force_destroy 設定後 apply → destroy
安全装置lifecycle エラー、deletion protectionprevent_destroy、deletion_protectionコードで解除 → apply → destroy
削除の遅延timeout while waitingCloudFront などは削除がもともと遅いtimeouts 調整、再実行
部分 destroy の残存再実行時に残りだけ表示先の失敗で一部だけ削除されたdestroy の再実行が基本

DependencyViolation — 犯人は Terraform の外にいます #

セキュリティグループや VPC を消そうとして出る DependencyViolation は destroy 失敗の代表です。Terraform は自分が知っている依存の順序で消すため、Terraform 管理のリソース同士ならこのエラーは出ません。 出たということは、Terraform の知らない何かがそのリソースを掴んでいるという意味です。

典型的な犯人はこうです。コンソールで手動起動した EC2 インスタンスが Terraform 管理のセキュリティグループを使っている場合、Lambda や ALB が作った ENI がサブネットに残っている場合、別のスタックや別のチームのリソースがこの VPC の中に作られている場合です。診断は対象ごとに場所が決まっています。セキュリティグループなら EC2 コンソールでその SG を参照する ENI を検索し、サブネットならそのサブネットの ENI 一覧を見て、VPC なら VPC コンソールのリソースマップで残りを確認します。CLI なら aws ec2 describe-network-interfaces --filters で同じ照会ができます。

依存物が見つかったら、それを先に消して destroy を再実行します。注意すべきは、その依存物が別のチームの実物かもしれない点です。消す前に所有者を確認する手順は省略してはいけません。

空になっていないリソース — force_destroy は apply が先です #

S3 バケットの BucketNotEmpty は 2 番目によくある失敗です。Terraform はデフォルトではオブジェクトの入ったバケットを消しません。force_destroy = true を設定するとオブジェクトごとバケットを削除しますが、ここに順序の落とし穴があります。この引数は state に反映されていて初めて機能するため、コードに書いて apply を 1 回実行してから destroy する必要があります。 destroy 直前にコードだけ直してすぐ destroy すると、以前の設定で動いて同じエラーがまた出ます。

バージョニングの効いたバケットは、すべてのバージョンと削除マーカーまで消す必要があるため、オブジェクトが多いと時間がかかります。ライフサイクルルールで先に空にするか、大量なら aws s3 rm --recursive で先に空にするほうが速いこともあります。ECR リポジトリのイメージ残存も同じ構造で、force_delete = true が対応する引数です。

安全装置 — 外す順序が決まっています #

prevent_destroy の掛かったリソースは、destroy の計画段階で「Instance cannot be destroyed」エラーで止まります。これは事故ではなく設計の意図が作動したものなので、本当に消すべきかの判断が先です。 消すのが正しいなら、コードから prevent_destroy = true を取り除くか false に変えてから destroy を実行します。lifecycle の設定は構成ファイルから読まれるため、この場合は apply なしで destroy できますが、コード修正がレビューを通るよう PR に残すのが運用の原則です。

RDS や ALB の deletion_protection は層が違います。これは AWS リソース自体の属性なので、引数を false に変えて apply で実物に反映してからでないと destroy が通りません。 RDS はここに最終スナップショットが重なります。skip_final_snapshot が false(デフォルト)のときは final_snapshot_identifier がないと削除が失敗するため、スナップショットを残すか決めて引数を合わせ、apply → destroy の順で進みます。データ層のこうした安全装置の設計は実践 #5 RDS と S3 で扱ったとおり、わざと作っておいた摩擦なので、面倒だからとデフォルトから外しておくのは方向が逆です。

遅延、部分失敗、そして残存物 #

CloudFront ディストリビューションや一部のグローバルリソースは、削除自体に数十分かかります。timeout エラーが出た場合、実際には削除が進行中のことが多く、しばらくして destroy を再実行すると、すでに消えたという確認とともに先へ進みます。常習的に引っかかるリソースには timeouts ブロックで delete の時間を延ばしておきます。

destroy が途中で失敗すると、消えたものは state から抜け、残ったものだけが残ります。apply の部分適用と同じく、原因を取り除いて destroy を再実行するのが基本の復旧です。まれに実物は消えたのに state に残る逆のケースがありますが、refresh が消えたリソースを検知して整理してくれ、だめなら removed ブロックや terraform state rm で整理します。これらの道具は運用 #2 drift と state 手術の領域です。

まとめ #

  • destroy の失敗は外部の依存物、空になっていないリソース、安全装置の 3 つの枝です。エラーメッセージで枝を先に判定します。
  • DependencyViolation は Terraform の外のリソースが掴んでいる合図です。ENI・リソースマップで依存物を探し、所有者を確認してから除去します。
  • force_destroy と deletion_protection の解除は、apply で実物に反映してから destroy します。コードだけ直してすぐ destroy すると同じエラーが繰り返されます。
  • prevent_destroy は設計が作動したものです。解除はコード修正で行い、本当に消す対象かの判断が先です。
  • タイムアウトと部分失敗は再実行が基本の復旧です。Terraform 管理の VPC の中に手動リソースを作らない規律が、この事故の根本的な予防です。
X