Входные, локальные и выходные переменные модуля

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

Входные, локальные и выходные переменные модуля

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

Чтобы модуль стал переиспользуемым, ему нужны три вещи: ручки снаружи (вход), записная книжка внутри (локальные значения) и табличка с результатами (выход). Это variable, locals и output. Разберём каждую по очереди и соберём один модуль, который умеет разворачивать и крошечный dev, и серьёзный prod без единой правки своего кода. Это базовый кирпич, без которого инфраструктура как код (iac) превращается в копипасту с человеческим лицом.

variable - входные параметры модуля

variable - это аргумент, который модуль принимает снаружи. Объявил переменную - и тот, кто вызывает модуль, может передать в неё значение. Не передал - возьмётся значение по умолчанию, если ты его задал.

Базовый набор полей у variable простой: type (тип), default (значение по умолчанию), description (описание для людей и для terraform plan apply). Типы бывают примитивные (string, number, bool) и составные (list, map, object).

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

variable "env_name" {
  type        = string
  description = "Имя окружения: dev, stage, prod"
}

variable "instance_count" {
  type    = number
  default = 1
}

variable "machine_type" {
  type    = string
  default = "standard-v3"
}

variable "memory_gb" {
  type    = number
  default = 2
}
Обрати внимание: у env_name нет default. Это сознательно - окружение обязано указать тот, кто вызывает модуль, иначе непонятно что мы вообще разворачиваем. Если default нет и значение не передали, Terraform остановится и спросит. А вот instance_count и machine_type имеют разумные дефолты: вызвал модуль без них - получишь одну небольшую машину. Это хорошее правило: обязательным делай только то, без чего модуль не имеет смысла, остальное снабжай дефолтами.

Можно добавить и проверку - validation. Например, не дать никому передать ноль инстансов:

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

variable "instance_count" {
  type    = number
  default = 1

  validation {
    condition     = var.instance_count >= 1 && var.instance_count <= 10
    error_message = "instance_count должен быть от 1 до 10."
  }
}
Внутри модуля к переменной обращаешься через var.имя: var.env_name, var.instance_count. Запомни: var - это вход в модуль, а не глобальная переменная на весь мир.

Изображение

locals - внутренняя кухня

Теперь частая ошибка новичков. Им нужно склеить имя ресурса вида web-prod или вычислить число ядер, и они заводят под это... ещё одну variable. Не надо. Если значение вычисляется внутри модуля из других значений и снаружи его задавать не должны - это locals, локальное значение.

locals - это записная книжка модуля. Туда складывают константы (порты, теги, префиксы) и промежуточные вычисления, чтобы не повторять одно и то же выражение в десяти местах.

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

locals {
  app_name    = "web"
  name_prefix = "${local.app_name}-${var.env_name}"
  http_port   = 80
  https_port  = 443

  common_labels = {
    environment = var.env_name
    managed_by  = "terraform"
  }
}
Несколько правил, которые экономят нервы:
  • Блок называется locals (множественное число), а обращение идёт через local.имя (единственное). Перепутать легко, ошибка будет загадочной.
  • Внутри locals можно ссылаться на var, на ресурсы и на другие local. Можно собирать значение из значения - как name_prefix выше.
  • Локальные значения не настраиваются снаружи. Это их фишка, а не баг: ты прячешь внутреннюю логику от пользователя модуля.
Правило большого пальца: повторяешь одно и то же выражение дважды - вынеси в local. Хочешь, чтобы значение задавали при вызове - это var. Хочешь отдать значение наружу - читай следующую секцию.

output - что модуль отдаёт наружу

Ресурсы, созданные внутри модуля, по умолчанию снаружи не видны. Корневой код не может просто так дотянуться до id группы или адреса балансировщика, которые родились в недрах модуля. Чтобы пробросить значение наружу, его объявляют как output.

output - это табличка с результатами на двери модуля. Создал группу инстансов - отдай её id. Поднял балансировщик - отдай его адрес, чтобы другой модуль или человек знал, куда стучаться.

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

output "instance_group_id" {
  description = "ID группы инстансов"
  value       = yandex_compute_instance_group.web.id
}

output "instance_ids" {
  value = yandex_compute_instance_group.web.instances[*].instance_id
}

output "lb_address" {
  description = "Внешний адрес балансировщика"
  value       = yandex_lb_network_load_balancer.web.listener[*].external_address_spec[*].address
}
И вот теперь магия связывания модулей. Когда ты вызываешь модуль в корневом коде, его output-ы доступны через module.имя_модуля.имя_output. Это тот самый клей, которым модули соединяются между собой: один модуль отдал id сети, другой его принял на вход.

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

module "backend" {
  source         = "./modules/app"
  env_name       = "prod"
  instance_count = 3
}

module "frontend" {
  source   = "./modules/app"
  env_name = "prod"

  # передаём выход одного модуля во вход другого
  upstream_group_id = module.backend.instance_group_id
}

output "backend_lb" {
  value = module.backend.lb_address
}
Заметь: значение пошло по цепочке вход -> ресурс -> выход -> вход следующего модуля. Terraform по этим ссылкам сам строит граф зависимостей и понимает, что backend надо создать раньше frontend. Тебе порядок указывать не нужно - он выводится из данных. Именно это отличает terraform от bash-скриптов с захардкоженной последовательностью.

