Проблема в том, что в 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"
}

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 {}
}
Но за удобство приходится платить. Как только у ресурса появляется count, Terraform превращает его в список. И обращаться к нему теперь надо по индексу:
Код: Выделить всё
# было бы yandex_vpc_gateway.nat.id - а теперь так нельзя
output "nat_gateway_id" {
value = yandex_vpc_gateway.nat[0].id
}
Лечится это безопасным доступом через 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
}
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 хочет именно 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
}
Такие шаблоны обычно скармливают в 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 и поиграй флагом, посматривая на пробелы.
Контрольные вопросы
- Почему в HCL нельзя написать обычный if для создания ресурса и какие идиомы это заменяют?
- Что произойдёт при обращении к ресурсу через [0], если его count равен 0, и как защититься?
- В чём преимущество условного for_each = условие ? toset(...) : [] перед count при нескольких ресурсах?
- Зачем нужна тильда ~ в строковой директиве %{ if ... ~} и что будет без неё?
Условная логика в Terraform - это надстройка из идиом над тернарником, а не настоящее ветвление. count = флаг ? 1 : 0 включает и выключает один ресурс, но превращает его в список, так что доступ через [0] оборачивай в one() или splat. Для групп бери for_each = условие ? toset(...) : [] - устойчивее к изменениям и адресует по ключам. Текст внутри шаблонов гни директивой %{ if } с тильдой против лишних пробелов. И помни: это управление инфраструктурой как код по флагам окружения, признанные хаки, которые работают - но требуют аккуратности с типами и доступом к элементам. Всё сказанное справедливо и для OpenTofu, синтаксис там тот же.