Terraformのprecondition・postcondition・checkでリソース設定を自動検証する方法|Terraform 1.5以降のバリデーション機能と実践設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > Terraformのprecondition・postcondition・checkでリソース設定を自動検証する方法|Terraform 1.5以降のバリデーション機能と実践設計
「terraform applyを実行したら、変数に想定外の値が渡されていてEC2インスタンスのタイプが期待と違っていた」と後から気づくケースは珍しくない。リソースが作成されたあとで設定値の誤りに気づいても、修正のためにdestroyしてapplyし直す手間がかかる。

こうした問題を未然に防ぐために、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段階バリデーションが実現できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は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のいずれかを指定してください。 ╵

applyの実行がブロックされ、リソースは一切作成されない。コードレビューや手動確認を待たずに、設定の誤りをCI/CDの段階で自動的に止められる。

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アーキテクチャではありません。フィルター条件を確認してください。" } } }

フィルターパターンを誤って変更したときに、意図せずarm64のAMIが取得されるケースをpostconditionで防げる。実際にRHEL 9.4環境でフィルターを変更した際に、このpostconditionがarm64 AMIの混入を検出した。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、precondition・postcondition・checkによるTerraformバリデーション設計から実運用まで、実務直結のTerraformスキルをハンズオン形式で習得できるセミナーを開催しています。
>> 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ブロックが失敗した場合のplan出力は次のようになる。

# 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.

applyは継続されるが警告として記録される。CI/CDのログに残るため、後からチームで原因を確認できる。

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エンドポイントが空になっています。インスタンスの状態を確認してください。" } }

DBのプロビジョニングは時間がかかるため、apply前のpreconditionチェックでミスを早期に発見することが重要だ。誤った設定値でapplyを走らせてRDSインスタンスが中途半端な状態になるリスクを、このパターンで減らせる。

バリデーションエラーが出た時の調査手順

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 {} ブロック
preconditionとpostconditionはTerraform 1.2以降、checkブロックはTerraform 1.5以降で使える。バージョンはterraform versionコマンドで事前に確認しておくこと。

3機能を適切に使い分けることで、「誤った設定でリソースが作られてしまった」というミスをコードの段階で防げるようになる。まずは新規で書くリソースのpreconditionから試してみてほしい。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、precondition・postcondition・checkブロックによる設定バリデーション設計から本番運用まで、実務直結のTerraformスキルをハンズオン形式で習得できるセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

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

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

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

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

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

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

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

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

この記事を書いた人

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

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

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