最終確認: 2026年5月
004 試験の対象となる AWS サービスを、プレーンな Terraform を使用して構築します。1 ブロックずつ、それぞれ試験ドメインに関連付けられています。同じコードが OpenTofu でも動作します。
このラボを完了するまでに、Terraform のコアワークフロー全体を最初から最後まで実行し、クラウドアカウントや費用を一切かけずに、Associate 試験の全ドメインに触れることができます。資格情報が不要な random プロバイダーと local プロバイダーを使用するため、試験で実際に問われること、つまり Terraform 自体の動作に焦点を当てることができます。
最初のリソースを記述し、変数と出力でパラメータ化し、for_each で多数のリソースを生成し、繰り返しを再利用可能なモジュールにリファクタリングし、ステートを検査して移行し、最後に設定を HCP Terraform に向けてリモート実行を行います。すべてのスニペットはプレーンな Terraform であり、OpenTofu でも同じコードが修正なしで動作します。ブロックを単一の main.tf にドロップし(いくつかのファイルに分かれているものについては別途説明します)、terraform init を一度実行してから、terraform apply をステップバイステップで実行してください。
>= 1.5 または OpenTofu >= 1.6 があること(terraform version)。ステップ 6 では設定駆動型の import ブロックを使用するため、1.5 以上が必要です。random プロバイダーと local プロバイダーは完全にローカルマシンで動作します。mkdir tf-associate-lab && cd tf-associate-lab)。このラボは完全に無料です。random および local プロバイダーはクラウドのリソースを作成しません — あなた自身のディスク上のいくつかの小さなファイルと、ローカルの terraform.tfstate にエントリを作成するだけです。ステップ 7(HCP Terraform)は無料枠を使用し、単一のラボワークスペースには十分です。アイドル状態でも課金されるものはありません。
何かを実行する前に、期待する Terraform のバージョンと依存するプロバイダーを宣言します。バージョン固定は試験でよく出題されます — Terraform の基礎およびコアワークフローの両方のドメインで、required_version、required_providers ブロック、およびプロバイダープラグインを .terraform/ にダウンロードする terraform init の役割を理解しているかが問われます。
意図的に random と local を選択します。どちらもクラウドログインを必要としないため、ラボ全体が無料で再現可能であり、試験では特定のクラウドについては問われず、Terraform について問われます。これを新しい main.tf にドロップし、terraform init を実行します。Terraform が両方のプロバイダーを解決し、.terraform.lock.hcl ファイルにロックするのを確認できます。これは、基礎ドメインの論点でもあります(すべての実行で同じプラグインバージョンが使用されるようにロックファイルをコミットします)。
terraform {
required_version = ">= 1.5"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
# Both providers run locally and need no "provider" configuration
# block at all - a useful reminder that providers are just plugins.次に、Terraform コアワークフローの使用ドメイン(試験の18%)の中心的な部分を実践します。フレンドリーな名前を生成する random_pet と、その名前をディスクに書き込む local_file を宣言します。local_file.greeting リソースは random_pet.name.id を参照しており、この参照が、ファイルがペットに依存していることを Terraform に伝えます。これは暗黙の依存関係順序付けであり、depends_on は必要ありません。
ワークフローを順に実行します: terraform plan は、何も変更が加えられる前に何が変更されるかの差分を表示し、terraform apply はそれを現実のものとし、結果を terraform.tfstate に記録します。何も変更せずに terraform apply を2回実行すると、変更なしと表示されます — これがべき等性であり、なぜ2回目の apply がノーオペレーション(何もしない)になるのかを試験でよく問われます。動作するリソースと新しいステートファイルを手に入れたので、設定を柔軟に作成し始めることができます。
resource "random_pet" "name" {
length = 2
separator = "-"
}
resource "local_file" "greeting" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}ハードコードされた値はデモには問題ありませんが、設定の読み取り、生成、変更ドメイン(19%)ではパラメータ化が求められます。type、default、および許可された環境以外を拒否する validation ブロックを持つ入力 variable を追加します。検証は plan 時に実行され、頻繁な試験問題です。locals マップを一度計算して再利用し、output ブロックで結果を公開することで、他の設定(および terraform output)から読み取れるようにします。
試験で問われるレイヤリングに注目してください: variable は入力、locals は派生/中間値、output は公開された結果です。再度適用し、terraform output pet_name を試して単一の値を読み取るか、terraform output -json を使用してスクリプトで処理してください。入力と出力が設定されたので、一度に1つのリソースを記述するのをやめ、一連のリソース全体を生成する準備ができました。
variable "environment" {
description = "Deployment environment label."
type = string
default = "dev"
validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "environment must be one of: dev, staging, prod."
}
}
locals {
common_tags = {
environment = var.environment
managed_by = "terraform"
}
}
output "pet_name" {
description = "The generated pet name."
value = random_pet.name.id
}
output "tags" {
value = local.common_tags
}実際の設定では、すべてのものを1つずつ宣言することはほとんどありません。ここでは、コレクションからリソース作成を駆動することで、設定の読み取り、生成、変更ドメインをさらに深く掘り下げます。set(string) 変数は論理サービスをリストし、for_each はサービスごとに1つの random_string と1つの local_file を生成し、それぞれ random_string.suffix["api"] などとしてアドレス指定できます。
このステップでは、試験で認識することが期待される式と組み込み関数も紹介します: 現在の要素のための each.key、サービスごとのバケット名を構築するための文字列補間、および HCL オブジェクトをディスク上の JSON ファイルに変換する jsonencode()。ステップ 3 の var.environment と local.common_tags を再利用して、生成されるすべてのファイルが一貫したメタデータを持つようにします。次に当然の疑問 — これは繰り返しの作業になりつつあるが、どうやってパッケージ化するのか? — は、まさにモジュールが答えるものです。
variable "services" {
description = "Logical services to generate a config file for."
type = set(string)
default = ["api", "web", "worker"]
}
resource "random_string" "suffix" {
for_each = var.services
length = 6
special = false
upper = false
}
resource "local_file" "service_config" {
for_each = var.services
filename = "${path.module}/config/${each.key}.json"
content = jsonencode({
service = each.key
environment = var.environment
bucket = "${each.key}-${random_string.suffix[each.key].result}"
tags = local.common_tags
})
}Terraform モジュールとの対話ドメインでは、モジュールを作成し、呼び出し、値を渡したり受け取ったりすることが求められます。サービスごとのロジックを ./modules/service の子モジュールに移動し、独自の variable 入力と output を与え、ルートから for_each で呼び出します — サービスごとに1つのモジュールインスタンスです。
以下の2つのファイルは境界を明確に示しています: 子モジュールはどのサービスが存在するかを知りません(それは呼び出し元の var.name を介した仕事です)、ルートはサービスがどのように構築されるかを知りません(それはモジュール内にカプセル化されています)。モジュールを追加したら terraform init を再度実行してください — 試験では、新しいモジュールソースがインストールされるには再初期化が必要であることが問われます。これで設定がモジュール化されたので、Associate 試験の最後の大きなトピックは、Terraform が常に静かに追跡してきたステートです。
# modules/service/main.tf
variable "name" {
type = string
}
variable "environment" {
type = string
}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
resource "local_file" "config" {
filename = "${path.root}/config/${var.name}.json"
content = jsonencode({
service = var.name
environment = var.environment
bucket = "${var.name}-${random_string.suffix.result}"
})
}
output "bucket_name" {
value = "${var.name}-${random_string.suffix.result}"
}
# main.tf (root) - call the module once per service
module "service" {
source = "./modules/service"
for_each = var.services
name = each.key
environment = var.environment
}
output "service_buckets" {
value = { for k, m in module.service : k => m.bucket_name }
}ステートの実装と維持(19%)とコアワークフロー外での Terraform の使用(9%)の両方のドメインがここにあります。ステートは、設定アドレスを実際のリソースにマッピングする JSON 台帳です。terraform state list はそれを列挙し、terraform state show <addr> は1つのエントリを表示します。試験では、設定でリソース名を変更すると通常は破壊して再作成されることを知っていることが期待されます — ただし、Terraform にアドレスが移動したことを伝えた場合を除きます。
moved ブロックは宣言的にそれを正確に行います: local_file.greeting(ステップ2から)を local_file.welcome に名前変更すると、moved ブロックがステートをその場で移行するため、plan は破壊して再作成ではなく移動を表示します。(命令形での同等の操作は terraform state mv local_file.greeting local_file.welcome です。)また、設定駆動型の import ブロックも示します — これは、古い terraform import CLI コマンドを使用せずに、既存のオブジェクトをステートに取り込むための 1.5 以降の方法です。ステートが制御下にあるので、残りの1つの機能は、これらすべてをリモートで実行することです。
# Renaming a resource? A "moved" block migrates state in place
# instead of destroying and recreating the object. Replace the
# Step 2 "greeting" resource with this renamed "welcome" one.
moved {
from = local_file.greeting
to = local_file.welcome
}
resource "local_file" "welcome" {
filename = "${path.module}/hello.txt"
content = "Hello from ${random_pet.name.id}!\n"
}
# Config-driven import (Terraform 1.5+): adopt an object that
# already exists into state, no "terraform import" CLI command.
# terraform_data is a built-in resource - nothing to provision.
import {
to = terraform_data.tracked
id = "existing-id"
}
resource "terraform_data" "tracked" {}最後のドメインであるHCP Terraform の機能の理解(5%)で、試験が完結します。terraform {} 内の単一の cloud ブロックは、ローカル実行を HCP Terraform に切り替えます。ステートはリモートに保存され、実行中はロックされ、terraform plan/apply は HashiCorp のランナーで実行され、実行出力(および保存されたプラン)は Web UI に表示されます。ここでは、HCP Terraform の機能 — リモートステート、実行履歴、Sentinel/OPA によるポリシー適用、プライベートモジュールレジストリ — と、ステップ 1〜6 で使用した純粋なローカルワークフローとの対比も試験で問われます。
my-org をご自身の組織に置き換え、terraform login を一度実行して API トークンを保存し、次に terraform init を実行してステートをリモートワークスペースに移行します。このラボで記述したすべてのコードは、実行場所が変わっただけで、変更なしで実行されます。ベアな main.tf からリモートバックアップされたワークスペースへのこの往復は、Associate 試験の全体を一度に網羅するものです。
terraform {
cloud {
organization = "my-org"
workspaces {
name = "terraform-associate-lab"
}
}
}
# Run once to authenticate, then re-init to migrate state:
# terraform login
# terraform initすべてがあなたのマシンに保存されるため、クリーンアップは迅速です。
terraform destroy を実行して、生成されたファイルを削除し、ステートからクリアします。terraform-associate-lab ワークスペースを削除するか(または事前に terraform destroy を実行してから)、cloud ブロックを削除してください。cd .. && rm -rf tf-associate-lab。ローカルの .terraform/ プラグインキャッシュ、.terraform.lock.hcl、および terraform.tfstate も一緒に削除されます。Associate 試験は特定のクラウドではなく Terraform というツールに関するものなので、このラボでは意図的にクラウドインフラストラクチャをプロビジョニングしません。AWS / Azure / GCP リソースは意図的にスキップしています。これらは資格情報が必要であり、コストが発生する可能性があり、試験で実際に問われるメカニズム — ワークフロー、ステート、設定言語、モジュール — から注意をそらしてしまうためです。
HCP Terraform 以外のバックエンド(S3、Azure Blob、GCS、Consul)、HashiCorp が最終手段と位置付けている remote-exec のようなプロビジョナー、および複数のステートインスタンスを管理するためのワークスペースなど、Associate 試験のいくつかのトピックは、実際に実行するよりも読む方が適しています。これらのトピックについては、この資格ページにある閲覧およびプレイブックセクションで概念的な解説があります。ここでのハンズオンの価値は、init → plan → apply の筋肉の記憶、ステートファイルの読み取り、および moved を使用した安全なリファクタリングです。