はじめに
個人開発で AWS を触っていて、検証が終わったので terraform destroy で一式まとめて片付けました。「これで課金ゼロに戻ったはず」と安心していたのですが、後日コンソールを見たら、消したはずの ALB と WAF がまだ残っていて課金が続いていました😨
原因は単純で、それらは **Terraform で作っていなかった(=state の外にいた)**リソースでした。
先に結論:terraform destroy が消すのは Terraform が state で管理しているものだけ。コンソールや CLI で手作業で作ったリソースは、destroy しても残ります。
環境
- Terraform(AWS プロバイダ)
- Application Load Balancer(ALB)
- AWS WAF(WAFv2・REGIONAL)
- デフォルト VPC 上に手作業で作っていた検証リソース
- リージョン: ap-northeast-1(東京)
起きたこと
メインの構成は Terraform で作っていたので、片付けはこれ一発のつもりでした。
terraform destroy
destroy 自体は正常に完了しました。ところが後日、コンソールと請求まわりを見ると、ALB と WAF の Web ACL が残ったまま。ALB は稼働時間や LCU に応じた料金がかかり、WAF も Web ACL やルール、リクエスト数に応じて料金が発生します。どちらも「使っていないつもり」でも、残っていれば請求要因になります。
「destroy は成功したのに、なんで残ってる…?」というのが最初の混乱です。
原因:手で作ったものは state に載っていなかった
思い出すと、その ALB と WAF は 以前 WAF の挙動を手で試したときに、コンソール/CLI で作ったものでした。Terraform のコードには書いていません。
確認すると、やはり管理対象に入っていませんでした。
terraform state list | grep -i "lb\|waf"
# → 何も出てこない(=Terraform は管理していない)
terraform destroy は state に載っているリソースを消すコマンドなので、state に無い=手作業で作ったものは、当然そのまま残ります。「IaC で作った構成だから destroy すれば全部消える」と思い込んでいたのが落とし穴でした。実際には、同じアカウントの中に “Terraform 管理” と “手作業” が混在していて、後者だけ取り残されていたわけです。
対処:依存順に手で外してから、再発防止
残骸は依存関係があるので、順番に外す必要がありました。今回は不要な Web ACL だったので、関連付けを外したあとに Web ACL 自体も削除しています(WAF は、関連付けさえ外せば Web ACL を削除できます)。
WAF の関連付け(association)を外す
→ Web ACL を削除(今回は不要なので削除。残す運用もある)
→ ALB を削除
→ (消えるまで待つ)
→ ALB 用のセキュリティグループを削除
この順で削除し、最後に CLI で「対象が残っていないか」を確認しました。
aws wafv2 list-web-acls --scope REGIONAL --region ap-northeast-1
aws elbv2 describe-load-balancers --region ap-northeast-1
# → 対象の ALB / Web ACL が一覧から消えていれば、少なくとも今回確認した課金要因は削除できた
(アカウント全体の請求が止まったとは限りません。NAT Gateway・EIP・EBS・スナップショット・CloudWatch Logs など、別の残骸があれば別途確認が要ります。)
そのうえで再発防止として、手で作っていたこの検証構成を、独立した Terraform コードに起こし直しました。SG・ALB・リスナー・WAFv2(REGIONAL)・関連付けを 1 ファイルにまとめ、
-
terraform applyでいつでも同じ構成を再現できる -
terraform destroyで関連付けごと一括で消せる
という形にしておきました。こうしておけば、次からは「手作業で作って消し忘れる」がそもそも起きません。
# 復旧・検証用に独立させた構成(抜粋)
resource "aws_lb" "demo" { /* ... */ }
resource "aws_wafv2_web_acl" "demo" { /* ... */ }
resource "aws_wafv2_web_acl_association" "demo" {
resource_arn = aws_lb.demo.arn
web_acl_arn = aws_wafv2_web_acl.demo.arn
}
なお、すでに手作業で作ったものを「今後も残したい」場合は、削除ではなく Terraform 管理へ移す手もあります。その場合は まず Terraform コードにそのリソースを定義し、その後 terraform import で state に取り込む、という順番になります(import は「コードに書いてから取り込む」のが前提)。今回は不要な検証環境だったので、削除して作り直せる形に寄せました。
学び
-
terraform destroyが消すのは state にあるものだけ。手作業で作ったリソースは残る。 - 片付けたら、コードだけでなく コンソール/CLI でも「本当に消えたか」を確認する。特に ALB・WAF・NAT Gateway など “残っているだけで料金がかかる” 系は要注意。
- 既存リソースを Terraform 管理へ移すなら、コード定義 →
terraform importの順。不要なら削除して終わらせる。 - 「IaC で作ったから destroy で全部消える」は、同じアカウントに手作業リソースが混じっていると成り立たない。
おわりに
「destroy は成功、でも請求は止まっていない」というのは、慣れていないと気づきにくいところでした😅 IaC を使っていても、手で作ったものは別管理になる——という当たり前を、課金で痛感した一件です。同じく「消したつもり」の人の確認のきっかけになれば嬉しいです🙌
参考
- Terraform 公式:
terraform destroy(state 管理下のリソースを破棄する)
https://developer.hashicorp.com/terraform/cli/commands/destroy - Terraform 公式:
import(既存リソースを state に取り込む。コードに定義してから取り込む)
https://developer.hashicorp.com/terraform/cli/import - AWS公式: AWS WAF 料金
https://aws.amazon.com/waf/pricing/ - AWS公式: AWS WAF の Web ACL(削除前に関連付けを解除する)
https://docs.aws.amazon.com/waf/latest/developerguide/web-acl-deleting.html