Один модуль - два окружения

Соберём всё вместе. Вот ради чего мы и затевали переменные: один и тот же код модуля даёт нам лёгкий dev и тяжёлый prod. Разница только в значениях при вызове.

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

module "app_dev" {
  source         = "./modules/app"
  env_name       = "dev"
  instance_count = 1
  machine_type   = "standard-v3"
  memory_gb      = 2
}

module "app_prod" {
  source         = "./modules/app"
  env_name       = "prod"
  instance_count = 4
  machine_type   = "standard-v3"
  memory_gb      = 8
}
Код модуля - один. Стендов - два, и они честно разные по размеру. Захотел третий, для нагрузочного теста - дописал ещё один блок module с своими цифрами. Никакого копирования папок, никакого риска, что в одной копии поправил, а в другой забыл. Состояние (terraform state) при этом аккуратно разведёт ресурсы по адресам module.app_dev.* и module.app_prod.*, так что они не перепутаются.

Типичные грабли
  • Путают locals и local. Объявляешь в блоке locals, обращаешься через local.x. Множественное и единственное число - классическая засада.
  • Заводят variable там, где нужен local. Если значение никто снаружи не задаёт, а оно вычисляется внутри - это local. Лишние input-ы раздувают интерфейс модуля и сбивают с толку.
  • Забывают, что output - это контракт. Удалил или переименовал output, на который кто-то ссылается через module.x.y - сломал зависимый код. Меняй выходы осознанно.
  • Пытаются достать ресурс модуля напрямую, без output. Снаружи виден только то, что явно отдано через output. Внутренние ресурсы инкапсулированы, и это правильно.
  • Ставят default на то, что обязано быть осознанным выбором. Дефолтный env_name = "dev" однажды устроит тебе деплой в dev вместо prod или наоборот. Опасные параметры лучше держать без дефолта.
  • Думают, что local кешируется между запусками. Нет, locals вычисляются на каждый terraform plan apply заново, это не переменная состояния.
Кстати, всё описанное один в один работает и в opentofu - синтаксис variable, locals и output там идентичный, так что навык переносимый.

Мини-лаба

Возьми свой модуль из прошлого урока (или собери учебный на пару ресурсов Yandex Cloud) и проделай руками:
  • Вынеси в variable три параметра: env_name (без default), instance_count и machine_type (с дефолтами). Добавь к instance_count блок validation на диапазон 1..10.
  • Заведи блок locals с name_prefix вида "${var.env_name}-web" и map common_labels. Используй name_prefix хотя бы в двух местах внутри модуля.
  • Объяви два output: id главного ресурса и какой-нибудь адрес или имя. Пропиши description у обоих.
  • В корневом коде вызови модуль дважды - app_dev и app_prod - с разными значениями. В корне сделай output, который читает module.app_prod.<твой_output>.
  • Прогони terraform plan apply (или tofu plan apply) и убедись, что dev и prod различаются по числу и размеру машин, а ресурсы разведены по адресам module.app_dev.* и module.app_prod.*.
Контрольные вопросы
  • Чем variable отличается от locals и в каком случае нужно заводить именно local, а не лишний input?
  • Что произойдёт при terraform plan apply, если у variable нет default и значение не передали при вызове модуля?
  • Как из корневого кода прочитать значение, созданное внутри модуля? Через какую конструкцию?
  • Почему опасно ставить default на переменную env_name и для каких параметров дефолты, наоборот, уместны?
Итог

variable - вход, locals - внутренняя кухня, output - выход. Этой троицей модуль превращается из бетонной плиты в настраиваемый инструмент. Входы делай минимальными и снабжай дефолтами всё, кроме осознанного выбора. Вычисления и константы прячь в locals, чтобы не раздувать интерфейс. Через output отдавай наружу id и адреса - именно они склеивают модули в цепочку и формируют граф зависимостей. Один код модуля, разные значения при вызове - и вот у тебя dev и prod из одного источника. Это и есть смысл модулей terraform yandex cloud в реальной работе.
👍3 ❤️ 🔥1 😄 🤔2
Аватара пользователя
bash69
Сообщения: 1
Зарегистрирован: 23 май 2026, 06:18

Re: Входные, локальные и выходные переменные модуля

Сообщение bash69 »

Спасибо, наконец дошло чем local от variable отличается. Я как раз плодил input под каждое склеенное имя, теперь переделаю на locals.
👍3 ❤️1 🔥 😄 🤔
Аватара пользователя
dryda5
Сообщения: 1
Зарегистрирован: 12 май 2026, 21:44

Re: Входные, локальные и выходные переменные модуля

Сообщение dryda5 »

А validation внутри variable работает в OpenTofu так же? И можно ли в condition ссылаться на другую переменную, или только на саму себя?
👍 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Модули Terraform: переиспользуем инфраструктуру
Следующая глава →
Версионирование модулей и подводные камни

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

Поделиться темой: ✈ Telegram VK
Похожие запросы: как вынести terraform в модуль и переиспользовать инфраструктурувходные и выходные переменные модуля terraform как передатьansible inventory и hosts файлкак создать роль ansible с нуля и структура каталогов

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

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

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