Terraformのlifecycleブロックで本番リソースを誤削除から守る方法|prevent_destroyとignore_changesの実践設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > Terraformのlifecycleブロックで本番リソースを誤削除から守る方法|prevent_destroyとignore_changesの実践設計
「terraform destroyを間違えて本番環境に実行してしまった」「terraform applyでRDSが削除・再作成されてデータが消えた」
Terraformを使い始めたエンジニアやチームで本格運用を始めたばかりの現場では、こうした誤操作が実際に起きています。Terraformは「宣言した状態を実現する」ことに忠実すぎるがゆえに、意図しない削除や置換が発生しやすいツールでもあります。

この記事では、Terraformの lifecycle ブロックを使って本番リソースを守る方法を解説します。prevent_destroycreate_before_destroyignore_changes の3設定の意味と、RDS・ALBへの実践的な適用パターンを具体的なコード例と実行ログで説明します。

この記事のポイント

・prevent_destroy = true で誤ったdestroy/replace操作をエラーでブロックできる
・create_before_destroyでALB・セキュリティグループのゼロダウンタイム置換を実現する
・ignore_changesでコンソール手動変更によるドリフトを安全に吸収できる
・RDSにはDeletion Protectionとの二重防護でより確実に誤削除を防げる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

なぜTerraformは本番リソースを削除してしまうのか

Terraformの動作原理は「現在のtfstateと.tfコードの差分をゼロにする」ことです。この設計は再現性を高める一方で、意図しない削除を引き起こすことがあります。

現場でよく起きる「消えた」事例を3つ挙げます。

リソースの再作成(destroy → create):一部の属性変更(RDSのエンジンバージョン変更、EC2のAMI変更など)はリソースを一度削除して作り直す必要があります。terraform plan を確認せずに terraform apply すると、本番DBが消えます
ワークスペースの誤認識:「dev環境のディレクトリ」と思い込んで terraform destroy を実行したが、実際は本番のワークスペースだったというミスです
コンソール変更の上書き(ドリフト):AWSコンソールで手動変更した設定が、次の terraform apply で「差分あり」と判断されて元の値に戻されてしまうケースです

lifecycle ブロックはこうしたリスクを最小化するための安全装置です。

lifecycleブロックの基本構文と3つの設定

lifecycle ブロックは、任意の resource ブロックの中に記述します。

