こうした問題を未然に防ぐために、Terraformにはバリデーション機能が用意されている。apply前に入力値を検証するprecondition、リソース作成後の状態を確認するpostcondition、そして継続的な整合性チェックを担うcheckブロックの3種類だ。
この記事では、Terraform 1.2以降で使えるprecondition・postconditionと、Terraform 1.5以降のcheckブロックの構文と使い分けを解説する。RHEL 9.4・Terraform 1.9で動作確認済みのコード例を中心に説明する。
この記事のポイント
・preconditionはapply前に入力値を検証し、失敗するとapplyをブロックできる
・postconditionはリソース作成後の状態をselfで参照して確認できる
・checkブロックは非ブロッキングで継続的なインフラ整合性チェックができる
・3機能を組み合わせると入力→作成→状態の3段階バリデーションが実現できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜTerraformにバリデーション機能が必要なのか
Terraformは変数の型(string・number・bool)を宣言できるため、型の不一致はplan実行時のプロバイダーエラーとして早期に検出できる。しかし型が合っていても、論理的に不正な値は素通りしてしまう。「本番環境ではインスタンスタイプにt2.microを使ってはいけない」という制約は、型チェックでは表現できない。実際の現場でも、開発環境用の設定ファイルを本番に流用したことで意図しないインスタンスタイプが起動するミスは起こりうる。
こうした論理的な制約をコードで表現するために、Terraformは以下の3つのバリデーション機能を提供している。
・precondition:リソースが作成される前に条件を検証する。条件を満たさない場合はapplyをブロックする(Terraform 1.2以降)
・postcondition:リソースが作成された後に状態を確認する。期待値と一致しない場合はエラーにする(Terraform 1.2以降)
・checkブロック:applyとは独立して外部の状態やリソース属性を継続的に確認する。警告は出るが処理はブロックしない(Terraform 1.5以降)
preconditionの使い方|apply前に入力値を検証する
1. 基本構文と記述位置
preconditionはresource・data source・output valueのlifecycleブロック内に記述する。# インスタンスタイプを許可リストで制限するpreconditionの例 variable "instance_type" { type = string } resource "aws_instance" "web" { ami = "ami-0abcdef1234567890" instance_type = var.instance_type lifecycle { precondition { condition = contains(["t3.medium", "t3.large", "m5.large"], var.instance_type) error_message = "instance_typeはt3.medium・t3.large・m5.largeのいずれかを指定してください。" } } }
terraform applyは次のようなエラーで停止する。# 条件不一致時のエラー出力例(RHEL 9.4 / Terraform 1.9.2) $ terraform apply -var="instance_type=t2.micro" ╷ │ Error: Resource precondition failed │ │ on main.tf line 10, in resource "aws_instance" "web": │ 10: precondition { │ │ instance_typeはt3.medium・t3.large・m5.largeのいずれかを指定してください。 ╵
2. 複数のpreconditionを組み合わせる
一つのlifecycleブロックに複数のpreconditionを記述できる。条件はすべて満たす必要があり、いずれか一つでも失敗するとapplyが止まる。# 本番環境ではMulti-AZを必須にする複合precondition resource "aws_db_instance" "main" { identifier = var.db_identifier engine = "mysql" engine_version = "8.0" instance_class = var.db_instance_class multi_az = var.multi_az username = var.db_username password = var.db_password lifecycle { # 本番環境ではMulti-AZを必須にする precondition { condition = var.environment != "prod" || var.multi_az == true error_message = "本番環境(environment=prod)ではmulti_azをtrueに設定してください。" } # DBインスタンスクラスを許可リストで制限する precondition { condition = contains(["db.t3.medium", "db.r6g.large", "db.r6g.xlarge"], var.db_instance_class) error_message = "db_instance_classの値が許可リストに含まれていません。" } } }
3. error_messageの書き方のポイント
error_messageに記述する内容が、トラブルシュートの速度を大きく左右する。次の情報を盛り込むと運用しやすい。・何が問題か:「multi_azがfalseになっています」のように具体的な状態を書く
・何を直せばよいか:「trueに設定してください」のように対処を明記する
・なぜ制約があるか:「本番環境の可用性要件のため」のように理由を添える
漠然とした「設定が不正です」という表現では、後から見た担当者が原因を調べるのに時間がかかる。
postconditionの使い方|リソース作成後の状態を確認する
1. 基本構文とselfの使い方
postconditionもlifecycleブロックの中に書く。preconditionとの大きな違いは、作成済みリソースの属性をselfで参照できる点だ。# 作成後のEC2にパブリックIPが割り当てられているか確認する resource "aws_instance" "app" { ami = var.ami_id instance_type = "t3.medium" subnet_id = var.public_subnet_id associate_public_ip_address = true lifecycle { postcondition { condition = self.public_ip != "" error_message = "EC2にパブリックIPが割り当てられていません。サブネットのauto-assign public IP設定を確認してください。" } } }
selfはリソースが作成された後の実際の属性値を参照する。preconditionでselfを使おうとするとエラーになるため注意が必要だ。2. data sourceへのpostcondition
data sourceにも同様にpostconditionを適用できる。取得したAMIや既存リソースの状態が期待どおりかを、後続リソースの作成前に確認するために使う。# 取得したAMIのアーキテクチャを確認する(RHEL 9.4 / us-east-1 で動作確認済み) data "aws_ami" "rhel9" { most_recent = true owners = ["309956199498"] # Red Hat filter { name = "name" values = ["RHEL-9.*x86_64*"] } lifecycle { postcondition { condition = self.architecture == "x86_64" error_message = "取得したAMIがx86_64アーキテクチャではありません。フィルター条件を確認してください。" } } }
>> Terraform実践セミナーの詳細はこちら
checkブロックの使い方|継続的な整合性チェック
1. checkブロックはなぜ非ブロッキングなのか
preconditionとpostconditionはapplyを止めるゲートとして機能する。一方、checkブロックはインフラの継続的な整合性を確認するために設計されており、条件が失敗しても警告を出すだけでapplyの処理は続行する。ALBのヘルスチェックエンドポイントは、デプロイ後の暖機運転中に一時的に失敗することがある。その場合にapplyそのものをブロックしてしまうとデプロイが止まる。checkブロックは「監視として記録したいが、失敗してもapplyは通したい」という用途に適している。
2. checkブロックの基本構文
checkブロックはリソースやmoduleと同じトップレベルに記述する。オプションでdataブロックをcheckの中に書くと、checkスコープだけで使えるデータソースを定義できる。# ALBのヘルスチェックエンドポイントを監視するcheckブロック check "alb_health" { data "http" "health_endpoint" { url = "https://${aws_lb.main.dns_name}/health" } assert { condition = data.http.health_endpoint.status_code == 200 error_message = "ALBヘルスチェックが200を返していません(ステータス: ${data.http.health_endpoint.status_code})。ターゲットグループの状態を確認してください。" } }
# checkブロック失敗時の出力例(applyは継続される) $ terraform plan ╷ │ Warning: Check block assertion failed │ │ on checks.tf line 5, in check "alb_health": │ 5: assert { │ │ ALBヘルスチェックが200を返していません(ステータス: 503)。 │ ターゲットグループの状態を確認してください。 ╵ No changes. Your infrastructure matches the configuration.
3. precondition・postconditionとcheckブロックの使い分け
3機能の選択基準を整理すると次のようになる。・precondition:「この条件が満たされなければリソースを作ってはいけない」場合に使う。入力バリデーション・ポリシー強制が主な用途
・postcondition:「リソースが作成されたが、期待した状態かどうか確認したい」場合に使う。AMI確認・割り当てIP確認が主な用途
・checkブロック:「インフラは動いているが、継続的に外部サービスとの整合性を監視したい」場合に使う。ヘルスチェック・外部依存の監視が主な用途
実務での組み合わせパターン
3機能を組み合わせた実用的な構成例を示す。本番DBの作成時に入力検証→作成後確認→継続監視を一つのモジュールで表現する。# 3機能を組み合わせた本番DBモジュールの例 resource "aws_db_instance" "main" { identifier = var.db_identifier engine = "mysql" engine_version = "8.0" instance_class = var.instance_class multi_az = var.multi_az username = var.db_username password = var.db_password lifecycle { # precondition: apply前にインスタンスクラスと本番要件を検証 precondition { condition = var.environment != "prod" || var.multi_az == true error_message = "本番環境(prod)ではmulti_azをtrueに設定してください。" } precondition { condition = contains(["db.t3.medium", "db.r6g.large"], var.instance_class) error_message = "instance_classの値が許可リストに含まれていません。" } # postcondition: 作成後にstatusがavailableになっているか確認 postcondition { condition = self.status == "available" error_message = "DBのstatusがavailableになっていません。VPCセキュリティグループとサブネット設定を確認してください。" } } } # checkブロック: DBエンドポイントが設定されているか継続的に確認 check "db_endpoint_exists" { assert { condition = aws_db_instance.main.endpoint != "" error_message = "DBエンドポイントが空になっています。インスタンスの状態を確認してください。" } }
バリデーションエラーが出た時の調査手順
1. preconditionでselfを参照するとエラーになる
selfはpostconditionでのみ使える。preconditionの段階ではリソースがまだ存在しないため、selfを参照しようとするとエラーが発生する。# NG: preconditionでselfを使うとエラー lifecycle { precondition { condition = self.instance_type == "t3.medium" # selfは使えない error_message = "..." } } # OK: preconditionではvarやdataソースを参照する lifecycle { precondition { condition = var.instance_type == "t3.medium" error_message = "..." } }
2. checkブロックにassertがないとエラーになる
checkブロックにはassertブロックが最低1つ必要だ。dataブロックだけでassertを書き忘れるとterraform planが失敗する。# NG: assertがない(planエラーになる) check "test" { data "http" "health" { url = "https://example.com" } } # OK: assertを含める check "test" { data "http" "health" { url = "https://example.com" } assert { condition = data.http.health.status_code == 200 error_message = "エンドポイントが応答していません。" } }
3. postconditionがplan時に評価されないケース
postconditionの条件は「apply後」に評価される。plan段階では属性値が確定していないため、postconditionはpランでは常に通過する。実際にリソースを作成してみて初めてpostconditionが評価される。このため、postconditionのテストは必ずapplyを走らせて確認すること。本記事のまとめ
Terraformのバリデーション機能3種類をまとめると次のようになる。| 機能 | 実行タイミング | ブロッキング | 記述場所 |
|---|---|---|---|
| precondition | apply実行前 | はい | lifecycle { precondition {} } |
| postcondition | リソース作成後 | はい | lifecycle { postcondition {} } |
| checkブロック | plan・apply後 | いいえ(警告のみ) | トップレベルcheck {} ブロック |
terraform versionコマンドで事前に確認しておくこと。3機能を適切に使い分けることで、「誤った設定でリソースが作られてしまった」というミスをコードの段階で防げるようになる。まずは新規で書くリソースのpreconditionから試してみてほしい。
>> Terraform実践セミナーの詳細はこちら
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:terraform apply -targetの使い所と危険な乱用パターン|ピンポイント変更の判断基準と安全な運用設計
- この記事の属するカテゴリ:Terraformへ戻る

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