Terraform

Terraform for_each メタ引数を正しく使い分ける: マップ/セット単位の生成

2026-04-19
NicheeLab編集部

Terraform の for_each は、コレクション(主にマップ、セット)を元に複数インスタンスを安定的に生成するためのメタ引数です。試験では count との違い、各キーの意味、インスタンスアドレスの安定性が出題されやすいポイントです。

本稿では、マップ/セット単位の生成という観点で、実務での使い所と落とし穴、モジュールでの適用、試験対策の観点までをまとめます。公式ドキュメントの動作仕様に基づき、バージョンに依存しにくい安定概念のみを扱います。

for_each の基本と試験観点

for_each は、マップまたはセット(セットは文字列の集合が基本)を受け取り、要素ごとにリソース(またはモジュール)インスタンスを作ります。各インスタンスはキーでアドレス指定されるため、順序の入れ替えに強く、意図しない差分を抑えられます。

リソースやモジュールブロック内では each.key と each.value が参照できます。マップでは key がマップのキー、value がその値(値はオブジェクトでも可)。セットでは key と value は同一の文字列です。Associate では count との違い(アドレス、安定性)、マップ/セットの選び方、型変換(toset, tomap, keys など)が問われがちです。

  • for_each はマップ(推奨)またはセット(文字列)を受け付ける
  • 各インスタンスは resource.type.name["key"] のようなキーアドレスで管理
  • each.key / each.value を使用できる(モジュール呼び出しブロックでも可)
  • セットは順序を持たないが、キー=値の文字列なのでアドレスは安定しやすい
  • count はインデックス管理で順序変更に弱い。命名できるなら for_each が有利

最小例: マップから 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"]

マップでの for_each: 名前付きインスタンスの安定生成

マップはキーを持つため、名前付きのインスタンスを安定的に作る用途に最適です。値にオブジェクトを置けば、各インスタンス固有の設定を詰め込めます。削除・追加もキー単位で検出され、アドレスが揺れません。

実務では、ID や論理名をキーにした map(object(...)) を for_each に与えるのが定番です。キーの変更は旧キーの破棄と新キーの作成として扱われるため、意図せずロールアウトが走らないよう、キーを安易に変えない設計が重要です。

  • キーはインスタンスアドレスを決定する最重要要素
  • 値はオブジェクトでもよく、各種属性を each.value で参照
  • キー変更はリネームではなく破棄+新規作成として扱われる

マップのキーとリソースアドレスの対応

var.instances (map)web = { cpu=2, mem=4 } / api = { cpu=1, mem=2 }null_resource.node["web"]null_resource.node["api"]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

セットでの for_each: 存在フラグ型の生成

セットは順序を持たない文字列集合です。単に“この名前が存在するか”を表す用途で有効です。各インスタンスのアドレスは要素文字列に基づくため、集合のメンバーが変わらない限り安定します。重複は自動で除去されます。

セットは値情報を持たないため、属性を持たせたい場合はマップを使うか、別のデータソースで付加情報を解決してください。リストを toset で変換する場合、順序は失われる点に注意します。

  • each.key と each.value は等しく、どちらも要素文字列
  • 重複は吸収、順序は未定義
  • 詳細属性が必要なら map(object) に切り替える

セット(文字列)の例

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"].id

count との比較と選択基準

count は単純で強力ですが、インデックスに依存するため、並べ替えや途中挿入・削除でインスタンスの入れ替わり(チャン)が起きやすいです。名前で管理できる場合は for_each を選ぶと安定します。

Associate 試験では、どちらを使うべきか、なぜ差分が安定するのか、という観点がよく問われます。

  • 命名できるなら for_each、数だけ増やすなら count
  • 既存 count からの移行はキー設計と moved/state mv の活用が要
  • セットで保持できない属性は map(object) で表現
項目countfor_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 と設計パターン

モジュール呼び出しにも for_each を使えます。呼び出し側のモジュールブロック内で each.key/each.value を参照し、サブモジュールに入力を渡します。サブモジュール内では each は使えないため、通常どおり変数で受け取ります。

map(object) でアプリ群を管理し、キーを論理名、値を構成オブジェクトにするのが実務でも安定です。出力は module.block["key"].output のように参照します。

  • 呼び出し側で each を使う。サブモジュール内では変数参照
  • キーを論理名に固定し、将来も不変にする設計が重要
  • 出力参照は module.x["key"].out 形式

モジュール呼び出しでの 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 が有用です。

  • キーは不変に、可変情報は each.value 側へ
  • セットは重複除去・順序なし。順序依存の処理を前提にしない
  • リファクタ時は state mv / moved で安全に移行
  • フィルタは内包表記で map を作る段階で実施(if 句)

フィルタとキー設計の例

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.app

問題で確認

Associate

問題 1

複数のセキュリティルール(id, from_port, to_port を持つオブジェクトの集合)を安定的に管理したい。将来ルールの並び順が変わっても不要な破棄・再作成を避けたい場合、最も適切なアプローチはどれか。

  1. ルール id をキーにした map(object(...)) を作り、for_each を使ってインスタンスを生成する
  2. count = length(var.rules) を使い、count.index で各ルールを参照する
  3. rules を set(object(...)) に変換して for_each に渡す
  4. depends_on を追加して順序の影響を防ぐ

正解: 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 の内容を引数として渡します。

この記事で学んだ内容を問題で確認しましょう

16,000問以上の問題で実力チェック

無料で問題を解いてみる
この記事の著者

NicheeLab編集部

データエンジニアリング・クラウド資格の専門家。Databricks・Snowflake等の認定資格を保有し、実務経験に基づいた問題作成・解説を行っています。NicheeLab運営。


関連記事
Terraform

Terraform HCL 構文の基礎:Block / Attribute / Expression を正しく使い分ける

Terraform Associate で頻出の HCL 構文を、ブロック・属性・式の3視点で整理。実務で迷いがちな書き...

Terraform

Terraform Authoring & Ops Pro: 上位資格の範囲と対策

上位レベルを想定したTerraformの設計・運用ドメインを整理し、実務で通用する対策を提示。モジュール設計、ステート運...

Terraform

Terraform Providers の基本: プラグイン型アーキテクチャを正しく使いこなす

Associate レベルで押さえるべき Provider の基礎、インストール、バージョニング、認証、エイリアス運用を...

Terraform

Terraform Resourceブロック徹底ガイド: 最小単位のリソース定義

Associateレベルで押さえるべきResourceブロックの構造、依存関係、メタ引数、ライフサイクル制御を実務目線で...

Terraform

Terraform Data Source徹底理解:既存リソースの参照で壊さず足す

Terraform Associate向けに、Data Sourceを用いた既存リソース参照の基本、選択基準、評価順序、...

Terraformの記事一覧 (101件)
© 2026 NicheeLab All rights reserved.