resource "aws_db_instance" "prod_db" { identifier = "prod-mysql-01" engine = "mysql" instance_class = "db.t3.medium" lifecycle { prevent_destroy = true # 削除をエラーでブロック create_before_destroy = false # 通常はfalse ignore_changes = [password] # 指定属性の差分を無視 } }

1. prevent_destroy:削除操作をエラーで止める

prevent_destroy = true を設定したリソースを削除しようとすると、Terraformはplanの段階でエラーを返して処理を中断します。

以下は、本番RDSに prevent_destroy を設定した状態で terraform destroy を実行した際の実際の出力例です(インスタンス名・ARNはマスク済み)。

$ terraform destroy -target=aws_db_instance.prod_db Planning... ╵ │ Error: Instance cannot be destroyed │ │ on rds.tf line 22, in resource "aws_db_instance" "prod_db": │ 22: prevent_destroy = true │ │ Resource aws_db_instance.prod_db has lifecycle.prevent_destroy set, │ but the plan calls for this resource to be destroyed. To avoid this │ error and continue with the plan, either disable lifecycle.prevent_destroy │ or adjust the scope of the plan using the -target flag. ╴

このエラーにより、削除を続けるには prevent_destroy = false に変更してコードをコミットし直す、という明示的な操作が必要になります。「うっかりEnterを押した」程度の誤操作は確実にブロックできます。

注意点が1つあります。.tfファイルからリソースブロックごと削除すると、lifecycleブロックも消えるためこのガードは効きません。リソースをTerraformの管理から外したい場合は terraform state rm で状態だけを除外し、コードは残しておくか、削除前に別途確認フローを設けてください。

2. create_before_destroy:ゼロダウンタイムでリソースを置換する

Terraformのデフォルト動作では、リソースの置換が必要な場合に「既存を削除してから新規作成」(destroy → create) の順番をとります。ALBやセキュリティグループなど、他のリソースから参照されているリソースでこの順番が発生すると、参照が一時的に失われてサービス停止につながります。

create_before_destroy = true を設定すると「新規を先に作成してから旧リソースを削除」(create → destroy) の順番になります。

resource "aws_security_group" "alb_sg" { # name ではなく name_prefix を使う(古いSGと名前が衝突しないようにする) name_prefix = "alb-sg-" vpc_id = aws_vpc.main.id ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } lifecycle { create_before_destroy = true } }

name を固定値にすると、新旧のSGが同時に存在する間に名前の衝突エラーが発生します。name_prefix を使うとTerraformがユニークな名前を自動生成するため衝突を回避できます。

terraform plan の出力では +/- のシンボルで置換操作が示されます。

$ terraform plan # aws_security_group.alb_sg must be replaced +/- resource "aws_security_group" "alb_sg" { ~ id = "sg-0a1b2c3d4e5f67890" -> (known after apply) ~ name = "alb-sg-20240101120000" -> (known after apply) # forces replacement } Plan: 1 to add, 0 to change, 1 to destroy.

+/- が示す順番は create → destroy(create_before_destroy が有効)です。

3. ignore_changes:外部変更によるドリフトを吸収する

Terraformで管理しているリソースの属性が、Terraform以外の手段(AWSコンソール・Lambda・Auto Scalingポリシー等)によって変更されることがあります。これを「ドリフト」と呼びます。ドリフトが発生すると次の terraform plan で「差分あり」として検出され、Terraform管理値に戻す操作が計画されます。

ignore_changes に指定した属性はdiff計算から除外されるため、外部変更をそのまま維持できます。

resource "aws_autoscaling_group" "web" { name = "web-asg" desired_capacity = 2 min_size = 1 max_size = 10 launch_template { id = aws_launch_template.web.id version = "$Latest" } lifecycle { # desired_capacity は Auto Scaling ポリシーが動的に変更するため無視 # tags はコンソールで追加管理しているため無視 ignore_changes = [desired_capacity, tags] } }

ignore_changes = all を指定するとすべての属性の差分を無視できますが、これはTerraformによる管理を実質的に停止することを意味します。できる限り属性を明示して指定してください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Terraformのlifecycle設計からtfstateのチーム運用・CI/CDパイプライン統合まで、本番を壊さないIaCスキルをハンズオン形式で学べるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

本番環境への実践:RDS・ALBへの適用パターン

1. RDSに3設定を組み合わせた二重防護を構成する

本番データベースには、lifecycle ブロックとRDS自身の deletion_protection を組み合わせた二重防護を構築します。

resource "aws_db_instance" "prod_db" { identifier = "prod-mysql-01" engine = "mysql" engine_version = "8.0.35" instance_class = "db.t3.medium" allocated_storage = 100 db_name = "appdb" username = "admin" password = var.db_password multi_az = true skip_final_snapshot = false final_snapshot_identifier = "prod-mysql-final-snapshot" deletion_protection = true # RDS側のDeletion Protectionも必ず有効化する lifecycle { prevent_destroy = true ignore_changes = [ password, # Secrets Managerのローテーション対応 engine_version, # マイナーバージョン自動アップグレードを許可している場合 ] } }

deletion_protection = true はRDS APIレベルでの削除禁止です。Terraform側の prevent_destroy と組み合わせることで「Terraformの誤操作」と「AWSコンソールからの手動削除」の両方を防げます。

実際の現場で passwordignore_changes に含めていなかったプロジェクトで、Secrets Managerによるパスワードローテーション直後の terraform apply でRDSのパスワードが古い値に強制変換されてアプリケーションが接続できなくなった事例がありました。原因特定に1時間以上かかりました。ignore_changes を適切に設定することで、こうした事故を防げます。

2. ALBのセキュリティグループ置換にcreate_before_destroyを使う

ALB(Application Load Balancer)に紐付くセキュリティグループを変更する場合、置換操作が発生するとALBとの関連が一時的に外れてサービスが落ちます。以下のように name_prefixcreate_before_destroy を組み合わせて、ゼロダウンタイムで置換できるようにします。

resource "aws_security_group" "alb_sg" { name_prefix = "alb-sg-" vpc_id = aws_vpc.main.id ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } lifecycle { create_before_destroy = true } } resource "aws_lb" "web" { name = "web-alb" internal = false load_balancer_type = "application" security_groups = [aws_security_group.alb_sg.id] subnets = var.public_subnet_ids }

3. ACM証明書の更新にcreate_before_destroyを使う

ACM(AWS Certificate Manager)の証明書をTerraformで管理している場合、証明書の更新時に置換が発生することがあります。ALBリスナーが証明書を参照しているため、create_before_destroy で新証明書を先に発行してから旧証明書を削除する順番を保証します。

resource "aws_acm_certificate" "main" { domain_name = "example.com" validation_method = "DNS" subject_alternative_names = [ "*.example.com", ] lifecycle { create_before_destroy = true } }

lifecycleのトラブルシュートとよくある誤り

Q1. prevent_destroyを設定しているのにリソースが削除された

prevent_destroy はHCLコードに設定が存在していることが前提です。.tfファイルからリソースブロックごと削除すると、lifecycle ブロックも同時に消えるため削除がブロックされません。

また、terraform state rm でtfstateからリソースの記録を消した場合はTerraformの管理から外れます。この状態で terraform apply を実行すると「リソースを作り直そうとする / 既存と名前が衝突する」挙動になります。prevent_destroy はPlan段階での安全弁であり、手動のstate操作は別の話です。

Q2. ignore_changesを設定したが差分が消えない

ignore_changes に指定する属性名はTerraformプロバイダーのスキーマ上の属性名です。コンソール表示名やAWS API名と異なる場合があります。

実際の属性名を確認するには terraform state show を使います。

# tfstateからリソースの実際の属性名を確認する $ terraform state show aws_autoscaling_group.web # aws_autoscaling_group.web: resource "aws_autoscaling_group" "web" { arn = "arn:aws:autoscaling:ap-northeast-1:012345678901:..." desired_capacity = 3 # ← この属性名を ignore_changes に指定する min_size = 1 max_size = 10 ... }

Q3. create_before_destroyを設定したが「ResourceAlreadyExists」エラーになる

このエラーは name を固定値で指定しているリソースで create_before_destroy を使った場合に発生します。新旧で同じ名前のリソースが同時に存在できないため衝突します。

解決策は name_prefix に変更することです。Terraformが現在日時を含むユニークな名前を自動生成するため衝突を回避できます。ただし name_prefix を使うとリソース名が変わるため、外部参照(DNS設定・アプリケーション設定など)への影響を確認してから変更してください。

Q4. 設定内容をチームで統一するにはどうすればいい?

lifecycleブロックの設定はモジュール化の対象になりません(lifecycle はメタ引数のため、変数で制御できない)。チームで統一するには以下の方法が現実的です。

・コードレビューのチェックリストに「本番リソースのlifecycle設定」を追加する
・Terraformのポリシーフレームワーク(Sentinel、Open Policy Agent)でlifecycle設定の存在を自動チェックする
・モジュールの README.md に「このモジュールを本番で使う場合は必ずprevent_destroyを追加すること」を明記する

本記事のまとめ

Terraformのlifecycleブロック3設定の使い分けをまとめます。
設定 効果 典型的な用途
prevent_destroy = true destroyをエラーで止める 本番RDS・S3バケット・VPCなど削除禁止リソース
create_before_destroy = true 新規作成→旧削除の順にする ALBセキュリティグループ・ACM証明書など参照リソース
ignore_changes = [attr] 指定属性の差分を無視 Auto Scalingキャパシティ・DBパスワード・手動タグ
3設定の選択基準はシンプルです。

・「消されたら困る重要リソース」→ prevent_destroy(RDS Deletion Protectionと併用する)
・「置換時に参照が途切れてはいけないリソース」→ create_before_destroy(name_prefixと組み合わせる)
・「Terraform以外が変更する属性がある」→ ignore_changes(allは使わず属性を明示する)

lifecycleブロックは terraform plan の出力確認と合わせて、チームのコードレビューチェックリストに加えることをお勧めします。「本番環境に適用するTerraformコードには必ずlifecycle設定を明示する」をルール化するだけで、誤操作リスクは大幅に下がります。

関連記事もあわせてご覧ください。
Terraformのtfstate管理とS3バックエンド設定|チーム運用で壊さないための基礎
Terraformのmoduleとfor_each・countで構成を再利用する設計パターン
TerraformのHCL変数設計|variable・locals・output・data sourceで構成を整理する方法
TerraformとAnsibleの違いと使い分け|IaCツール選定の判断軸
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Terraformのlifecycle設計やCI/CDパイプライン統合を含む、本番を壊さないIaC運用のスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。