はじめに
Terraformのコードを読んでいると、リソースの参照で[0]や[*]といった[](ブラケット)が登場します。
# パターン1
aws_nat_gateway.main[0].id
# パターン2
values(aws_subnet.private)[*].id
この2つは見た目が似ていますが、意味がまったく異なります。この記事では、countとfor_eachというリソースの作成方法の違いから、それぞれの[]の意味を初心者向けに解説します。
対象読者
- Terraformを学び始めたばかりの方
-
[0]や[*]の意味がよくわからない方 -
countとfor_eachの違いを整理したい方
前提知識:countとfor_each
Terraformでは、リソースを条件付きで作成したり、複数作成したりするためにcountとfor_eachという2つの仕組みがあります。この作成方法の違いが、[]の使い方の違いに直結します。
パターン1:[0] — countで作ったリソースへのアクセス
countとは
countを使うと、リソースを指定した数だけ作成できます。
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? 1 : 0 # trueなら1つ作成、falseなら作成しない
# ...
}
countを使うとリソースは「配列(リスト)」になる
countを使ったリソースは、配列(リスト) として管理されます。配列の要素には インデックス番号(0から始まる) でアクセスします。
aws_nat_gateway.main
├── [0] ← 最初の要素
├── [1] ← 2番目の要素(count=2以上の場合)
└── ...
[0]の意味
[0]は「配列の最初の要素」を指します。
# count = 1 の場合(リソースが1つ作成される)
aws_nat_gateway.main[0].id # → 最初(唯一)のNAT GatewayのID ✅
aws_nat_gateway.main[1].id # → エラー(2番目は存在しない)❌
# count = 0 の場合(リソースが作成されない)
aws_nat_gateway.main[0].id # → エラー(リソース自体が存在しない)❌
よくある使い方
countは「作るか作らないか(0 or 1)」の条件分岐でよく使われます。
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? 1 : 0
# ...
}
# 参照するときは [0] で最初の要素にアクセス
resource "aws_route" "private" {
nat_gateway_id = aws_nat_gateway.main[0].id
}
パターン2:[*] — for_eachで作ったリソースへのアクセス
for_eachとは
for_eachを使うと、mapやsetの各要素に対してリソースを作成できます。
variable "private_subnet_cidrs" {
default = {
"a" = "10.0.1.0/24"
"c" = "10.0.2.0/24"
"d" = "10.0.3.0/24"
}
}
resource "aws_subnet" "private" {
for_each = var.private_subnet_cidrs # mapの各要素に対してリソースを作成
cidr_block = each.value
# ...
}
for_eachを使うとリソースは「map」になる
for_eachを使ったリソースは、map(キーと値のペア) として管理されます。要素にはキー(文字列) でアクセスします。
aws_subnet.private
├── ["a"] ← キー "a" の要素
├── ["c"] ← キー "c" の要素
└── ["d"] ← キー "d" の要素
個別にアクセスする場合
aws_subnet.private["a"].id # → AZ aのサブネットID ✅
aws_subnet.private["c"].id # → AZ cのサブネットID ✅
aws_subnet.private[0].id # → エラー(mapなのでインデックスは使えない)❌
全要素のIDをまとめて取得したい場合
ここで登場するのがvalues()と[*](splat演算子)です。
ステップ1:values()でmapからリストに変換
values()は、mapの値だけを取り出してリストにする関数です。
values(aws_subnet.private)
# → [
# aws_subnet.private["a"], ← リソースオブジェクト
# aws_subnet.private["c"],
# aws_subnet.private["d"]
# ]
ステップ2:[*](splat演算子)で全要素の属性を取得
[*]はsplat演算子と呼ばれ、リスト内のすべての要素に対して指定した属性を取得します。
values(aws_subnet.private)[*].id
# → [
# "subnet-aaaaa", ← aws_subnet.private["a"].id
# "subnet-ccccc", ← aws_subnet.private["c"].id
# "subnet-ddddd" ← aws_subnet.private["d"].id
# ]
処理の流れを整理
aws_subnet.private ← map(キーと値のペア)
↓ values() で値だけ取り出す
[リソースA, リソースC, リソースD] ← リスト(リソースオブジェクト)
↓ [*].id で全要素の.idを取得
["subnet-aaaaa", "subnet-ccccc", ...] ← リスト(IDの文字列)
比較まとめ
| 項目 | aws_nat_gateway.main[0] |
values(aws_subnet.private)[*] |
|---|---|---|
| 作成方法 | count |
for_each |
| データ構造 | 配列(リスト) | map |
| アクセス方法 | インデックス番号([0], [1]...) |
キー(["a"], ["c"]...) |
[0]の意味 |
配列の最初の要素 | ー(使わない) |
[*]の意味 |
ー(使わない) | リスト内のすべての要素に適用 |
| 主な用途 | 単一リソースへのアクセス | 複数リソースの属性をまとめて取得 |
実際のコードでの使用例
# ─── count を使った場合(単一リソース)───
resource "aws_nat_gateway" "main" {
count = var.enable_nat_gateway ? 1 : 0
# ...
}
# [0] で1つのNAT GatewayのIDを取得
nat_gateway_id = aws_nat_gateway.main[0].id
# ─── for_each を使った場合(複数リソース)───
resource "aws_subnet" "private" {
for_each = var.private_subnet_cidrs
# ...
}
# values() + [*] ですべてのサブネットIDをリストで取得
subnet_ids = values(aws_subnet.private)[*].id
# → ["subnet-aaaaa", "subnet-ccccc", "subnet-ddddd"]
countとfor_eachの使い分け
| ケース | 使うべきもの | 理由 |
|---|---|---|
| リソースを作るか作らないか(0 or 1) | count |
単純なON/OFFに最適 |
| 同じ種類のリソースを複数作る | for_each |
キーで個別管理でき、追加・削除が安全 |
countで複数リソースを作ることも可能ですが、途中の要素を削除するとインデックスがずれて意図しない再作成が発生するため、複数リソースにはfor_eachが推奨されます。
まとめ
-
[0]:countで作ったリソース(配列)の最初の要素にアクセスするインデックス -
[*]:リスト内のすべての要素に対して属性を取得するsplat演算子 -
countで作ったリソースは配列、for_eachで作ったリソースはmapとして扱われる -
for_eachのリソースからIDのリストを取得するには、values()でリストに変換してから[*]を使う
[]の中身が数字なのか*なのかで、背景にある仕組みがまったく異なることを覚えておくと、Terraformのコードがぐっと読みやすくなります。