はじめに
Terraformを学び始めたとき、自分は「AWSをコードで作るツール」くらいに考えていました。
でも、それだけだと State や Provider がなぜ必要なのか、plan が何を比べているのかがうまくつながりません。
調べていくと、Terraformは 何を材料に差分を考え、どこを通じて実物を読み書きするのか で見ると理解しやすいと分かりました。
TL;DR
Terraformは、Stateを手がかりに実インフラの現在状態を確認します。
Configとの差分をPlanとして計算し、Provider経由で必要な変更を実行するツールです。
1. Terraformは「手順」ではなく「最終状態」を書く
TerraformはInfrastructure as Code(IaC)のツールです。
IaCは、インフラを画面操作だけで管理するのではなく、設定ファイルとして定義し、変更履歴やレビューの対象にする考え方です。
Terraformらしさが出るのは、その設定が宣言的なところでした。
resource "aws_instance" "web" {
ami = "ami-xxxxxxxx"
instance_type = "t3.micro"
}
ここには「EC2作成APIを呼んで、次にこの設定をして……」という手順は書いていません。
書いているのは「web というEC2がこの状態で存在してほしい」という最終状態です。
| 手順を中心に書く | Terraform |
|---|---|
| 何をどの順番で実行するかを書く | 最終的にどうなってほしいかを書く |
| 変更手順を人間が組み立てる | Terraformが現在との差分から変更内容を考える |
| API呼び出しの順番が主役 | Resource同士の関係とDesired Stateが主役 |
宣言だけで、作成順序は大丈夫なのか
ここで気になったのが、「VPCより先にSubnetを作ったりしないのか?」ということでした。
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "app" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}
aws_subnet.app は aws_vpc.main.id を参照しています。
Terraformはこの参照から依存関係を検出し、「SubnetはVPCの後に処理する必要がある」と判断します。依存しないResourceは並列で処理できます。
つまり、人間は手順そのものを書く代わりに、Resource同士の関係をConfigに表現するわけです。
2. 全体像は Config・State・Provider・実インフラで見る
「最終状態を書く」だけなら、Terraformは何を見て差分を判断するのでしょうか。
自分は次の4要素で見ると整理しやすくなりました。
Config: 「こうしたい」
Configは .tf に書くTerraform configurationです。
ここにはTerraformに維持してほしい状態、つまりDesired Stateを書きます。
State: 「どの実物を管理しているか」
Configには aws_instance.web と書けますが、AWS上の実物は i-012345... のようなIDを持ちます。
Stateは、このTerraform上のResourceと実際のremote objectの対応を保持します。
Provider: 外部システムを読み書きする窓口
Terraform本体がAWSやGitHubなど各サービスのAPI仕様を全部持つのではなく、Providerを通じて外部システムとやり取りします。
初学者としては、Providerを 「Terraformがremote systemを読み書きする窓口」 と考えるとつながりやすかったです。
実インフラ: AWSなどに存在する本物
Terraformが扱うのはConfig上の情報だけではありません。
通常の plan ではProvider経由で実物の現在状態を読み、apply ではProvider経由で必要な変更を書き込みます。
plan : remote system → Provider → Terraform
apply : Terraform → Provider → remote system
なのでTerraformは、ConfigをそのままAWS APIへ変換するだけのツールではありません。
Stateで管理対象を特定し、実物を読み、Configとの差分を判断する仕組みと考えると、4要素が一本につながりました。
3. init → plan → apply は1本のworkflow
全体モデルが見えると、init、plan、apply も単独のコマンドではなく役割分担として理解できます。
terraform init: 実行準備
terraform init はインフラを作るコマンドではありません。
Providerの取得、Moduleの取得、Backendの初期化など、そのworking directoryでTerraformを動かす準備をします。
terraform plan: 現在との差分を計算
通常の terraform plan は、既存remote objectのcurrent stateを読みます。
そのうえでcurrent configurationとprior stateを比較し、必要な変更を提案します。
たとえばConfigを次のように変えたとします。
resource "aws_instance" "web" {
- instance_type = "t3.micro"
+ instance_type = "t3.small"
}
TerraformはGit diffだけを見ているわけではありません。
Stateから管理対象を特定し、Provider経由で実物を確認したうえでPlanを作ります。
そのため、AWSコンソールなどから手動変更が入っていれば、その現在状態もPlanに影響します。
plan 自体は、提案したcreate / update / destroyを実行しません。
terraform apply: 実際に変更する
ここは2つの使い方を分けると混乱しません。
terraform apply
保存済みPlanを渡さない場合、Terraformはその場で新しいexecution planを作成し、確認後に実行します。
一方、
terraform plan -out=tfplan
terraform apply tfplan
とした場合は、保存したPlanを実行します。
変更が成功するとStateも更新され、そのStateが次の plan で再び使われます。
Configを変更
↓
planで差分確認
↓
applyで実物を変更
↓
State更新
↓
次のplanへ
Terraformがインフラのlifecycleを管理するというのは、このループを継続することでもあります。
4. なぜStateが必要なのか
自分が最初に引っかかったのは、「AWS APIから現在状態を取得できるなら、Stateはいらないのでは?」という疑問でした。
でもTerraformには、実物の状態だけでなく、Config上のどのResourceと、どの実物が対応しているのかという情報が必要です。
Configには、
aws_instance.web
というResourceがあります。
AWS上では、たとえば、
i-0123456789abcdef0
という実体です。
TerraformはStateにこのbindingを持ち、「aws_instance.web はこのEC2を管理している」と追跡します。
HashiCorpもStateのprimary purposeを、configuration内のResource instanceと
remote objectのbindingを保存することだと説明しています。
Stateには属性値や依存関係などの情報も含まれます。
ただ、「AWSの状態をコピーしたファイル」と考えるより、Terraformが前回から何を管理しているかを覚えるデータと考える方が理解しやすかったです。
Stateの保存方法を決めるのがBackendです。Backendを明示しなければlocal backendが使われます。
チームでのremote StateやlockingはStateだけでかなり話が広がるので、全体像の記事ではここまでに留めます。
5. Resourceは「管理」、Data Sourceは「参照」
Terraformが外部システムを読み書きできると分かると、「外部のものは全部Resourceとして管理するのか?」という疑問が出てきます。
Terraformには、管理する resource と、データを読み取る data があります。
Resource: Terraformにlifecycleを管理させる
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
ResourceはTerraformがcreate・update・destroyなどのlifecycleを管理する対象です。
Configを変えれば差分を計算し、Configから削除すればdestroy候補になることがあります。
Data Source: Terraform自身が管理せず、データを読み取る
Data Sourceは、Terraform自身がその対象のlifecycleを管理するのではありません。
Providerなどからデータを読み取り、Config内で参照するために使います。
たとえば、Canonicalが公開しているUbuntu 24.04のAMIから最新のものを探すなら、次のように書けます。
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd-gp3/ubuntu-noble-24.04-amd64-server-*"]
}
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
}
data.aws_ami.ubuntu はAMIをTerraform管理下に入れているわけではありません。
条件に合うデータを取得し、そのIDをEC2 Resourceから参照しています。
TerraformのLanguage docsでも、Data SourceはProviderなどからデータを取得しますが、
Resourceをcreate・modifyしないと説明されています。
AWS Provider v6では、aws_ami で most_recent = true を使う場合、
owners または owner-id / image-id で検索対象を絞る必要があります。
上の例ではCanonicalのowner IDを指定しています。
自分の中では、
resource = Terraformに管理してほしいもの
data = Terraformから参照したいデータ
と分けると整理しやすかったです。
Terraformが得意な領域
Terraformの中心は、Providerが扱えるremote objectを宣言し、その状態を継続管理することです。
| やりたいこと | Terraformとの相性 |
|---|---|
| VPC / Subnetを管理する | 高い |
| EC2 / RDSの構成を管理する | 高い |
| GitHub Repositoryを管理する | 高い |
| 既存AMIなどの情報を参照する | Data Sourceが合う |
| EC2内部の細かなOS設定を順番に流す | 主役ではない |
| DB schema migrationを順番に適用する | 主役ではない |
クラウドかどうかより、APIで表現できるremote objectの状態を宣言的に扱う仕事かで
考える方が、Terraformの担当範囲を判断しやすいと思います。
6. Terraform Repositoryは「何を管理するか」から読む
全体像がつながった後は、Repositoryもファイル名を片っ端から読むより、いくつかの観点を順番に見る方が読みやすくなりました。
1. Resource、またはModuleを見る
小さなRepositoryなら resource を見れば管理対象が分かります。
resource "aws_s3_bucket" "logs" {
# ...
}
ただし、root moduleにResourceがほとんどなく、
module "network" {
source = "./modules/network"
}
のようにModule呼び出しが中心の構成もあります。
その場合は、呼び出しているModuleの中で何を管理しているかをたどります。
Module設計自体を理解しなくても、「Resourceがない = 何もしていない」ではないと知っておくだけでRepository読解が止まりにくくなります。
2. Providerを見る
required_providers や provider blockから、どのremote systemとやり取りするのかを確認します。
3. Stateの管理方法を見る
backend や cloud の設定があれば、Stateをどう管理しているかの手がかりになります。
terraform {
backend "s3" {}
}
ただし、Backend設定を terraform init -backend-config=... で追加する
partial configurationもあります。
そのためRepositoryだけでState保存先を完全に特定できないケースもあります。
4. Planで実際の変更を見る
最後に terraform plan で、「今の実物に対して何が変わるのか」を確認します。
Configを読むと宣言内容は分かりますが、実際の変更案はConfigだけでは決まりません。
Stateとremote objectのcurrent stateも含めてPlanが作られるからです。
この順番で見ると、Terraform Repositoryを単なる .tf ファイルの集合ではなく、
管理対象・接続先・State・実際の差分として読めるようになりました。
おわりに
Terraformを「AWSをコードで作るツール」とだけ覚えていたときは、Resource、Provider、State、Planが別々の用語に見えていました。
今は、
Terraformは、Stateを手がかりに実インフラを確認し、Configとの差分をPlanにして、Provider経由で必要な変更を実行する
という流れで捉えています。
ConfigにはDesired Stateを書く。
Resourceの依存関係から実行順序を判断する。
Stateで管理対象との対応を持つ。
Providerで外部システムを読み書きする。
この流れがつながると、init / plan / apply も単なるコマンド暗記ではありません。
Terraformがインフラを継続管理するworkflowの一部として見えるようになりました。





