Terraform 基礎 #2 HCL の基本文法 — ブロック、引数、リソースアドレスと変更の 3 つの形
前回は main.tf 1 つで S3 バケットを作って消しました。そのとき「文法は次回に」と先送りした約束を果たす番です。Terraform のコードは HCL(HashiCorp Configuration Language)という専用言語で書きます。汎用言語より学ぶことがはるかに少なく、構造を一度つかめば、初めて見るリソースでもドキュメントを見ながらすぐ書けるようになります。今回はブロックと引数という骨格、リソースアドレスと参照、そして plan 出力の読み方を手に馴染ませていきます。
HCL の骨格 — ブロックと引数 #
HCL ファイルはすべてブロック(block) と引数(argument) の 2 つでできています。前回のコードをもう一度見ます。
resource "aws_s3_bucket" "hello" {
bucket = "my-terraform-hello-20260822"
tags = {
ManagedBy = "terraform"
}
}- ブロックは
ブロックタイプ "ラベル" { ... }という形です。resourceブロックはラベルを 2 つ(リソースタイプ、名前)受け取り、providerブロックは 1 つ、terraformブロックはラベルなしです。いくつ受け取るかはブロックタイプが決めます。 - 引数はブロック本体の中の
名前 = 値の 1 行です。bucket = "..."が引数です。 - ブロックの中にさらにブロックを入れられます。値が単純な
tagsはマップ値を持つ引数ですが、後で登場するlifecycle { ... }のように=なしで波かっこが直接続くネストブロックもあります。 - コメントは
#で始めます。
文法の全体は実質これだけです。リソースごとに変わるのは「どの引数を受け取るか」で、それは プロバイダーのドキュメントにリソース別に整理されています。HCL を知っているとは、文法を暗記することではなく、ドキュメントで引数を探して埋めることに近い作業です。
リソースアドレス — タイプと名前 #
resource "aws_s3_bucket" "hello" の 2 つのラベルをつなげた aws_s3_bucket.hello が、このリソースのアドレスです。ここで初心者が最も混乱しやすいのが 2 つ目のラベルです。hello は AWS に渡される値ではなく、Terraform のコードの中だけで使う名前です。実際のバケット名は bucket 引数の値であり、コンソールのどこにも hello という文字は現れません。だからこそ、同じタイプのリソースを複数宣言するときは、それぞれ違う名前を付けて区別します。
アドレスは 3 つの場面で使われます。plan 出力がリソースを指すとき、別のリソースが参照するとき、そして #5 で扱う state コマンドが対象を指定するときです。コードを読む人が役割を分かる名前(logs、static_assets)を付けるのがよく、bucket1 のような名前は 1 か月後の自分を苦しめます。
リソース同士を参照する #
インフラはリソース 1 つでは終わりません。バケットにバージョニング設定を付けてみます。AWS プロバイダーではバージョニングは別のリソースです。
resource "aws_s3_bucket" "hello" {
bucket = "my-terraform-hello-20260822"
}
resource "aws_s3_bucket_versioning" "hello" {
bucket = aws_s3_bucket.hello.id
versioning_configuration {
status = "Enabled"
}
}肝心なのは bucket = aws_s3_bucket.hello.id です。アドレスの後ろにドットを打って属性名を付けると、そのリソースの値を取得できます。バケット名を文字列でもう一度書かずに参照でつないだので、バケット名が変わってもこの行を直す必要はありません。どの属性を参照できるかも、プロバイダードキュメントのリソースページ下部(Attribute Reference)に整理されています。参照には文法以上の意味がもう 1 つあり、Terraform はこの参照を見て作成順序を決めます。その話は #6 でします。
変更の 3 つの形 — create、update、replace #
このコードで terraform apply をしてから、タグを 1 つ変えてもう一度 plan を実行してみます。
# aws_s3_bucket.hello will be updated in-place
~ resource "aws_s3_bucket" "hello" {
~ tags = { ... }
}記号が + ではなく ~ です。すでにあるバケットはそのままに、タグだけ直すという update in-place です。今度は bucket 引数のバケット名そのものを変えてみます。
# aws_s3_bucket.hello must be replaced
-/+ resource "aws_s3_bucket" "hello" {
~ bucket = "my-terraform-hello-20260822" -> "new-name" # forces replacement
}-/+ は消して作り直すという意味です。S3 バケット名は作った後に変えられない属性なので、名前を変えるには置き換えしかありません。中にデータがあればそのまま消えてしまう操作なので、plan でこの記号を見たら必ず立ち止まって確認する必要があります。まとめると、plan の記号は 4 つです。
| 記号 | 意味 |
|---|---|
+ | 新しく作ります |
~ | その場で修正します |
-/+ | 消して作り直します(置き換え) |
- | 消します |
どの属性が置き換えを引き起こすかはプロバイダーが決め、plan 出力の # forces replacement コメントが教えてくれます。置き換えを制御する方法は #7 で詳しく扱います。
fmt と validate — コミット前の習慣 #
HCL には公式フォーマッターが組み込まれています。
terraform fmt # カレントディレクトリの .tf ファイルの整列・インデントを整える
terraform validate # 文法と参照のエラーを検査(AWS 呼び出しなし)fmt はインデントと = の整列を標準形にそろえ、validate は AWS に接続せずにコード自体のエラー(タイプミスした引数、存在しない参照)を捕まえます。どちらも数秒で終わるので、コミット前に回す習慣を付ければ、レビューでスタイル指摘が消えます。チームでこれを CI で強制する方法は運用シリーズで扱います。
まとめ #
今回扱った内容です。
- HCL はブロックと引数の 2 つでできています。リソース別の引数は暗記するものではなく、プロバイダーのドキュメントで探すものです
aws_s3_bucket.helloのhelloは Terraform の中だけで使う名前で、AWS には渡されません。役割が伝わる名前を付けます- 別のリソースの値は
アドレス.属性で参照します。文字列をコピーせず参照でつなぐと、変更に強くなります - plan 記号
+、~、-/+、-のうち-/+(置き換え)はリソースを消して作り直すという意味なので、必ず確認します terraform fmtとvalidateはコミット前の習慣です
次回(#3 変数・出力・ローカル値)では、これまでハードコーディングしてきた値を変数に切り出す方法を扱います。同じコードで開発用と本番用を作れるようになる最初の一歩です。