Модули Terraform: переиспользуем инфраструктуру

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

Модули Terraform: переиспользуем инфраструктуру

Сообщение 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 и карьера
В уроках 8-9 мы собрали полноценный веб-кластер в Yandex Cloud: группа виртуалок (yandex_compute_instance_group) плюс Application Load Balancer перед ней. Работает, красиво, гордимся собой. А теперь представь, что тебе нужно поднять ровно такой же кластер для окружения dev, чтобы тестировщики не ломали прод. Что обычно делают? Копируют папку целиком, меняют пару имён и идут пить кофе.

Через месяц у тебя два каталога, которые почти одинаковые, но не совсем. В проде кто-то поправил healthcheck, в dev забыл. Где-то размер диска 4 ГБ, где-то 10, и никто уже не помнит почему. Любое изменение надо вносить дважды, а синхронность держится на честном слове и твоей памяти. Это и есть копипаста инфраструктуры, и лечится она ровно так же, как копипаста в коде. Функцией. В мире Terraform такая функция называется модуль, и эта тема - водораздел между "я написал terraform" и "я умею в инфраструктуру как код".

Что такое модуль на самом деле

Готовься к разочарованию: модуль в Terraform - это просто папка с файлами .tf. Всё. Никакой магии, никакого специального синтаксиса для объявления "вот тут начинается модуль". Любой каталог, в котором лежит хотя бы один .tf, уже является модулем.

Из этого следует важная штука. Когда ты запускаешь terraform apply в какой-то папке, эта папка становится корневым модулем (root module). Это просто роль, а не отдельная сущность. А вот когда внутри своего кода ты подключаешь чужую папку через блок module, она становится дочерним модулем (child module) по отношению к твоему корневому.

То есть всё это время, с самого первого урока, ты уже писал модули. Просто корневые, в единственном экземпляре. Сегодня мы научимся вызывать их повторно, как обычную функцию. Кстати, ровно по этой же механике подключаются готовые модули из реестра - и в registry.terraform.io, и в реестре OpenTofu. Снаружи они выглядят так же: папка с .tf, у которой есть входы и выходы.

Изображение

Выносим веб-кластер в отдельный модуль

Возьмём наш кластер из урока 8-9 и переложим его так, чтобы можно было вызывать. Договоримся о структуре каталогов проекта:

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

infra/
  modules/
    webserver-cluster/
      main.tf         # сами ресурсы: instance group, ALB, target group
      variables.tf    # входные параметры модуля
      outputs.tf      # что модуль отдаёт наружу
  live/
    prod/
      main.tf         # вызываем модуль для прода
    dev/
      main.tf         # вызываем тот же модуль для dev
Папка modules/webserver-cluster - это библиотека. Сама по себе она ничего не разворачивает, terraform apply ты в ней не запускаешь. Это заготовка, шаблон кластера. А папки live/prod и live/dev - это корневые модули, которые этот шаблон вызывают с разными параметрами.

Внутри modules/webserver-cluster/main.tf лежит наш знакомый кластер. Я приведу его в урезанном виде, чтобы был виден смысл, а не двести строк:

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

resource "yandex_compute_instance_group" "web" {
  name               = "web-cluster"
  folder_id          = var.folder_id
  service_account_id = var.service_account_id

  instance_template {
    platform_id = "standard-v1"

    resources {
      cores  = 2
      memory = 2
    }

    boot_disk {
      initialize_params {
        image_id = var.image_id
        size     = 4
      }
    }

    network_interface {
      network_id = var.network_id
      subnet_ids = var.subnet_ids
    }
  }

  scale_policy {
    fixed_scale {
      size = var.cluster_size
    }
  }

  allocation_policy {
    zones = var.zones
  }

  deploy_policy {
    max_unavailable = 1
    max_creating    = 2
  }
}
Обрати внимание: ничего не захардкожено. Размер кластера, образ, сеть, фолдер - всё пришло через var. Это и делает папку переиспользуемым модулем, а не копией конкретного прода.

Входы объявляем в variables.tf (детально про переменные - в следующем уроке, тут по верхам):

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

variable "cluster_size" {
  description = "Сколько ВМ в группе"
  type        = number
}

variable "image_id" {
  description = "ID образа для загрузочного диска"
  type        = string
}

variable "folder_id" {
  type = string
}

variable "service_account_id" {
  type = string
}

variable "network_id" {
  type = string
}

variable "subnet_ids" {
  type = list(string)
}

variable "zones" {
  type = list(string)
}
А наружу через outputs.tf отдаём то, что снаружи может понадобиться - например, id группы:

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

output "instance_group_id" {
  value       = yandex_compute_instance_group.web.id
  description = "ID группы экземпляров кластера"
}
Вызываем модуль из dev и prod

Теперь самое вкусное. В live/prod/main.tf вызываем нашу заготовку:

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

module "frontend" {
  source = "../../modules/webserver-cluster"

