Terraformでつまずくのは、HCLの書き方ではありません。「state(状態ファイル)とは何なのか」と「plan の出力をどう読むか」です。ここが分かっていないと、書けるけれど怖くて本番に流せない、という状態が続きます。
この2つは、AWSアカウントなしで確かめられます。ローカルにファイルを作るだけのプロバイダーを使えば、課金も権限設定もリソースの消し忘れも起きません。この記事のコマンドと出力は、すべて実際に手元で実行したものです(Terraform v1.14.4)。
まず動かす|書くのは15行
空のディレクトリに main.tf を1つ置きます。
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
variable "environment" {
type = string
default = "dev"
}
resource "local_file" "config" {
filename = "${path.module}/out/${var.environment}.conf"
content = "environment = ${var.environment}\nreplicas = 2\n"
}
terraform init でプロバイダーを取ってきます。
$ terraform init
Initializing provider plugins...
- Finding hashicorp/local versions matching "~> 2.5"...
- Installing hashicorp/local v2.9.0...
- Installed hashicorp/local v2.9.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
最後の1行は覚えておいてください。.terraform.lock.hcl はコミットするファイルです。これがないと、チームの各自やCIで違うバージョンのプロバイダーが入り、同じコードなのに結果が変わります。
plan は「これから何をするか」の宣言
$ terraform plan
# local_file.config will be created
+ resource "local_file" "config" {
+ content = <<-EOT
environment = dev
replicas = 2
EOT
+ filename = "./out/dev.conf"
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
記号に意味があります。
| 記号 | 意味 |
|---|---|
+ | 作成する |
~ | その場で変更する |
- | 削除する |
-/+ | 削除してから作り直す |
(known after apply) は「実行してみないと決まらない値」です。IDのように、作成後にプロバイダー側が採番するものがこれになります。
そしてplan を読むとき、最初に見るべきなのは最終行です。
Plan: 1 to add, 0 to change, 0 to destroy.
差分が長いときほど、上から順に読んでいると全体を見落とします。to destroy が 0 でないなら、そこで一度止まって理由を確認するのが習慣として一番効きます。
apply して state を見る
$ terraform apply -auto-approve
local_file.config: Creating...
local_file.config: Creation complete after 1s [id=893231be2a6406e0625bdb02bd46e0844045c3f8]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
$ cat out/dev.conf
environment = dev
replicas = 2
$ terraform state list
local_file.config
ここで terraform.tfstate というファイルができています。これがstateです。「Terraformが自分で作ったものの台帳」だと思ってください。
stateの正体|手で変えると何が起きるか

Terraformの動きを理解する一番早い方法は、Terraformが作ったものを手で変えてみることです。作られたファイルに1行足してみます。
$ echo "replicas = 99" >> out/dev.conf
$ terraform plan
# local_file.config will be created
+ resource "local_file" "config" {
...
Plan: 1 to add, 0 to change, 0 to destroy.
「作成する」と言っています。ファイルは存在するのに、です。
Terraformは plan のたびに実物を確認しにいきます。そして「stateに記録した内容と実物が違う」ことを検出し、自分が管理している状態に戻そうとします。これがドリフト(drift)です。手で足した replicas = 99 は、次の apply で消えます。
ここがTerraform運用の核心です。
- コードが正であり、手作業の変更は「間違い」として扱われる
マネジメントコンソールでの変更は、次の apply で巻き戻される - stateに載っていないものは、Terraformにとって存在しない
手で作ったリソースは、Terraformからは見えず、消えることもない
「急いでいたのでコンソールで直した」が事故になるのはこのためです。逆に言えば、コンソールで直したくなった時点で、コードに反映する手順を用意していないという設計の問題があります。

「変更」と「作り直し」は別物
もっとも事故につながるのがここです。設定の replicas を 2 から 3 に変えて plan を取ります。
$ terraform plan
~ content = <<-EOT # forces replacement
~ id = "893231be2a..." -> (known after apply)
Plan: 1 to add, 0 to change, 1 to destroy.
中身を少し変えただけなのに、1 to destroy、つまり一度削除されます。理由は content の行に書いてある # forces replacement です。
変数を変えた場合はもっとはっきり出ます。
$ terraform plan -var environment=prod
# local_file.config must be replaced
~ filename = "./out/dev.conf" -> "./out/prod.conf" # forces replacement
Plan: 1 to add, 0 to change, 1 to destroy.
ここではただのテキストファイルですが、同じことがRDSやEBSでも起きます。あとから変更できない属性を書き換えると、Terraformは淡々と「削除して作り直す」計画を立てます。データは戻りません。
覚えておくべきことは1つです。forces replacement と must be replaced を見たら、apply の前に必ず止まる。この2つの文字列を plan の出力から探す癖をつけるだけで、防げる事故がかなりあります。
どの属性が作り直しを引き起こすかはリソースごとに違うので、プロバイダーのドキュメントで Forces new resource の記載を確認します。
実務に移すときに追加で要るもの
ここまではローカルで完結する話です。実際のクラウド環境に使うときは、次が必要になります。
stateをローカルに置かない
stateがローカルにあると、複数人で作業したときに互いの変更を上書きします。S3などのリモートバックエンドに置き、同時実行をロックできる構成にします。
tfstateをコミットしない
# .gitignore
*.tfstate
*.tfstate.*
.terraform/
*.tfvars # 値によっては除外する
# .terraform.lock.hcl はコミットする(除外しない)
理由は運用上の都合ではなくセキュリティです。tfstateには、リソースの属性が平文で記録されます。データベースの初期パスワードやトークンを扱っていれば、それも入ります。公開リポジトリに上げると認証情報の流出になります。
複数リソースは count ではなく for_each
同じリソースを複数作るとき、count はインデックスで管理されるため、途中の要素を消すと後続がすべてずれて作り直しになります。キーで管理される for_each を使うのが基本です。この挙動の詳細はVPCのCIDR設計の記事で具体例つきで書いています。
CIでは plan と apply を分ける
terraform plan -out=tfplan で計画をファイルに保存し、レビュー後に terraform apply tfplan でそれを適用します。こうしないと、レビューした内容と実際に適用される内容がずれる可能性があります。plan と apply の間に誰かが別の変更を入れた場合が典型です。
最初に押さえる5つ
- stateはTerraformが作ったものの台帳
載っていないものは存在しないのと同じ - 手で変えたものは巻き戻される
コンソールでの変更は次のapplyで消える - planは最終行から読む
to destroyが0でなければ止まる forces replacementは削除と再作成
データを持つリソースでは致命的になりうる- tfstateは秘密情報
コミットしない。.terraform.lock.hclはコミットする
この5つは、ローカルプロバイダーで全部確認できます。AWSアカウントを触る前に、消えても困らない場所で一通り壊してみるのが、結局いちばん早い理解の仕方だと思います。
次に読む記事
