Terraform

Terraformのdynamicブロック徹底攻略: 可変な設定ブロックを安全に生成する

2026-04-19
NicheeLab編集部

dynamicブロックは、変数の内容に応じてリソース内のネストブロックを増減させるための言語機能です。

count/for_each(リソース単位)とは目的が異なり、nested blocks(例: ingress, lifecycle_rule など)を動的に生成するのが主用途です。

dynamicブロックの基本と構文

dynamicは、リソースやデータソースの内部で、可変個数のネストブロックを生成するための構文です。記述は dynamic "<ブロック名>" { for_each = ... iterator = ... content { ... } } の形を取り、for_eachで反復対象を与え、contentの中で各要素を展開します。

iteratorは省略可能で、省略時はeachというデフォルト名が使われます(each.valueで要素を参照)。mapをfor_eachに渡すとeach.key/each.valueが使え、list/setでは主にeach.valueを使います。

注意点として、dynamicは『プロバイダが定義するネストブロック』を生成するための仕組みであり、meta-argument(count, for_each, depends_on, provider, lifecycle など)の生成には使えません。

  • 使いどころ: ネストブロックが0個以上に増減するケース(例: 複数のingress/egress, 複数ルールのlifecycle_rule)
  • 基本参照: iterator省略時はeach.value、明示時は<iterator>.value
  • for_eachの値はPlan時に決定可能である必要がある(unknownだとエラー)
  • 属性(例: tags = { ... })はdynamicではなく式で直接組み立てる

セキュリティグループのingressをdynamicで生成

variable "ingress_rules" {
  description = "許可するingressルールの一覧"
  type = list(object({
    from_port         = number
    to_port           = number
    protocol          = string
    cidr_blocks       = list(string)
    description       = optional(string)
    ipv6_cidr_blocks  = optional(list(string), [])
  }))
}

