入力変数の型を曖昧にすると、モジュール利用時の誤入力や予期せぬ差分が起きやすくなります。Terraform は HCL2 の型システムを持ち、variable ブロックの type で型制約を宣言できます。この記事では、特に利用頻度の高い string、list、map、object の4型に絞り、型制約と一貫性の観点から設計・実装の勘所を整理します。
試験(Terraform Associate)では、型ごとの性質や挙動、validation、for_each など周辺機能との組み合わせが頻出です。実務の設計原則と一緒に押さえると、得点源にできます。
Terraform の入力変数は variable ブロックで宣言し、type 引数で型制約を付けると、plan/apply 時にユーザー入力や tfvars の値が検証されます。default を付けると、その値に合わせて型が暗黙決定されますが、モジュール境界の契約を明確にするため、明示的な type 指定を推奨します。
string は単一値、list は順序を持つ同一型の並び、map はキーと値の辞書(順序なし)、object は属性スキーマを固定した構造体的な型です。object はモジュール API の契約固定に向き、map は任意キーの辞書を受けたい場合に便利です。list は順序重要な入力(例: CIDR の優先順)で使います。
any は柔軟ですが、検証が弱くなり不正入力を見逃しやすいので、移行の過渡期を除き避けるのが安全です。
| 型 | 典型的リテラル例 | 主な用途 | 注意点 |
|---|---|---|---|
| string | "prod" | 環境名、リージョン、識別子 | 空文字や許容文字の検証を validation で併用 |
| list(string) | ["web", "api"] | 順序あるエントリ列(セキュリティルール等) | for_each には直接使えない(map/set へ変換が必要) |
| map(string) | { env = "prod", team = "platform" } | タグ/ラベル、任意キー辞書 | 順序なし。lookup/merge を多用する設計が定番 |
| object({ name=string, size=string }) | { name = "app", size = "t3.small" } | モジュール入出力の契約固定 | 全属性が必須(欠落はエラー)。拡張時は後方互換に配慮 |
| list(object({...})) | [{ name="a" }, { name="b" }] | 同型オブジェクトの配列(複数リソース駆動) | for_each 用に key 化(name など)して map 化が実務で安定 |
型制約と検証の評価フロー(入力 → 型チェック → モジュール)
主要4型の宣言例(型制約つき)
variable "env" {
type = string
description = "デプロイ環境(dev/stg/prod)"
validation {
condition = contains(["dev", "stg", "prod"], var.env)
error_message = "env は dev/stg/prod のいずれかにしてください。"
}
}
variable "subnets" {
type = list(string)
description = "サブネットIDの順序付きリスト"
}
variable "tags" {
type = map(string)
description = "共通タグ"
default = {}
}
variable "server" {
type = object({
name = string
size = string
tags = map(string)
})
description = "単一サーバの仕様"
}string は最も単純ですが、命名規則や許容値の制御を甘くすると、クラウド側の命名制約に引っかかったり、環境の取り違えが起こります。validation で値域や正規表現を併用するのが実務では標準です。
マルチライン文字列が必要ならヒアドキュメント(<<-EOT ... EOT)を使えますが、入力変数で長文を受けるよりは、テンプレートファイルの利用や locals での構築がメンテしやすいです。
string 変数の安全な定義例(正規表現+値域)
variable "env" {
type = string
description = "環境名 (dev|stg|prod)"
validation {
condition = can(regex("^(dev|stg|prod)
NicheeLab を読み込み中…
quot;, var.env)) error_message = "env は dev, stg, prod のいずれかにしてください。" } } variable "service_name" { type = string description = "サービス名(小文字英数字とハイフン、1..30 文字)" validation { condition = can(regex("^[a-z0-9-]{1,30}
quot;, var.service_name)) error_message = "service_name は小文字英数字とハイフンのみ(最大30文字)。" } }
list は順序が意味を持つ並びです。型制約は list(T) の T が全要素に適用されます。list(string) は ID リストなどに適し、list(object) は同一スキーマのエントリ集合を表現できます。
resource の for_each は map か set に限定されるため、list(object) を直接 for_each には使えません。実務では name などの一意キーを用いて、内包表記で map に変換してから for_each するのが安定です。順序が重要で count を使うケースでは、インデックスに依存する破壊的な並べ替えを避ける設計も重要です。
list(object) を map に変換して for_each する例
variable "servers" {
description = "配備するサーバ群"
type = list(object({
name = string
size = string
tags = map(string)
}))
}
locals {
servers_by_name = { for s in var.servers : s.name => s }
}
# 例: プロバイダ非依存の擬似リソース
resource "example_server" "this" {
for_each = local.servers_by_name
name = each.value.name
size = each.value.size
tags = each.value.tags
}
map(string) はタグやラベルの代表格です。順序は保証されないため、plan の表示順は変動し得ます。一貫性を保つには、map の初期値を {} にしておき、merge で共通タグ・上書きタグを合成するパターンが扱いやすいです。
存在しないキー参照には lookup(map, key, default) を使うと安全です。キーの形式(許容文字)を validation で縛ると、クラウド側の制約に先回りできます。
map(string) の合成と安全な参照
variable "tags" {
type = map(string)
description = "共通タグ(呼び出し元で上書き可能)"
default = {}
validation {
condition = alltrue([for k in keys(var.tags) : can(regex("^[-a-z0-9]+
NicheeLab を読み込み中…
quot;, k))])
error_message = "タグのキーは小文字英数字とハイフンのみ許可。"
}
}
locals {
base_tags = { "managed-by" = "terraform" }
final_tags = merge(local.base_tags, var.tags)
}
output "team_tag" {
value = lookup(local.final_tags, "team", "unknown")
}
object は属性名と型を固定できるため、モジュール境界の契約として強力です。全属性の存在が要求され、欠落すると型不一致でエラーになります。将来的に属性を追加する際は、後方互換(既存利用者の tfvars がそのまま通ること)に配慮して、別の変数として分離する、または map(string) で拡張用フックを設ける戦略が有効です。
object 全体の default に null を使うことはできますが、受け側では null を考慮して coalesce や条件分岐でデフォルト補完する必要があります。個々の属性を null 許容にするには、設計段階で null を許可しない方針に寄せ、明確なデフォルトを用意する方が一貫性を保ちやすいです。
object 変数の設計と validation 例
variable "server" {
description = "サーバ仕様(厳密スキーマ)"
type = object({
name = string
size = string
tags = map(string)
})
validation {
condition = can(regex("^[a-z0-9-]+
NicheeLab を読み込み中…
quot;, var.server.name)) && length(var.server.name) <= 30
error_message = "server.name は小文字英数字とハイフン、30文字以内。"
}
}
locals {
server_tags = merge({ "managed-by" = "terraform" }, var.server.tags)
}
# 利用例(出力)
output "server_label" {
value = "${var.server.name}:${var.server.size}"
}
実務では、変数の型を明示し、validation で値域を縛り、list は map 化して for_each、map は merge/lookup で合成・安全参照、object は契約固定に使う、という定石を徹底すると混乱が減ります。型変換関数(toset/tomap/tolist)で入力を正規化するのも有効です。
試験では、list と map の違い(順序、for_each 可否)、object の厳格性、any の注意、validation の役割が問われやすいです。tfvars と -var/-var-file による入力経路、default の影響、plan 時に型エラーが検出される点も押さえておきましょう。
入力経路と正規化のスニペット(tfvars と型変換)
# terraform.tfvars 例
env = "stg"
subnets = ["subnet-aaa", "subnet-bbb"]
# CLI からの指定例
# terraform plan -var-file="env/stg.tfvars"
# 型正規化の一例(list → map)
locals {
items = ["a", "b", "c"]
items_map = { for v in local.items : v => upper(v) }
}
Associate
問題 1
モジュールは次の入力変数を受け取ります。tags の型制約に適合する tfvars の定義として正しいものはどれですか? variable "tags" { type = map(string) description = "共通タグ" }
正解: A
map(string) はキーが string、値も string の辞書です。A は key:string → value:string の形で適合。B は list、C は値が list、D は単一の string で不適合です。
map と object の使い分けは?
任意キーの辞書を受けたい(タグなど)場合は map(string)。属性名と型を固定して契約を厳密にしたい場合は object。将来の拡張を見込むなら、必須は object、任意拡張は map で受けるハイブリッドも有効です。
list を for_each に使えないのはなぜ?どう回避する?
for_each は map か set を要求します。list は順序と重複を許すため、安定キーを持ちません。回避策は内包表記で map 化することです({ for v in var.list : key(v) => v })。
型不一致エラーが出たときの基本的な対処は?
variable の type と tfvars/デフォルトの実値を見比べ、toset/tomap/tolist などで正規化します。map(string) に list を入れていないか、object の必須属性が欠落していないかを確認し、validation の条件も再点検してください。
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を用いた既存リソース参照の基本、選択基準、評価順序、...