Встроенные функции, типы и выражения HCL

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

Встроенные функции, типы и выражения HCL

Сообщение 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 и карьера
Если ты дошел до этой главы, то уже умеешь писать ресурсы и переменные. Но рано или поздно наступает момент, когда тупого присваивания "переменная -> атрибут" перестает хватать. Тебе нужно склеить два списка подсетей, подставить дефолт, если значение не передали, сгенерировать CIDR для каждой зоны доступности, прочитать ключ из файла и засунуть его в метаданные виртуалки. И вот тут начинается настоящий HCL - язык, на котором написан Terraform.

Боль, которую это решает, простая. Без типов и функций ты либо копипастишь одно и то же по десять раз, либо хардкодишь значения, которые потом невозможно поменять в одном месте. Инфраструктура как код (iac) ценна ровно тем, что код можно параметризовать и переиспользовать. А значит, надо уметь этим кодом немного программировать. Не пугайся слова "программировать" - HCL не Тьюринг-полный, циклов while тут нет, и это скорее плюс: меньше способов выстрелить себе в ногу.

В этом уроке разберем систему типов HCL, самые ходовые встроенные функции, валидацию переменных и terraform console - инструмент, который сэкономит тебе часы отладки.

Система типов: что вообще бывает

HCL знает три примитивных типа и три составных (коллекции и структуры).

Примитивы:
  • string - строка, "hello".
  • number - число, причем и целое, и дробное (42, 3.14). Отдельного int нет.
  • bool - true или false.
Коллекции и структуры:
  • list(T) - упорядоченный список одного типа: ["a", "b", "c"]. Доступ по индексу.
  • set(T) - множество, без порядка и без дубликатов.
  • map(T) - словарь ключ-значение, ключи всегда строки: { env = "prod" }.
  • object({...}) - структура с фиксированным набором именованных полей разных типов.
  • tuple([...]) - как list, но каждый элемент может быть своего типа и количество фиксировано.
Есть еще тип any - "терраформ, разберись сам". Удобно, но злоупотреблять не стоит: any выключает проверку типов, и ошибку ты словишь не на plan, а где-то глубже.

Объявление типизированных переменных выглядит так:

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

variable "zones" {
  type    = list(string)
  default = ["ru-central1-a", "ru-central1-b", "ru-central1-d"]
}

variable "labels" {
  type    = map(string)
  default = { team = "platform", env = "dev" }
}

variable "instance" {
  type = object({
    cores  = number
    memory = number
    name   = string
  })
}
Отдельно стоит знать про optional внутри object. По умолчанию все поля объекта обязательны. Если хочешь, чтобы поле можно было не указывать, оборачивай его в optional и при желании задавай значение по умолчанию:

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

variable "vm" {
  type = object({
    cores  = number
    memory = number
    preemptible = optional(bool, false)
    nat         = optional(bool, true)
  })
}
Теперь тот, кто использует эту переменную, может передать только cores и memory, а preemptible и nat подставятся сами. Очень удобно для модулей с кучей настроек.

Изображение

Функции, которые реально пригодятся

Функций в Terraform под сотню, но в повседневной жизни ты крутишь штук пятнадцать. Разберу самые ходовые на примерах Yandex Cloud.

lookup и coalesce - дефолты. lookup достает значение из map по ключу, а если ключа нет - возвращает запасное. coalesce берет первое непустое значение из списка аргументов.

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

locals {
  zone_to_subnet = {
    "ru-central1-a" = "10.1.0.0/24"
    "ru-central1-b" = "10.2.0.0/24"
  }
  subnet = lookup(local.zone_to_subnet, "ru-central1-a", "10.0.0.0/24")
  name   = coalesce(var.custom_name, "default-vm")
}
merge и concat - склейка. merge соединяет несколько map в один (последний выигрывает при конфликте ключей), concat склеивает списки.

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

locals {
  common_labels = { managed_by = "terraform", project = "cyberlake" }
  all_labels    = merge(local.common_labels, var.extra_labels)
  all_zones     = concat(["ru-central1-a"], var.extra_zones)
}
flatten и length. flatten разворачивает вложенные списки в один плоский, length считает элементы (или символы в строке).

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

