Terraformを使い始めたエンジニアやチームで本格運用を始めたばかりの現場では、こうした誤操作が実際に起きています。Terraformは「宣言した状態を実現する」ことに忠実すぎるがゆえに、意図しない削除や置換が発生しやすいツールでもあります。
この記事では、Terraformの
lifecycle ブロックを使って本番リソースを守る方法を解説します。prevent_destroy・create_before_destroy・ignore_changes の3設定の意味と、RDS・ALBへの実践的な適用パターンを具体的なコード例と実行ログで説明します。この記事のポイント
・prevent_destroy = true で誤ったdestroy/replace操作をエラーでブロックできる
・create_before_destroyでALB・セキュリティグループのゼロダウンタイム置換を実現する
・ignore_changesでコンソール手動変更によるドリフトを安全に吸収できる
・RDSにはDeletion Protectionとの二重防護でより確実に誤削除を防げる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ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による管理を実質的に停止することを意味します。できる限り属性を明示して指定してください。
>> 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コンソールからの手動削除」の両方を防げます。実際の現場で
password を ignore_changes に含めていなかったプロジェクトで、Secrets Managerによるパスワードローテーション直後の terraform apply でRDSのパスワードが古い値に強制変換されてアプリケーションが接続できなくなった事例がありました。原因特定に1時間以上かかりました。ignore_changes を適切に設定することで、こうした事故を防げます。2. ALBのセキュリティグループ置換にcreate_before_destroyを使う
ALB(Application Load Balancer)に紐付くセキュリティグループを変更する場合、置換操作が発生するとALBとの関連が一時的に外れてサービスが落ちます。以下のようにname_prefix と create_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パスワード・手動タグ |
・「消されたら困る重要リソース」→ 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ツール選定の判断軸
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。
登録無料・いつでも解除できます