Terraform の for_each は、コレクション(主にマップ、セット)を元に複数インスタンスを安定的に生成するためのメタ引数です。試験では count との違い、各キーの意味、インスタンスアドレスの安定性が出題されやすいポイントです。
本稿では、マップ/セット単位の生成という観点で、実務での使い所と落とし穴、モジュールでの適用、試験対策の観点までをまとめます。公式ドキュメントの動作仕様に基づき、バージョンに依存しにくい安定概念のみを扱います。
for_each は、マップまたはセット(セットは文字列の集合が基本)を受け取り、要素ごとにリソース(またはモジュール)インスタンスを作ります。各インスタンスはキーでアドレス指定されるため、順序の入れ替えに強く、意図しない差分を抑えられます。
リソースやモジュールブロック内では each.key と each.value が参照できます。マップでは key がマップのキー、value がその値(値はオブジェクトでも可)。セットでは key と value は同一の文字列です。Associate では count との違い(アドレス、安定性)、マップ/セットの選び方、型変換(toset, tomap, keys など)が問われがちです。
最小例: マップから null_resource を生成
variable "services" {
type = map(object({
size = number
}))
default = {
web = { size = 2 }
api = { size = 1 }
}
}
resource "null_resource" "svc" {
for_each = var.services
triggers = {
name = each.key
size = tostring(each.value.size)
}
}
# アドレス例:
# null_resource.svc["web"], null_resource.svc["api"]マップはキーを持つため、名前付きのインスタンスを安定的に作る用途に最適です。値にオブジェクトを置けば、各インスタンス固有の設定を詰め込めます。削除・追加もキー単位で検出され、アドレスが揺れません。
実務では、ID や論理名をキーにした map(object(...)) を for_each に与えるのが定番です。キーの変更は旧キーの破棄と新キーの作成として扱われるため、意図せずロールアウトが走らないよう、キーを安易に変えない設計が重要です。
マップのキーとリソースアドレスの対応
map(object) を使った具体例
variable "instances" {
type = map(object({
cpu = number
mem = number
tags = map(string)
}))
}
resource "null_resource" "node" {
for_each = var.instances
triggers = {
name = each.key
cpu = tostring(each.value.cpu)
mem = tostring(each.value.mem)
}
}
# 参照例: null_resource.node["web"].idセットは順序を持たない文字列集合です。単に“この名前が存在するか”を表す用途で有効です。各インスタンスのアドレスは要素文字列に基づくため、集合のメンバーが変わらない限り安定します。重複は自動で除去されます。
セットは値情報を持たないため、属性を持たせたい場合はマップを使うか、別のデータソースで付加情報を解決してください。リストを toset で変換する場合、順序は失われる点に注意します。
セット(文字列)の例
locals {
names = toset(["web", "api", "web"]) # "web" の重複は自動で除去
}
resource "null_resource" "role" {
for_each = local.names
triggers = {
name = each.key # == each.value
}
}
# 参照例: null_resource.role["api"].idcount は単純で強力ですが、インデックスに依存するため、並べ替えや途中挿入・削除でインスタンスの入れ替わり(チャン)が起きやすいです。名前で管理できる場合は for_each を選ぶと安定します。
Associate 試験では、どちらを使うべきか、なぜ差分が安定するのか、という観点がよく問われます。
| 項目 | count | for_each(マップ) | for_each(セット) |
|---|---|---|---|
| インスタンスアドレス | resource.name[0] などの数値インデックス | resource.name["key"] | resource.name["value"] |
| 差分の安定性 | 順序変化に弱い(入れ替わりが起こりやすい) | キーが不変なら安定 | 集合メンバーが不変なら安定 |
| 値の表現力 | 個別属性の割当はやや冗長 | value にオブジェクトを持てる | 値を持てない(キー=値の文字列) |
| 典型用途 | N 個の同一構成 | 名前付き・個別設定の複数構成 | 存在フラグ型の有無管理 |
| キー変更時の挙動 | 該当なし(インデックス) | 旧キー破棄+新キー作成 | 旧要素削除+新要素追加 |
count から for_each への安全な移行(例)
# 旧: count による作成(順序に弱い)
# resource "null_resource" "srv" {
# count = length(var.names)
# triggers = { name = var.names[count.index] }
# }
# 新: マップ化して for_each(安定)
locals {
names_map = { for n in var.names : n => { name = n } }
}
resource "null_resource" "srv" {
for_each = local.names_map
triggers = { name = each.key }
}
# 既存リソースとキーの対応が変わる場合は、
# terraform state mv または moved ブロックでマッピングを明示して無停止移行を検討
モジュール呼び出しにも for_each を使えます。呼び出し側のモジュールブロック内で each.key/each.value を参照し、サブモジュールに入力を渡します。サブモジュール内では each は使えないため、通常どおり変数で受け取ります。
map(object) でアプリ群を管理し、キーを論理名、値を構成オブジェクトにするのが実務でも安定です。出力は module.block["key"].output のように参照します。
モジュール呼び出しでの for_each
variable "apps" {
type = map(object({
image = string
replicas = number
}))
}
module "app" {
source = "./modules/app"
for_each = var.apps
name = each.key
image = each.value.image
replicas = each.value.replicas
}
# 参照例: module.app["frontend"].endpoint
# modules/app/variables.tf
# variable "name" { type = string }
# variable "image" { type = string }
# variable "replicas" { type = number }キー変更はリソースの入れ替えを招きます。キーは不変識別子(論理名や ID)を選定し、表示名のような可変情報は値オブジェクトに持たせます。リストからの toset 変換は順序を失うため、順序前提のロジックを混ぜないようにします。
既存のアドレスと新しいキーの対応付けが必要なリファクタでは、terraform state mv で手動移行するか、構成に moved ブロックを置いて計画外の破壊を防ぎます。差分の把握には terraform plan、アドレス確認には terraform state list が有用です。
フィルタとキー設計の例
locals {
# バージョンが安定版のアプリだけを対象にし、論理名をキー化
filtered = {
for name, cfg in var.apps :
name => cfg
if startswith(cfg.version, "stable-")
}
}
module "app" {
source = "./modules/app"
for_each = local.filtered
name = each.key
version = each.value.version
}
# デバッグ:
# terraform plan
# terraform state list | grep module.appAssociate
問題 1
複数のセキュリティルール(id, from_port, to_port を持つオブジェクトの集合)を安定的に管理したい。将来ルールの並び順が変わっても不要な破棄・再作成を避けたい場合、最も適切なアプローチはどれか。
正解: A
for_each に map(object) を渡し、id をキーとすることで各インスタンスのアドレスをキーに固定でき、順序変更による入れ替わりを防げる。count はインデックスに依存し順序変更に弱い。set(object) はサポート対象外(キーが一意に決まらない)。depends_on は依存順序の宣言であり、アドレス安定性の問題は解決しない。
リソースやモジュールで count と for_each を同時に指定できますか?
できません。どちらか一方のみ指定可能です。命名できる場合や個別属性がある場合は for_each、同一構成を N 個だけ作るなら count が簡潔です。
セット(toset で変換)を for_each に使うと順序はどうなりますか?
セットは順序を持ちません。for_each のキーは要素文字列(= 値)なので、集合のメンバーが変わらない限りアドレスは安定しますが、並べ替えの概念はありません。重複は自動的に除去されます。
モジュールで for_each を使うとき、サブモジュール内でも each.key / each.value は使えますか?
いいえ。each は呼び出し側のモジュールブロック内でのみ参照可能です。サブモジュール内では通常どおり変数で受け取り、必要なら呼び出し側で each.value の内容を引数として渡します。
NicheeLab編集部
データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。
Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける
Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...
Terraform Authoring & Ops Pro: 上位資格の範囲と対策
上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...
Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす
Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...
Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義
Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...
Terraform Data Source徹底理解:既存リソースの参照で壊さず足す
Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...