locals {
  nested = [["a", "b"], ["c"], []]
  flat   = flatten(local.nested)   # ["a", "b", "c"]
  count  = length(local.flat)      # 3
}
file и templatefile - работа с файлами. file просто читает содержимое файла как строку. templatefile читает шаблон и подставляет в него переменные - незаменим для cloud-init и user-data.

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

resource "yandex_compute_instance" "web" {
  name = "web-1"
  # ... resources, boot_disk, network_interface ...

  metadata = {
    ssh-keys  = "ubuntu:${file("~/.ssh/id_ed25519.pub")}"
    user-data = templatefile("${path.module}/cloud-init.tftpl", {
      hostname = "web-1"
      packages = ["nginx", "git"]
    })
  }
}
jsonencode и jsondecode. Превращают HCL-значение в JSON-строку и обратно. Постоянно нужно, когда API провайдера или сервиса ждет JSON: IAM-политики, конфиги, метаданные.

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

locals {
  policy = jsonencode({
    version = "1"
    rules   = [{ action = "allow", port = 443 }]
  })
}
cidrsubnet - сети. Нарезает большую подсеть на куски. Первый аргумент - базовый CIDR, второй - сколько бит добавить к маске, третий - номер куска. Идеально для раздачи подсетей по зонам.

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

variable "zones" {
  type    = list(string)
  default = ["ru-central1-a", "ru-central1-b", "ru-central1-d"]
}

resource "yandex_vpc_subnet" "this" {
  count          = length(var.zones)
  name           = "subnet-${var.zones[count.index]}"
  zone           = var.zones[count.index]
  network_id     = yandex_vpc_network.this.id
  v4_cidr_blocks = [cidrsubnet("10.10.0.0/16", 8, count.index)]
}
Здесь cidrsubnet("10.10.0.0/16", 8, 0) даст 10.10.0.0/24, индекс 1 - 10.10.1.0/24 и так далее. Никакого ручного ведения таблицы CIDR.

formatdate - даты. Форматирует timestamp в нужный вид. Часто используют с timestamp() для меток времени в именах или labels.

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

locals {
  today = formatdate("YYYY-MM-DD", timestamp())
}
try и can - защита от ошибок. Это твои страховочные тросы. try пробует вычислить выражения по очереди и возвращает первое, которое не упало. can возвращает true/false - получилось вычислить или нет, удобно в валидации.

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

locals {
  # если var.config.timeout не задан или структура другая - возьмем 30
  timeout = try(var.config.timeout, 30)
}
Валидация переменных

Хорошая переменная не только типизирована, но и проверяет сама себя. Блок validation внутри variable не дает запустить plan с мусором на входе. Это дешевле, чем ловить ошибку через десять минут apply где-то в облаке.

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

variable "cores" {
  type        = number
  description = "Количество vCPU"
  validation {
    condition     = var.cores >= 2 && var.cores <= 32
    error_message = "cores должно быть в диапазоне от 2 до 32."
  }
}

variable "env" {
  type = string
  validation {
    condition     = contains(["dev", "stage", "prod"], var.env)
    error_message = "env должен быть одним из: dev, stage, prod."
  }
}
В паре с can валидация становится мощнее - можно проверить, что строка вообще парсится как нужный формат, не уронив всю конфигурацию:

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

variable "cidr" {
  type = string
  validation {
    condition     = can(cidrnetmask(var.cidr))
    error_message = "cidr должен быть валидным CIDR, например 10.0.0.0/16."
  }
}
terraform console - твоя песочница для отладки

Главный инструмент урока. Команда terraform console открывает интерактивную консоль, где можно вычислять любые выражения на данных текущего проекта: переменные, locals, функции, даже атрибуты ресурсов из state. Не надо делать plan, чтобы проверить, что выдаст твой merge или cidrsubnet - просто спроси у консоли.

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

$ terraform console
> 1 + 2
3
> cidrsubnet("10.10.0.0/16", 8, 5)
"10.10.5.0/24"
> merge({a=1}, {b=2})
{
  "a" = 1
  "b" = 2
}
> [for z in var.zones : upper(z)]
[
  "RU-CENTRAL1-A",
  "RU-CENTRAL1-B",
  "RU-CENTRAL1-D",
]
> try(var.config.timeout, 30)
30
Привыкни проверять сложные выражения тут перед тем, как вставлять их в код. Экономит кучу циклов plan-правка-plan. Кстати, в OpenTofu (открытый форк Terraform) console работает точно так же - синтаксис HCL и набор функций у них общий, так что все из этого урока применимо и к opentofu.