resource "aws_security_group" "web" {
  name        = "web-sg"
  description = "Web tier security group"

  # 可変個数のingressブロックを生成
  dynamic "ingress" {
    for_each = var.ingress_rules
    iterator = rule
    content {
      from_port        = rule.value.from_port
      to_port          = rule.value.to_port
      protocol         = rule.value.protocol
      cidr_blocks      = rule.value.cidr_blocks
      ipv6_cidr_blocks = try(rule.value.ipv6_cidr_blocks, null)
      description      = try(rule.value.description, null)
    }
  }

  # 既定のegress(固定1ブロックのためdynamicは不要)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

パターン集: 条件付き・フィルタ・入れ子

dynamicは単純な繰り返しだけでなく、条件付きの有無や、enabledフラグによるフィルタ、入れ子ブロックの展開にも使えます。0/1個のブロックを条件で切り替えたい場合は、for_eachに[]または[要素1つ]を渡すパターンが実用的です。

また、入力のリストから enabled = true の要素のみを抽出してからfor_eachに渡すと、条件ロジックがcontent内に散らからず読みやすくなります。入れ子構造は、dynamicの中でさらに固定のサブブロックを書く、または必要に応じてdynamicを入れ子にします(過度な入れ子は可読性低下に注意)。

  • 0/1個の有無: for_each = condition ? [obj] : []
  • フィルタ: for v in var.items : v if v.enabled
  • iterator名を付けて可読性を上げる(例: iterator = log, rule)
  • 入れ子は2段程度までに抑えるのが目安

dynamicブロック評価の流れ

Variables/LocalsFilter/Map opsfor_each (known?)Empty => 0 blocksdynamic iterationcontent {...}展開Provider schema evalPlanに反映(差分可視化)Variables/Locals → for_each (known?) → dynamic iteration → Provider schema eval

0/1個のオプションブロックを動的生成

variable "access_logging" {
  type = object({
    bucket = string
    prefix = optional(string)
  })
  default = null
}

resource "aws_s3_bucket" "b" {
  bucket = "example-bucket-1234"

  dynamic "logging" {
    for_each = var.access_logging == null ? [] : [var.access_logging]
    iterator = log
    content {
      target_bucket = log.value.bucket
      target_prefix = try(log.value.prefix, null)
    }
  }
}

dynamicと他手段の比較と選び方

可変構成を作る手段は複数あります。dynamicはネストブロックを増減したいときの道具です。リソース自体の数を増減したいならresourceレベルのfor_each/countを使います。属性値の計算は内包表記やmergeで十分で、dynamicを使う必要はありません。

試験でも実務でも“何を可変にしたいのか(リソース数か、ネストブロック数か、単なる属性値か)”を起点に選ぶと誤りにくいです。

  • ネストブロックの反復 → dynamic
  • リソースインスタンスの反復 → resourceのfor_each/count
  • 単一属性の構築(例: tags, labels) → 式(map内包/merge)
  • 文字列テンプレートでHCLを生成するのは非推奨(型検証が効かない)
手段主な使いどころ長所注意点
dynamicブロックネストブロックの可変生成(ingress, lifecycle_rule等)スキーマ検証が効く/DRY化for_eachはPlan時に既知で必要、過度な入れ子は可読性低下
resource for_eachリソース個数の可変化(同型リソースの複数作成)個別アドレス指定が容易(each.key)キーの安定性が必須(setではなくmap推奨)
count単純なN個の反復(indexでアクセス)シンプル・軽量要素の追加削除でインデックスがズレやすい
内包表記/merge属性値(map/list)の生成可読性・型整合が取りやすいネストブロックは作れない(dynamicと役割が違う)

やってはいけない例と代替

# NG: 属性(map)をdynamicで作ろうとするのは誤り
# dynamic "tags" { ... } は不可。tagsはブロックではなく属性。

# OK: 属性は式で組み立てる
locals {
  base_tags = {
    app = "web"
  }
}

resource "aws_instance" "example" {
  ami           = "ami-12345678"
  instance_type = "t3.micro"

  tags = merge(local.base_tags, {
    env = var.env
  })
}

# リソース数を増やしたいならresourceのfor_each
resource "aws_iam_user" "u" {
  for_each = var.user_names  # map推奨(キー安定)
  name     = each.key
}

よくある落とし穴とベストプラクティス

for_eachに渡す値がPlan時に未確定(unknown)だと、dynamicは展開できずエラーになります。データソースの結果に依存して数が決まる場合は、入力を見直すか、事前に値を確定させる設計に改めます。

list/setでfor_eachするとキーが不安定になりやすいため、map化して安定キーを使うのが安全です。また、optionalな属性はtry(..., null)で包み、プロバイダがnullを“未設定”として扱う前提に合わせると差分が出にくくなります。

  • Plan時既知の原則: for_eachはplan時に決定可能な値にする
  • 安定キー: list/setではなくmap(キー=論理名)に変換して反復
  • optional属性: try(..., null) で未設定扱いに寄せる
  • meta-argumentはdynamicで生成不可(count/for_each/lifecycle等)
  • 過度な入れ子はlocalsで前処理してから1段で展開

型のガードとフィルタの定石

variable "rules" {
  type = list(object({
    name     = string
    enabled  = optional(bool, true)
    priority = number
  }))
}

locals {
  # enabledのみ抽出し、キー=論理名でmap化して安定キーを確保
  active_rules = { for r in var.rules : r.name => r if try(r.enabled, true) }
}

resource "example_resource" "main" {
  # ネストブロック rule を生成する(仮の例)
  dynamic "rule" {
    for_each = local.active_rules
    iterator = r
    content {
      name     = r.value.name
      priority = r.value.priority
    }
  }
}

試験対策ポイント(Associate / Pro)

試験では、dynamicの役割(ネストブロックの生成)と、resourceのfor_each/count(リソース数の可変化)を取り違えないことが頻出ポイントです。iteratorやeach.valueの参照、条件付きで0/1個だけ生成するパターンも押さえておきましょう。

また、dynamicでmeta-argumentを作れない点、属性は式で構築する点、for_eachの値はPlan時既知が要件である点は定番のひっかけになります。

  • dynamicはネストブロック専用。tags等の属性は対象外
  • iterator省略時はeach、明示時は<iterator>.valueで参照
  • 0/1個の生成は [] / [obj] のトグルが定石
  • for_eachはPlan時既知(dataで未確定だとNG)
  • meta-argument(lifecycle, count, for_each等)はdynamic不可

Iterator名と参照の確認

dynamic "ingress" {
  for_each = var.ingress_rules
  iterator = blk
  content {
    from_port = blk.value.from_port
    to_port   = blk.value.to_port
    protocol  = blk.value.protocol
  }
}

実務ケース: バケットのライフサイクルルールを可変に

ストレージバケットのライフサイクルルールは、条件やアクションの組み合わせで複数定義することが多く、dynamicと相性が良い題材です。enabledフラグでオンオフし、optional属性はtry(..., null)で包むと差分が安定します。

以下はGoogle Cloud Storageの例ですが、考え方は他プロバイダのlifecycle_rule相当でも同様です。

  • enabledフラグでルール追加・削除を安全に制御
  • 条件集合(matches_prefix等)は空ならnullで未設定に
  • ルール名など論理キーがあればmap化して安定反復

GCS バケットの lifecycle_rule を dynamic で生成

variable "lifecycle_rules" {
  type = list(object({
    enabled  = optional(bool, true)
    action   = object({
      type          = string              # e.g. "Delete" | "SetStorageClass"
      storage_class = optional(string)
    })
    condition = object({
      age                   = optional(number)
      matches_prefix        = optional(list(string))
      matches_suffix        = optional(list(string))
      with_state            = optional(string)
      matches_storage_class = optional(list(string))
    })
  }))
}

locals {
  active_lc = [for r in var.lifecycle_rules : r if try(r.enabled, true)]
}

resource "google_storage_bucket" "b" {
  name     = "example-bucket-xyz"
  location = "US"

  dynamic "lifecycle_rule" {
    for_each = local.active_lc
    iterator = lc
    content {
      action {
        type          = lc.value.action.type
        storage_class = try(lc.value.action.storage_class, null)
      }
      condition {
        age                   = try(lc.value.condition.age, null)
        matches_prefix        = try(lc.value.condition.matches_prefix, null)
        matches_suffix        = try(lc.value.condition.matches_suffix, null)
        with_state            = try(lc.value.condition.with_state, null)
        matches_storage_class = try(lc.value.condition.matches_storage_class, null)
      }
    }
  }
}

問題で確認

Associate / Pro

問題 1

Terraformで、変数の内容に応じてネストブロック(例: ingress)を0個以上生成したい。最も適切な実装はどれか。

  1. dynamic "ingress" に for_each を与え、content で各要素を展開する
  2. resourceにcountを設定し、count.indexを使ってingressブロックを増減させる
  3. templatefileでHCL文字列を生成してlocalsに埋め込む
  4. dynamicでlifecycleメタ引数ブロックを生成して制御する

正解: A

ネストブロックの可変生成にはdynamicが適切。countはリソース数の反復であり、ネストブロックは増減できない。templatefileでHCLを生成するのは型検証が効かず非推奨。lifecycleなどのメタ引数はdynamicで生成できない。

よくある質問

dynamicのfor_eachにlist/set/mapのどれを渡すべき?

map推奨です。キーが安定して計画・差分が安定します。list/setも使えますが、順序や暗黙キーが不安定になりやすく、後続の変更で不要な差分が発生しがちです。必要なら { for v in var.list : v.name => v } のようにmap化してから反復します。

for_eachにdataソースの結果(Plan時に未確定)を渡すとどうなる?

Plan時にunknownのままではdynamicを展開できずエラーになります。for_eachの値はPlan時既知である必要があります。設計としては、入力変数で制御する、もしくは事前プロセスで値を確定させてからTerraformに渡すのが安全です。

meta-argument(lifecycle, count, for_each等)をdynamicで作れますか?

できません。dynamicはプロバイダが定義する通常のネストブロック用です。meta-argumentはTerraform言語レベルの仕組みなので、通常の静的なブロック/引数として記述してください。

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

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.