Циклы в Terraform: for_each по множествам и картам

Рейтинг: 70.2% · 15 голосов
Подробный курс по Terraform и OpenTofu на примерах Yandex Cloud: ресурсы и провайдеры, состояние и бэкенды, модули, циклы и условия HCL, секреты и Lockbox, тестирование, CI/CD и командная работа. С актуализацией на 2026 (Terraform 1.3-1.10, OpenTofu).
Ответить
Аватара пользователя
Vlad_DevOps
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

Циклы в Terraform: for_each по множествам и картам

Сообщение Vlad_DevOps »

Оглавление курса (47)
  1. Что такое инфраструктура как код и зачем она нужна
  2. Категории инструментов IaC: чем отличаются Ansible, Packer, Kubernetes и Terraform
  3. Чем Terraform отличается от Ansible, Pulumi и CloudFormation
  4. Как работает Terraform под капотом и кто такой OpenTofu
  5. Установка Terraform и OpenTofu, подготовка Yandex Cloud
  6. Первый ресурс: разворачиваем виртуальную машину в Yandex Cloud
  7. Веб-сервер на ВМ: метаданные, user-data и группы безопасности
  8. Переменные ввода и выходные значения в Terraform
  9. Кластер ВМ: группа экземпляров Yandex Compute Instance Group
  10. Балансировщик нагрузки: Application Load Balancer в Yandex Cloud
  11. Состояние Terraform: что такое tfstate и чем опасен локальный файл
  12. Удалённое хранилище состояния: бэкенд на Yandex Object Storage
  13. Изоляция состояния: рабочие области против раскладки по папкам
  14. Источники данных и terraform_remote_state: связываем компоненты
  15. Модули Terraform: переиспользуем инфраструктуру
  16. Входные, локальные и выходные переменные модуля
  17. Версионирование модулей и подводные камни
  18. Циклы в Terraform: параметр count
  19. Циклы в Terraform: for_each по множествам и картам (вы здесь)
  20. Выражения for и строковые директивы
  21. Условная логика: count-трюк, for_each и директива if
  22. Встроенные функции, типы и выражения HCL
  23. Развёртывание без простоя: create_before_destroy
  24. Подводные камни Terraform и рефакторинг с блоком moved
  25. Управление секретами: основы и типы
  26. Инструменты управления секретами: Yandex Lockbox, Vault, SOPS
  27. Аутентификация провайдера и секреты: сервисные аккаунты и OIDC
  28. Секреты в ресурсах и состоянии, ephemeral-значения
  29. Провайдеры Terraform: установка, версии, required_providers
  30. Несколько копий одного провайдера: alias и мультизона
  31. Несколько провайдеров и аккаунтов: configuration_aliases
  32. Terraform, Docker и Kubernetes: Managed Kubernetes в Yandex Cloud
  33. Код Terraform промышленного уровня: почему это долго
  34. Мелкие компонуемые модули вместо монолита
  35. Тестируемые модули и проверки: validation, precondition, postcondition
  36. Версионирование, публикация модулей и Terragrunt
  37. Ручное тестирование Terraform и очистка ресурсов
  38. Автоматические тесты на Terratest: модульные тесты
  39. Интеграционные и сквозные тесты, пирамида тестирования
  40. Нативный terraform test и статический анализ
  41. Внедрение Terraform в команде: процессы и культура
  42. Конвейер развёртывания прикладного кода
  43. Конвейер инфраструктурного кода и золотое правило Terraform
  44. Стиль, ревью и CI/CD-инструменты: Atlantis, TACOS, policy as code
  45. Что нового в Terraform 1.3-1.10: моды, импорт, провайдер-функции, Stacks
  46. OpenTofu в 2026: чем живёт форк и как мигрировать
  47. Сертификация HashiCorp Terraform Associate и карьера
Зачем вообще нужны циклы в IaC

