Представь: у тебя пять одинаковых дисков, восемь правил в security group и три почти одинаковых ВМ. Можно, конечно, скопировать блок ресурса восемь раз и поменять пару строчек. Но как только заказчик попросит девятое правило, ты вспомнишь, почему инфраструктура как код (iac) придумала циклы. Копипаста - это технический долг, который ты создаёшь руками прямо сейчас.
В Terraform за размножение ресурсов отвечают два мета-аргумента: count и for_each. В прошлой главе мы разбирали count - он берёт число и делает тебе N копий. Звучит просто, и для совсем тупого размножения это работает. Но у count есть подлая особенность, из-за которой опытные инженеры по умолчанию тянутся к for_each. Об этом ниже, а сначала - механика.

Как устроен for_each: ключ и значение
for_each принимает не число, а коллекцию: либо множество строк (set of strings), либо карту (map). На каждый элемент коллекции Terraform создаёт один экземпляр ресурса. Внутри блока тебе доступны два магических объекта:
- each.key - ключ элемента. Для map это ключ карты, для set он совпадает со значением.
- each.value - значение элемента. Для map это значение по ключу, для set - сама строка.
Простой пример с множеством имён сетевых дисков:
Код: Выделить всё
variable "disk_names" {
type = set(string)
default = ["data", "logs", "backup"]
}
resource "yandex_compute_disk" "extra" {
for_each = var.disk_names
name = "disk-${each.key}"
zone = "ru-central1-a"
size = 10
type = "network-ssd"
}
Почему for_each лучше count: удаление из середины
Вот тот самый момент, ради которого всё затевалось. Допустим, у тебя count и список из трёх дисков: ["data", "logs", "backup"]. В state они лежат как [0], [1], [2]. Ты решаешь убрать "logs" из середины. Список становится ["data", "backup"], и происходит катастрофа в миниатюре:
- индекс [0] остаётся "data" - ок;
- индекс [1] был "logs", а теперь стал "backup" - Terraform думает, что диск надо пересоздать;
- индекс [2] исчез - его удаляют.
for_each привязывается к ключу. Убери "logs" из множества - Terraform удалит ровно yandex_compute_disk.extra["logs"], а ["data"] и ["backup"] даже не вздрогнут. Ключ не сдвигается. Это и есть причина, по которой правило звучит так: если элементы коллекции имеют осмысленную идентичность - бери for_each, и только для тупых одинаковых N штук без индивидуальности оставляй count.
Ресурсы из карты конфигов
Самый сочный паттерн - когда у каждого экземпляра свои параметры. Тут set уже не хватает, нужна map, где значение - это объект с настройками. Сделаем три диска разного размера и типа:
Код: Выделить всё
variable "disks" {
type = map(object({
size = number
type = string
}))
default = {
data = { size = 50, type = "network-ssd" }
logs = { size = 20, type = "network-hdd" }
backup = { size = 100, type = "network-hdd" }
}
}
resource "yandex_compute_disk" "pool" {
for_each = var.disks
name = "disk-${each.key}"
zone = "ru-central1-a"
size = each.value.size
type = each.value.type
}
for_each внутри ресурса: dynamic-блоки
Второй фронт применения - когда повторять надо не сам ресурс, а вложенный блок внутри него. Классика жанра - правила security group. Писать руками десять блоков ingress лень и опасно. На помощь приходит dynamic - это for_each, но для inline-блоков.
Код: Выделить всё
variable "ingress_rules" {
type = map(object({
port = number
description = string
}))
default = {
ssh = { port = 22, description = "ssh" }
http = { port = 80, description = "web" }
https = { port = 443, description = "web tls" }
}
}
resource "yandex_vpc_security_group" "web" {
name = "web-sg"
network_id = yandex_vpc_network.main.id
dynamic "ingress" {
for_each = var.ingress_rules
content {
protocol = "TCP"
description = ingress.value.description
port = ingress.value.port
v4_cidr_blocks = ["0.0.0.0/0"]
}
}
egress {
protocol = "ANY"
description = "all out"
v4_cidr_blocks = ["0.0.0.0/0"]
}
}
Типичные грабли
- for_each не ест список объектов напрямую. Он хочет либо set(string), либо map. Если у тебя list(object(...)), преврати его в map - например, через выражение { for r in var.rules : r.name => r }. Иначе получишь ошибку о неподходящем типе.
- Значения ключей должны быть известны на этапе plan. Если ключ for_each зависит от того, что родится только после apply (id ресурса, который ещё не создан), Terraform ругнётся "Invalid for_each argument". Лечится тем, что в качестве ключей берут статические строки, а не вычисляемые значения.
- Нельзя смешивать count и for_each в одном ресурсе. Только что-то одно.
- Миграция с count на for_each ломает адреса. Был [0], стал ["data"] - Terraform захочет всё пересоздать. Спасает terraform state mv или блоки moved, которыми ты вручную сообщаешь, что [0] теперь это ["data"].
- each доступен только когда задан for_each. Внутри ресурса с count есть count.index, а не each. Не путай.
Мини-лаба
Повтори руками, без подглядывания:
- Опиши переменную disks как map(object) с тремя дисками разного размера, прогони terraform plan apply, убедись что в выводе ресурсы адресуются по ключам в кавычках.
- Удали средний диск из map, сделай plan и глазами проверь: Terraform планирует ровно одно destroy, без пересоздания соседей.
- Сделай тот же фокус через count со списком и сравни план - почувствуй, как сдвигаются индексы.
- Собери security group с тремя правилами через dynamic "ingress", добавь четвёртое правило одной строкой в map.
- Чем отличается each.key от each.value, когда for_each идёт по множеству строк, и когда по карте?
- Почему удаление элемента из середины при count опасно, а при for_each - нет?
- Какой тип данных нужно подать в for_each и что делать, если у тебя на руках list(object)?
- Как внутри dynamic-блока обратиться к текущему значению и почему там не each.value?
for_each - инструмент по умолчанию, count - для редких случаев чистого размножения N одинаковых штук. Ключ - это стабильный якорь в terraform state, поэтому добавление и удаление элементов не задевает соседей. Карта конфигов плюс for_each дают тебе масштабируемый ресурс из одного блока, а dynamic переносит ту же идею внутрь ресурса для повторяющихся inline-блоков вроде правил security group или вложенных дисков. Освоил это - и половина боли с разрастающейся инфраструктурой в terraform yandex cloud просто исчезает.