count и for_each: множественные ресурсы
Чтобы не копировать один блок десять раз, Terraform даёт два meta-аргумента: count создаёт N одинаковых копий по индексу, for_each — по ключам из набора. Выбор между ними влияет на стабильность state.
Правило простое: если ресурсы по-настоящему одинаковы и их различает только число —
count. Если у каждого своя идентичность —for_each. Перепутаете — получите лишние пересоздания.
count принимает число и создаёт столько экземпляров, адресуя их по индексу: aws_instance.web[0], [1], [2]. Внутри блока доступен count.index. for_each принимает мапу или множество и создаёт по экземпляру на каждый ключ, адресуя их по ключу: aws_instance.web["api"]. Внутри доступны each.key и each.value.
Почему for_each стабильнее
# count: адресация по индексу
resource "aws_instance" "srv" {
count = 3
instance_type = "t2.micro"
tags = { Name = "srv-${count.index}" }
}
# for_each: адресация по ключу
resource "aws_instance" "srv" {
for_each = toset(["web", "api", "worker"])
instance_type = "t2.micro"
tags = { Name = each.key }
}
Опасность count: если из списка ["web","api","worker"] удалить средний элемент, индексы сдвинутся — worker с индекса 2 переедет на 1, и Terraform пересоздаст ресурсы, которые трогать не собирался. У for_each такого нет: ключ "worker" остаётся ключом независимо от соседей.
Как работает под капотом
Terraform хранит экземпляры в state по их адресу. Для count адрес — это позиция (индекс), для for_each — ключ. Смоделируем катастрофу сдвига индексов: удаляем «api» из середины и смотрим, что Terraform «думает» о каждой позиции.
before = ["web", "api", "worker"]
after = ["web", "worker"] # удалили api из середины
print("=== COUNT (по индексу) ===")
for i in range(max(len(before), len(after))):
b = before[i] if i < len(before) else None
a = after[i] if i < len(after) else None
verdict = "ok" if b == a else f"ПЕРЕСОЗДАТЬ {b} -> {a}"
print(f" [{i}] было={b}, стало={a} -> {verdict}")
print("\n=== FOR_EACH (по ключу) ===")
for key in set(before) | set(after):
if key in before and key in after:
print(f" [{key}] остаётся (ok)")
elif key in before:
print(f" [{key}] удаляется")
else:
print(f" [{key}] создаётся")
«Попробуй сам ▶» — у count сдвиг индексов задевает worker, хотя его не трогали. У for_each удаляется ровно api, остальное цело.
Частые ошибки
- count для разнородных ресурсов. Список, который меняется в середине, постоянно вызывает лишние пересоздания.
- for_each по списку без toset.
for_eachтребует множество или мапу; список нужно обернуть вtoset(...). - Значения, неизвестные на этапе plan. Если ключи
for_eachзависят от ещё не созданных ресурсов, Terraform не сможет построить план.
Best practices
- По умолчанию выбирайте
for_each— стабильные адреса по ключу избавляют от случайных пересозданий. countоставьте для двух случаев: фича-флаг (count = var.enabled ? 1 : 0) и реально N одинаковых копий.- Ключи
for_eachдолжны быть статичными строками, известными на этапе plan.
Разбор глубже
Есть тонкость, о которую спотыкаются почти все: значения, передаваемые в for_each, должны быть известны на этапе plan. Если ключи зависят от атрибутов ресурсов, которые ещё не созданы (например, ID, генерируемый облаком), Terraform не сможет построить план и выдаст ошибку вроде «Invalid for_each argument». Лечится это либо использованием статически известных ключей, либо разбиением на два apply, либо переходом на count там, где число известно. Понимание границы «известно на plan / известно только на apply» — один из ключевых навыков опытного инженера Terraform.
Отдельно стоит запомнить идиому условного ресурса. Конструкция count = var.create_lb ? 1 : 0 — это стандартный способ включать и выключать ресурс по флагу: при false создаётся ноль копий, то есть ресурса нет вовсе. Аналогично делают и с модулями. Но есть подвох: к такому ресурсу обращаются через индекс aws_lb.this[0], и если флаг выключен, обращение к индексу 0 упадёт. Поэтому ссылки на условные ресурсы оборачивают в проверки или используют функции вроде one(...), аккуратно обрабатывающие пустой список.
Итог: count адресует по индексу и страдает от сдвигов, for_each — по ключу и стабилен. Для уникальных ресурсов почти всегда нужен for_each. Дальше — функции и выражения, делающие код выразительнее.