Где функции реально нужны в инфра-коде

Чтобы не сложилось ощущения "красиво, но зачем" - вот живые сценарии:
  • Раздача подсетей по зонам через cidrsubnet и count - классика VPC.
  • Сборка labels из общих и специфичных через merge - чтобы тегирование было единообразным.
  • Генерация cloud-init для виртуалок через templatefile.
  • Формирование JSON-политик и конфигов через jsonencode.
  • Безопасное чтение опциональных настроек модуля через try и lookup, чтобы модуль не падал на отсутствующих полях.
Типичные грабли
  • Путать list и set. К set нельзя обратиться по индексу - порядка нет. Если делаешь for_each, set как раз то что надо, а вот var.zones[0] на set не сработает.
  • Числа и строки. cidrsubnet и подобные ждут number, а из переменной окружения или tfvars иногда прилетает string. Приводи через tonumber, если что.
  • try глушит все подряд. Соблазнительно обернуть пол-конфига в try, но тогда настоящая ошибка молча превратится в дефолт, и ты будешь долго гадать, почему ресурс создался не такой. Используй точечно.
  • any вместо нормального типа. Работает, пока не сломается. Описывай object явно - получишь подсказки об ошибках на plan.
  • Забыть про path.module в file/templatefile. Относительные пути считаются от рабочей директории, а не от модуля. Внутри модуля всегда префиксуй ${path.module}.
Мини-лаба

Сделай руками, без этого не уложится:
  • Открой terraform console и поиграйся: merge двух map, concat двух списков, flatten вложенного списка, cidrsubnet с разными индексами.
  • Объяви переменную типа object с двумя обязательными и одним optional полем. Проверь, что без optional-поля plan проходит.
  • Добавь к числовой переменной блок validation с диапазоном и убедись, что неверное значение ловится на plan.
  • Напиши local с templatefile-шаблоном (хотя бы пустым cloud-init) и посмотри результат через console командой local.имя.
Контрольные вопросы
  • Чем set отличается от list и почему к set нельзя обратиться по индексу?
  • Что делает optional в object и зачем второй аргумент в нем?
  • В чем разница между try и can и где каждый уместнее?
  • Зачем нужен terraform console и что в нем можно вычислять?
Итог: что запомнить

HCL дает тебе типы (string, number, bool, list, set, map, object, tuple, any plus optional) и десятки функций, чтобы инфраструктура как код была параметризуемой и DRY. Самые ходовые функции - lookup, coalesce, merge, concat, flatten, length, file, templatefile, jsonencode, cidrsubnet, try. Валидация переменных ловит ошибки на этапе terraform plan, а не во время apply. И главное - terraform console превращает написание выражений из гадания в нормальную отладку. Проверяй сложную логику там, прежде чем доверять ей реальные ресурсы в Yandex Cloud.
👍3 ❤️3 🔥3 😄 🤔
Аватара пользователя
torch_wizard
Сообщения: 1
Зарегистрирован: 24 май 2026, 17:22

Re: Встроенные функции, типы и выражения HCL

Сообщение torch_wizard »

Про terraform console прям спасибо, я раньше каждый раз plan гонял чтобы проверить cidrsubnet, а оно вон как. Сэкономили мне нервы.
👍2 ❤️2 🔥 😄 🤔
Аватара пользователя
brengs123
Сообщения: 1
Зарегистрирован: 17 май 2026, 23:16

Re: Встроенные функции, типы и выражения HCL

Сообщение brengs123 »

А подскажите, optional работает только внутри object или к обычной переменной тоже можно прикрутить дефолт через него? Немного запутался где optional, а где default.
👍 ❤️1 🔥1 😄 🤔
Ответить
← Предыдущая глава
Условная логика: count-трюк, for_each и директива if
Следующая глава →
Развёртывание без простоя: create_before_destroy

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

Поделиться темой: ✈ Telegram VK

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

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

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