Представь: у тебя пять одинаковых дисков, восемь правил в 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 - сама строка.
Главная фишка - адресация. count создаёт ресурсы с индексами aws_disk[0], aws_disk[1], aws_disk[2]. А for_each создаёт их с человекочитаемыми ключами: ydb_disk["data"], ydb_disk["logs"]. Terraform хранит это соответствие в terraform state, и ключ - это якорь, по которому ресурс опознаётся при следующем terraform plan apply.

Простой пример с множеством имён сетевых дисков:

Код: Выделить всё

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"
}
Получишь три диска: yandex_compute_disk.extra["data"], ["logs"] и ["backup"]. Имя каждого - disk-data, disk-logs, disk-backup. Чисто и предсказуемо.

Почему for_each лучше count: удаление из середины

Вот тот самый момент, ради которого всё затевалось. Допустим, у тебя count и список из трёх дисков: ["data", "logs", "backup"]. В state они лежат как [0], [1], [2]. Ты решаешь убрать "logs" из середины. Список становится ["data", "backup"], и происходит катастрофа в миниатюре:
  • индекс [0] остаётся "data" - ок;
  • индекс [1] был "logs", а теперь стал "backup" - Terraform думает, что диск надо пересоздать;
  • индекс [2] исчез - его удаляют.
Итог: вместо одного удаления ты получил удаление логов И пересоздание бэкапа. На дисках это потеря данных, на ВМ - даунтайм, на правилах фаервола - дырка в безопасности на секунды передеплоя. count привязывается к порядковому номеру, а порядок штука хрупкая.

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
}
Один блок ресурса - три разных диска, каждый со своей конфигурацией. Хочешь четвёртый? Добавь строчку в map. Хочешь убрать logs? Удали строчку - и пострадает только logs. Это и есть нормальные модули terraform в зародыше: данные отдельно, логика отдельно.

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"]
  }
}
Обрати внимание: внутри dynamic итератор называется по имени блока - ingress.value, а не each.value. Это сбивает новичков. По умолчанию итератор берёт имя dynamic-блока (тут ingress), и обращаешься ты через него. Хочешь другое имя - задавай явно аргументом iterator. Блок content описывает, как выглядит один экземпляр inline-блока.

Типичные грабли
  • 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. Не путай.
Кстати, всё сказанное один в один работает и в opentofu - это форк Terraform, синтаксис for_each и dynamic там идентичен, так что знание переносимое.

Мини-лаба

Повтори руками, без подглядывания:
  • Опиши переменную 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 просто исчезает.
👍 ❤️3 🔥1 😄 🤔1
Аватара пользователя
KernelNerd
Сообщения: 1
Зарегистрирован: 12 май 2026, 21:00

Re: Циклы в Terraform: for_each по множествам и картам

Сообщение KernelNerd »

Спасибо, наконец-то дошло зачем for_each вместо count. У меня как раз на проде список ВМ через count и при удалении одной всё пересоздаётся, теперь понятно почему. Пойду переделывать через map.
👍1 ❤️1 🔥1 😄 🤔1
Аватара пользователя
zvi1
Сообщения: 1
Зарегистрирован: 11 май 2026, 19:02

Re: Циклы в Terraform: for_each по множествам и картам

Сообщение zvi1 »

А подскажите, если у меня переменная именно list(object), то выражение { for r in var.rules : r.name => r } куда писать - прямо в for_each ресурса или сначала в locals? Немного запутался где это объявлять.
👍 ❤️1 🔥 😄 🤔1
Ответить
← Предыдущая глава
Циклы в Terraform: параметр count
Следующая глава →
Выражения for и строковые директивы

Все главы курса «Terraform: инфраструктура как код на Yandex Cloud»

Поделиться темой: ✈ Telegram VK
Похожие запросы: terraform yandex cloud с чего начать новичкукак установить terraform и opentofu на linuxчто такое terraform state и tfstate простыми словамиудаленный backend terraform на object storage yandex cloudчто такое инфраструктура как код iac простыми словамиterraform plan и apply что делают и чем отличаются

Вернуться в «Terraform: инфраструктура как код на Yandex Cloud»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость