Условная логика: count-трюк, for_each и директива if

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

Условная логика: count-трюк, for_each и директива if

Сообщение 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 и карьера
Рано или поздно ты упрёшься в стену: один и тот же код Terraform нужно гонять в dev, stage и prod, но окружения хотят разного. В dev не нужен дорогой балансировщик. В prod нужен бэкап и алертинг, а в тестовом - ни в коем случае, чтобы не палить бюджет. Хочется сказать "вот этот ресурс создавай только если флаг включён" - и тут начинается самое интересное.

Проблема в том, что в HCL нет привычного if (...) { создать ресурс }. Декларативный язык так не работает: ты описываешь желаемое состояние, а не пошаговый алгоритм. Поэтому условное создание ресурсов в Terraform - это набор идиом, местами откровенных хаков. Давай честно: то, что мы тут разберём, выглядит как трюки, потому что это и есть трюки. Но они стандартные, их используют все, и без них инфраструктура как код быстро превращается в копипасту трёх почти одинаковых конфигов.

В этом уроке: тернарный оператор, count-трюк для включения/выключения ресурса, тот же фокус через for_each, и строковая директива %{ if } в шаблонах. Всё на провайдере Yandex Cloud, чтобы пощупать на реальных ресурсах.

Тернарный оператор: фундамент всего

Единственное настоящее условное выражение в HCL - это тернарник, который ты наверняка знаешь из любого языка:

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

условие ? значение_если_истина : значение_если_ложь
Работает он именно как выражение, а не как оператор ветвления. То есть он не "выполняет" одну из веток - он вычисляет одно из двух значений и подставляет результат туда, где стоит. Простой пример - выбрать размер диска в зависимости от окружения:

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

variable "env" {
  type    = string
  default = "dev"
}

resource "yandex_compute_disk" "boot" {
  name = "boot-${var.env}"
  size = var.env == "prod" ? 50 : 20
  zone = "ru-central1-a"

  image_id = "fd8ci0qhqpkfn8d4q1pk"
}
Тут size получает 50 для прода и 20 для всего остального. Важный нюанс: обе ветки должны быть одного типа. Нельзя в одной вернуть число, а в другой строку - Terraform на этапе terraform plan ругнётся на несовместимость типов. И ещё: тернарник вычисляет обе ветки на уровне типов, поэтому обе должны быть валидными выражениями, даже если фактически выберется только одна.

Изображение

count-трюк: создать ресурс или не создавать

А теперь главный фокус главы. Как не создать ресурс вовсе? Через мета-аргумент count. Он задаёт, сколько копий ресурса развернуть. Если передать 0 - копий ноль, ресурс просто не появится. Складываем это с тернарником:

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

variable "enable_nat" {
  type    = bool
  default = false
}

resource "yandex_vpc_gateway" "nat" {
  count = var.enable_nat ? 1 : 0
  name  = "nat-gateway"

  shared_egress_gateway {}
}
Читается как "если enable_nat - создай один шлюз, иначе ни одного". Это и есть тот самый count = var.flag ? 1 : 0, идиома номер один для условного создания. Удобно: один булев флаг включает и выключает кусок инфраструктуры.

Но за удобство приходится платить. Как только у ресурса появляется count, Terraform превращает его в список. И обращаться к нему теперь надо по индексу:

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

# было бы yandex_vpc_gateway.nat.id - а теперь так нельзя
output "nat_gateway_id" {
  value = yandex_vpc_gateway.nat[0].id
}
Вот здесь новички и попадают в главную ловушку. Когда флаг включён, в списке есть элемент [0], всё хорошо. А когда флаг выключен, count равен 0, список пустой - и обращение к [0] валит план с ошибкой "index out of range". То есть код, который прекрасно работал в prod, разваливается в dev, где ресурс отключён.

Лечится это безопасным доступом через one() или через тернарник вокруг самого обращения:

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

# вариант 1: one() вернёт сам элемент либо null, если список пуст
output "nat_gateway_id" {
  value = one(yandex_vpc_gateway.nat[*].id)
}

# вариант 2: проверяем флаг перед индексацией
output "nat_gateway_id" {
  value = var.enable_nat ? yandex_vpc_gateway.nat[0].id : null
}
Конструкция nat[*].id - это splat-выражение: оно собирает поле id со всех элементов списка в новый список. Если элементов ноль, получаем пустой список, и one() аккуратно отдаёт null вместо падения. Запомни этот приём, он спасает нервы.

for_each как более чистая альтернатива

count-трюк отлично работает для бинарного "включить/выключить", но у него есть слабость: ресурсы адресуются по числовому индексу. Если у тебя список из трёх ресурсов и ты удалишь средний, индексы сдвинутся, и Terraform решит пересоздать половину - потому что он привязывает состояние к позиции в списке. Для условного "ноль или один" это неважно, а вот когда ресурсов несколько - больно.

Поэтому для условных групп ресурсов чаще берут for_each. Хитрость та же тернарная, только вместо 0/1 мы подсовываем пустое или непустое множество:

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

variable "create_buckets" {
  type    = bool
  default = true
}

variable "bucket_names" {
  type    = set(string)
  default = ["logs", "backups", "artifacts"]
}

resource "yandex_storage_bucket" "this" {
  for_each = var.create_buckets ? var.bucket_names : toset([])

  bucket = "cyberlake-${each.key}"
}
Когда флаг включён, for_each идёт по множеству имён и создаёт по бакету на каждое. Когда выключен - получает пустое множество toset([]) и не создаёт ничего. Идиома читается как условие ? toset(...) : []. Бонус: ресурсы адресуются по ключу (yandex_storage_bucket.this["logs"]), а не по индексу, поэтому добавление и удаление элементов не дёргает соседей. Это та же логика "выключить кусок инфраструктуры по флагу", но устойчивее к изменениям.

Маленькое правило: for_each хочет именно set или map. Если у тебя список (list), оберни его в toset(), иначе Terraform может пожаловаться или начать капризничать на повторах.

Строковая директива %{ if } в шаблонах

Условия живут не только на уровне ресурсов. Иногда нужно собрать строку - cloud-init, конфиг nginx, скрипт инициализации - и включить в неё кусок текста только при определённом условии. Для этого в HCL есть строковые директивы прямо внутри шаблонных строк:

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

locals {
  with_monitoring = true

  init_script = <<-EOT
    #!/bin/bash
    apt-get update
    %{ if local.with_monitoring ~}
    apt-get install -y prometheus-node-exporter
    systemctl enable node-exporter
    %{ endif ~}
    echo "init done"
  EOT
}
Синтаксис %{ if условие } ... %{ endif } вставляет блок текста, только если условие истинно. Есть и %{ else }, и даже %{ for x in list } для повторов. Тильда ~ в конце директивы съедает лишние пробелы и переносы строк, чтобы результат не зарос пустыми линиями - без неё в выводе остаётся мусор от форматирования.

Такие шаблоны обычно скармливают в user_data виртуалки или в templatefile(). Это позволяет одним и тем же шаблоном обслуживать и prod (с мониторингом), и dev (без него), не плодя файлы.

Типичные грабли
  • Обращение к [0] у выключенного ресурса. Самая частая боль с count-трюком. Всегда оборачивай доступ в one(), splat или проверку флага.
  • Разные типы в ветках тернарника. true-ветка возвращает строку, false - число, и план падает. Приводи обе к одному типу.
  • list вместо set в for_each. Передавай toset(...), особенно если строишь множество динамически.
  • Переключение count на for_each "на лету". Если ресурс уже развёрнут с count, а ты переписал его на for_each, Terraform увидит смену схемы адресации и захочет всё пересоздать. На боевом это больно - планируй миграцию состояния через terraform state mv заранее.
  • Забытая тильда в директиве. %{ if } без ~ оставляет в строке пустые строки и отступы. В bash это обычно проходит, в YAML - ломает отступы и валит парсер.
Мини-лаба

Без облака, чисто на синтаксис - руками в пустой папке:
  • Объяви variable "env" и ресурс yandex_compute_disk, где size зависит от env через тернарник.
  • Добавь yandex_vpc_gateway с count = var.enable_nat ? 1 : 0 и сделай output его id через one() - проверь terraform validate при обоих значениях флага.
  • Перепиши условное создание группы бакетов через for_each = var.flag ? toset(...) : [] и сравни, как меняются адреса ресурсов в plan.
  • Собери local со скриптом и директивой %{ if ... ~} ... %{ endif ~}, выведи через output и поиграй флагом, посматривая на пробелы.
Если нет аккаунта в облаке - terraform validate и terraform plan всё равно проверят синтаксис и типы, реальные ресурсы для этого создавать не обязательно.

Контрольные вопросы
  • Почему в HCL нельзя написать обычный if для создания ресурса и какие идиомы это заменяют?
  • Что произойдёт при обращении к ресурсу через [0], если его count равен 0, и как защититься?
  • В чём преимущество условного for_each = условие ? toset(...) : [] перед count при нескольких ресурсах?
  • Зачем нужна тильда ~ в строковой директиве %{ if ... ~} и что будет без неё?
Итог: что запомнить

Условная логика в Terraform - это надстройка из идиом над тернарником, а не настоящее ветвление. count = флаг ? 1 : 0 включает и выключает один ресурс, но превращает его в список, так что доступ через [0] оборачивай в one() или splat. Для групп бери for_each = условие ? toset(...) : [] - устойчивее к изменениям и адресует по ключам. Текст внутри шаблонов гни директивой %{ if } с тильдой против лишних пробелов. И помни: это управление инфраструктурой как код по флагам окружения, признанные хаки, которые работают - но требуют аккуратности с типами и доступом к элементам. Всё сказанное справедливо и для OpenTofu, синтаксис там тот же.
👍3 ❤️3 🔥2 😄 🤔
Аватара пользователя
mojemo
Сообщения: 2
Зарегистрирован: 29 май 2026, 13:02

Re: Условная логика: count-трюк, for_each и директива if

Сообщение mojemo »

Спасибо, наконец дошло зачем нужен one(). У меня как раз в dev план падал на output к выключенному ресурсу, а я думал что это баг провайдера. Оказывается сам себе яму выкопал с count и [0].
👍1 ❤️2 🔥 😄 🤔
Аватара пользователя
hfornelli
Сообщения: 1
Зарегистрирован: 27 май 2026, 10:27

Re: Условная логика: count-трюк, for_each и директива if

Сообщение hfornelli »

А можно ли совмещать count и for_each на одном ресурсе? Или это всегда или одно, или другое? И ещё вопрос - тильду ставить только в конце директивы или в начале тоже иногда надо?
👍1 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Выражения for и строковые директивы
Следующая глава →
Встроенные функции, типы и выражения HCL

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

Поделиться темой: ✈ Telegram VK
  • Похожие темы
Похожие запросы: terraform count или for_each что выбрать для циклаусловная логика в terraform count for_each и директива if

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

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

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