  cluster_size       = 4
  image_id           = "fd8..."
  folder_id          = var.folder_id
  service_account_id = var.service_account_id
  network_id         = yandex_vpc_network.this.id
  subnet_ids         = [yandex_vpc_subnet.this.id]
  zones              = ["ru-central1-a", "ru-central1-b"]
}
Разберём по косточкам. Ключевое слово module, дальше его локальное имя - frontend. Это имя ты сам придумываешь, по нему потом обращаешься к выходам: module.frontend.instance_group_id. Параметр source говорит, где взять код модуля. Здесь это локальный путь ../../modules/webserver-cluster - относительно текущей папки. Source бывает и удалённым (git, реестр), но локальные пути - первое, с чего стоит начинать в своём проекте.

В live/dev/main.tf вызов почти такой же, только cluster_size = 1 (на тестинге незачем держать четыре машины), да фолдер другой. Один и тот же код, два окружения, нулевая копипаста. Поправил healthcheck в модуле - он автоматически поправился и в dev, и в prod. Вот ради этого всё и затевалось.

После любого изменения source или добавления нового блока module нужно скормить Terraform команду:

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

terraform init
terraform plan
terraform init подтягивает код дочерних модулей в служебную папку .terraform (для локальных путей это, по сути, регистрация ссылки). Без init модуль просто не подключится, и ты получишь ошибку "module not installed". А terraform plan уже покажет, что именно создастся - и заметь, имена ресурсов в плане будут с префиксом module.frontend.

Типичные грабли
  • Забыл terraform init после добавления модуля. Добавил блок module, сразу побежал делать plan и получил "Module not installed". init обязателен после любой возни с модулями и их source, а не только при первом запуске.
  • Хардкод внутри модуля. Самая частая беда. Если в modules/webserver-cluster прописать конкретный folder_id, имя "web-cluster-prod" или захардкодить зону - модуль перестаёт быть переиспользуемым. Хуже того, при вызове из двух окружений ресурсы подерутся за одинаковые имена. Всё, что отличается между dev и prod, обязано приходить через variable.
  • Конфликт имён ресурсов. Если внутри модуля имя ресурса жёстко задано строкой, два вызова модуля попытаются создать два объекта с одинаковым именем в облаке - и второй apply упадёт. Лечится тем же: подмешивай в имя var.env_name или подобное.
  • Путаница с путём source. Путь относительный к папке, где лежит вызывающий .tf, а не к той, откуда ты запустил terraform. ../../modules/... - считай уровни вложенности аккуратно.
  • Меняешь модуль и удивляешься, почему dev не обновился. Модуль - общий код, но state у каждого окружения свой. Правка в коде модуля применится к окружению только после apply именно в его папке. Никакой телепатии.
Мини-лаба

Руками, без копипасты из урока:
  • Создай структуру modules/webserver-cluster и live/dev, live/prod.
  • Перенеси в модуль группу экземпляров из урока 8 так, чтобы cluster_size, image_id, folder_id, network_id и subnet_ids приходили через variable.
  • Добавь output с id группы.
  • Вызови модуль из live/dev с размером 1 и из live/prod с размером 3.
  • Сделай terraform init и terraform plan в каждой папке. Убедись, что в плане ресурсы идут под именем module.frontend.
  • Поменяй memory в модуле с 2 на 4 и убедись, что plan видит изменение в обоих окружениях после apply.
Контрольные вопросы
  • Что технически делает папку модулем в Terraform и чем корневой модуль отличается от дочернего?
  • Зачем выносить веб-кластер в modules/webserver-cluster, если можно просто скопировать папку для dev?
  • За что отвечают три файла модуля - main.tf, variables.tf, outputs.tf?
  • Почему нельзя хардкодить имена ресурсов и folder_id внутри переиспользуемого модуля, и что будет при двух вызовах?
Что запомнить

Модуль - это просто папка с .tf, ставшая функцией для инфраструктуры. Корневым модулем папка становится, когда ты запускаешь в ней apply, дочерним - когда её подключают через block module с параметром source. Всё переменное (размеры, образы, сети, имена) выносим во входные variable, нужное наружу отдаём через output, а внутри модуля - ноль хардкода. После добавления модуля всегда terraform init, затем terraform plan. Так инфраструктура как код перестаёт быть копипастой и становится библиотекой, которую переиспользуешь между dev, stage и prod без боли. В следующем уроке докрутим входные, локальные и выходные переменные, чтобы модуль стал по-настоящему настраиваемым.
👍7 ❤️4 🔥 😄 🤔1
Аватара пользователя
nikson_s_d
Сообщения: 1
Зарегистрирован: 25 май 2026, 11:45

Re: Модули Terraform: переиспользуем инфраструктуру

Сообщение nikson_s_d »

А terraform init нужно делать в каждой папке live/ отдельно или один раз сверху хватает? У меня после добавления module ругался Module not installed пока не сделал init именно в live/dev. Видимо state у них реально раздельный, теперь дошло.
👍1 ❤️ 🔥 😄 🤔
Аватара пользователя
striker300
Сообщения: 1
Зарегистрирован: 28 май 2026, 02:26

Re: Модули Terraform: переиспользуем инфраструктуру

Сообщение striker300 »

Спасибо, наконец понял что модуль это просто папка, я думал там какой то особый синтаксис. Вопрос - а source можно сразу на гитхаб указать или сначала лучше локальными путями поиграться?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Источники данных и terraform_remote_state: связываем компоненты
Следующая глава →
Входные, локальные и выходные переменные модуля

Все главы курса «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 гость