Terraform 基礎 #2 HCL の基本文法 — ブロック、引数、リソースアドレスと変更の 3 つの形

読了 5分

前回main.tf 1 つで S3 バケットを作って消しました。そのとき「文法は次回に」と先送りした約束を果たす番です。Terraform のコードは HCL(HashiCorp Configuration Language)という専用言語で書きます。汎用言語より学ぶことがはるかに少なく、構造を一度つかめば、初めて見るリソースでもドキュメントを見ながらすぐ書けるようになります。今回はブロックと引数という骨格、リソースアドレスと参照、そして plan 出力の読み方を手に馴染ませていきます。

HCL の骨格 — ブロックと引数 #

HCL ファイルはすべてブロック(block)引数(argument) の 2 つでできています。前回のコードをもう一度見ます。

main.tf
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 コマンドが対象を指定するときです。コードを読む人が役割を分かる名前(logsstatic_assets)を付けるのがよく、bucket1 のような名前は 1 か月後の自分を苦しめます。

リソース同士を参照する #

インフラはリソース 1 つでは終わりません。バケットにバージョニング設定を付けてみます。AWS プロバイダーではバージョニングは別のリソースです。

main.tf
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 を実行してみます。

plan 出力
  # aws_s3_bucket.hello will be updated in-place
  ~ resource "aws_s3_bucket" "hello" {
      ~ tags = { ... }
    }

記号が + ではなく ~ です。すでにあるバケットはそのままに、タグだけ直すという update in-place です。今度は bucket 引数のバケット名そのものを変えてみます。

plan 出力
  # 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.hellohello は Terraform の中だけで使う名前で、AWS には渡されません。役割が伝わる名前を付けます
  • 別のリソースの値は アドレス.属性 で参照します。文字列をコピーせず参照でつなぐと、変更に強くなります
  • plan 記号 +~-/+- のうち -/+(置き換え)はリソースを消して作り直すという意味なので、必ず確認します
  • terraform fmtvalidate はコミット前の習慣です

次回(#3 変数・出力・ローカル値)では、これまでハードコーディングしてきた値を変数に切り出す方法を扱います。同じコードで開発用と本番用を作れるようになる最初の一歩です。

X