あかね
【Terraform】Planは成功、Applyは失敗——stateと実リソースのズレを理解する
2026年08月22日
要約を生成中...
はじめに
最近のインフラのタスクがあまりにも学びが多かったので、すぐ忘れても見たら思い出せるようにまとめておきます。
タスク概要
TerraformでPlanには成功するのにEFSの削除applyに失敗するから調べて削除してほしいというものです。
出ていたのはこんなエラーです。
Error: deleting Security Group (...):
DependencyViolation: resource ... has a dependent objectSecurity Groupが何かに使われていて依存関係のせいで消せない。ここまではエラー翻訳したらすぐわかりました。
そもそもEFSって何??だったんですけど、そこから掘り出すと無理だなって思ったので、今回は削除したいっていうタスクだしあまりなんのリソースかから掘り下げるのはやめました。
今回わからなかったこと
「Terraformで依存関係を管理しているのに、なんで消す順番を間違える?」というところでした。
普通にTerraformでリソースの管理してたらこんなこと起きないはずなのになんで??って思って、そこが気になったので掘り下げて調べてみることにしました。
調べていくと、原因は今のTerraformコードではなく、過去に行われたリソースのモジュール化まで遡りました。
そして今回、私が一番理解に時間がかかったのがTerraformのstate周辺の知識でした。
削除作業だけなら手順どおり進めれば終わりますが、今回は「なぜTerraformで削除できないのか」「なぜ手動で消してよいのか」を自分で説明できるところまで掘り下げました。
Terraform管理のものをAWSコンソールで触ってはいけない
Terraformを使い始めたときから、
Terraformで管理しているリソースをAWSコンソールから勝手に変更すると、Terraformの認識とAWSの実物に差分ができてややこしくなる
ということは理解していました。
なので本番環境で、「Terraformから見えていないEFSをAWSコンソールから削除する」という必要がありそうなエラーであることがわかった時点で私が困惑しました。
いや、Terraformで管理していたものを手で消していいの??なんでそんなことになった????
ここから今回の調査が始まりました。
Terraformは何を見てPlanを作っているのか
今回ここが一番腹落ちしたところです。
Terraformには、大きく3つの登場人物があります。
Terraformコード「こういう状態であってほしい」
↕
state「Terraformが今何を管理しているか」
↕
AWS「実際に存在しているリソース」私はなんとなくTerraformがコードとAWSの実物を比較して差分を出しているくらいのイメージを持っていましたし、概ね間違った解釈ではないと思います。
でも、それではコードからリソースを削除したときに困ります。
昨日まで、
Terraformのコード: EFS / mount target / Security Groupだったものが、
Terraformのコード: 何もないになったとします。
今のコードだけ見ても、Terraformには「以前EFSを管理していた」という情報がありません。
そこで必要になるのがstateです。
Terraformのコード: 何もない
state : EFS / mount target / Security Group
→ stateにはあるのにコードにはない
→ 削除しようつまり私の中では、state = Terraformの記憶と考えると一気に分かりやすくなりました。
逆に言えば、AWSに実物が存在していてもstateから消えてしまえば、Terraformの管理対象から外れてしまいます。
今回のEFSがまさにこの状態でした。
Planでは「Security Groupだけ消す」ことになっていた
今回、不要になった関連リソースのTerraformコードが削除されました。
本来であればPlanには、EFSやmount targetなど、関連リソースも削除対象として出てくるはずです。
ところが実際のPlanに出ていたのは、
Plan: 0 to add, 0 to change, 1 to destroySecurity Groupひとつだけだったんです。
ここで、「あれ? EFSとmount targetはどこに行った?」ということになりました。
EFS知らないから自力ではならないし、AIの解説を経て理解。
AWSコンソールを見ると、EFSもmount targetも実在しています。
つまり、
Terraform state
Security Group → いる
EFS → いない
mount target → いない
AWS
Security Group → いる
EFS → いる
mount target → いるという状態です。
Terraformからすると、
stateにSecurity Groupがいる コードにはもうSecurity Groupがない じゃあSecurity Groupを消そう
となります。
Planとしては何もおかしくありません。
ところがApplyするとAWSから怒られます。
Terraform
「Security Groupを削除してください」
AWS
「いや、そのSecurity Groupまだ使われてますけど?」
DependencyViolationここでようやく、Terraformが認識している世界とAWSの現実が食い違っていることが表面化しました。
Security Groupを掴んでいたのは誰なのか
今回の依存関係はこうなっていました。
EFS
└── mount target
└── ENI
└── Security GroupEFSのmount targetを作ると、その接続口としてENIが作られます。
そのENIにSecurity Groupが紐づいていました。
つまりAWSから見ると、
ENI「このSecurity Group使ってます」という状態です。
なのでSecurity Groupだけ消そうとしても、AWSは削除を拒否します。
ここでも最初、「Terraformって依存関係を見て、いい感じの順番で消してくれるんじゃないの?」と思いました。
その理解自体は間違っていませんでしたが、問題はTerraformがmount targetの存在を知らなかったことです。
Terraformが依存関係を管理できるのは、Terraformが管理しているリソースについてです。
今回のmount targetはAWSには存在するのにstateにはいない。
だからTerraformには、
mount target → Security Groupという今回必要な依存関係をたどれません。
TerraformはSecurity Groupを削除しようとする。
AWSは「まだ使われてるから無理」と拒否する。
やっと今回のエラーが繋がりました。
なんでEFSがstateから消えたのか
ここからGitの履歴を遡りました。
claudeは削除という目的に向けてしか調査すべきこと教えてくれないので、私がなんでこうなったのか聞きました。
git履歴見れるのはTerraformで管理する醍醐味やな
原因は過去に行われた、関連リソースのモジュール化でした。
もともと今回削除したかった関連のリソースは複数のtfファイルに散らばっていました。
それを、
modules/hoge/にまとめる変更が行われています。やりたかったこと自体は普通です。
ただしTerraformにとっては、モジュールに移動するとリソースの「住所」が変わります。
移動前
aws_efs_file_system.hoge
移動後
module.hoge.aws_efs_file_system.hogeAWS上のEFSそのものは同じで、変わるのはTerraform上の住所だけです。
ここでTerraformに、「古いリソースを削除して新しいものを作るんじゃなくて、同じリソースの住所が変わっただけだよ」と教える必要があります。
そのために使えるのがmovedです。
moved {
from = aws_efs_file_system.hoge
to = module.hoge.aws_efs_file_system.hoge
}これならstate上の住所を移動するだけなので、AWSリソースのIDを直接指定する必要がありません。
ところが当時の変更では、importを使った移行が行われていました。
import + removedで引っ越そうとしていた
当時のコードには、検証環境のEFS IDを指定したimportがありました。
イメージとしては、
import {
id = "検証環境のEFS ID"
to = module.hoge.aws_efs_file_system.hoge
}importは、AWSには存在しているけどTerraform管理下にいないリソースを、stateに登録するためのものです。
そして旧住所側には、
removed {
from = aws_efs_file_system.hoge
lifecycle {
destroy = false
}
}という変更が入っていました。
destroy = falseなので、AWSの実物は削除せず、Terraformの管理対象から旧住所の記録を外します。
つまり当時やろうとしていたことをかなり雑に書くと、
① import
新住所に登録する
② removed
旧住所から外す
→ 結果的に住所を移動するという形です。
検証環境ではこれが成立しましたが、本番環境では問題が起きます。
importに指定されているのは検証環境のEFS IDだからです。
結果として本番環境では、
新住所への登録
→ できていない
旧住所からremoved
→ 実行されるとなり、
AWS
EFSは残っている
Terraform state
旧住所にもいない
新住所にもいないという状態になってしまいました。
EFSがTerraformから迷子になった瞬間です。
そして、しばらく誰も気づかなかった
しばらくの間、AWSにはEFSが存在していました。
でもTerraformからすると管理対象ではありません。
普段のPlanにも出てこないので、リソースそのものを削除しようとするまで発覚しませんでした。
不要な関連リソースを削除する
↓
Terraformはstateに残っているSecurity Groupだけを削除しようとする
↓
AWSではEFS由来のENIがそのSecurity Groupを使用中
↓
DependencyViolation
↓
過去のstate移行の問題が発覚だいぶ遠いw最初はただ、「Security Groupが消えない」だったんですけどねぇ
「AWSコンソールから消していい」がやっと理解できた
ここが個人的には今回一番怖かったところです。
私はもともとTerraform管理のリソースをAWSコンソールから勝手に触らないという認識でした。
それなのに今回の対応は、AWSコンソールからmount targetとEFSを削除する必要があります。
最初は、「いや、さらにTerraformとの差分を増やさない??」と思いました。
でも今回のEFSは、すでにTerraformのstateに存在していません。
つまりこれは、Terraformが管理しているリソースを手で消すのではなく、Terraformの管理から外れてAWSに残ってしまったリソースを片付けるという話でした。
ここを理解できるまで、怖くて本番環境では触れませんでした。
AIに「手で消して大丈夫」と言われても、なぜ大丈夫なのか自分で説明できない状態で本番環境のリソースを削除するのは無理です。
なので何度も、
「なんで?」
「本当にTerraform管理じゃない?」
「そもそも最初から管理してなかった可能性は?」
「EFSを消す? ENIを消す?」
と確認しながら進めました。
結果として、今回触るべきなのはmount targetです。
mount targetを削除
↓
AWS管理のENIが削除される
↓
Security Groupを掴んでいるものがなくなる
↓
TerraformからSecurity Groupを削除できるENIそのものを手で削除するわけではありません。
今回理解できたこと
今回一番大きかったのは、Terraformを単に「AWSをコードで作るツール」として見るだけでは足りなかったことです。自分の中では今、次の3つを分けて考えるようになりました。
コード
「どうなってほしいか」
state
「Terraformが何を管理していると思っているか」
AWS
「実際にどうなっているか」普段はこの3つが一致しているので意識しませんが、state ≠ AWSになると一気にややこしくなります。今回はTerraformの認識上は問題がなかったためPlanは成功し、実際にAWSへ削除要求を出したところで依存関係にぶつかってApplyが失敗しました。この2段階を分けて考えると、かなり理解しやすかったです。
moved / import / removed
ここがやったことないしなんでそんなことしようとした??から始まって今回かなり混乱したので、最後に自分用に残しておきます。
moved
Terraform管理中の同じリソースの「住所」を変更する。
旧住所 → 新住所今回のようなモジュール化なら、まず検討したいもの。
import
AWSには存在するがTerraform管理下にないリソースを、Terraform管理下へ取り込む。
AWS上のどのリソースなのかを識別する必要がある。
removed
Terraformの管理対象から外す。
特にdestroy = falseなら、AWSの実物を残したままTerraformの管理対象から外れる。
今回の事故を見たあとだと、destroy = falseはかなり慎重に使いたい。
おわり
最初は、「Security Groupが消えない」だけでした。そこからENI、mount target、EFS、Terraform state、過去のmodule移行まで遡ることになりました。
正直、インフラはまだ全然わかりませんし、今回もAIがなかったら原因を追うだけでかなり時間がかかったと思います。ただ、本番環境なので、「AIがこう言っているから、このリソースを消します」ではとてもじゃないけど操作できません。
分からないところで止まり、Terraformは何を見ているのか、AWSには何が残っているのか、なぜ手で消していいのかを一個ずつ確認しました。その結果、最初は全然わからなかったstateを、今回の実例を通してかなり具体的に理解できました。
分からない技術を先に全部勉強してから実務で使うより、実際に起きている問題を追いながら「これ何?」を一個ずつ潰していく方が、私はやっぱり理解しやすい。
Security Groupひとつ消えなかっただけなのに、Terraformのstateについてかなり勉強することになりました。本番環境でやるには、だいぶ重たい教材でしたw
まだ調査完了くらいの段階で実際リソース削除はしてないんですが、本番環境でのリソースの作業をPRベース以外で行うのはただただ怖いのでペア作業してもらう予定です。




