Мелкие компонуемые модули вместо монолита

Рейтинг: 71.7% · 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 и карьера
Один модуль на все - и почему это болит

Представь типичную картину. Команда начинает с Terraform, пишет инфраструктуру как код, и вся прод-среда живет в одном модуле. Сеть, кластер Kubernetes, база данных, балансировщик, мониторинг, бакеты, права доступа - все свалено в одну папку. Поначалу удобно: один terraform apply и готово. А потом ты хочешь поднять идентичный стенд для тестов и обнаруживаешь, что переиспользовать нечего - все ресурсы жестко склеены друг с другом.

Боль начинается раньше, чем кажется. Когда в одном стейте сотни ресурсов, каждый terraform plan читает их все, опрашивает API облака по каждому, и ты ждешь минуты вместо секунд. А самое неприятное - blast radius, радиус взрыва. Тебе нужно поменять одну метку у бакета, а в плане висят изменения по сети и кластеру, потому что они в том же стейте. Один кривой apply - и ты роняешь не бакет, а полпрода. В IaC цена ошибки измеряется не строчкой кода, а живыми сервисами.

Монолитный модуль нарушает базовый инженерный принцип: одна сущность - одна ответственность. Так же как ты не пишешь функцию на тысячу строк, не стоит держать модуль terraform, который отвечает за все сразу. Философия мелких модулей ровно про это: дробим инфраструктуру на маленькие куски, у каждого своя зона ответственности, а собираем их как кубики через входы и выходы.

Изображение

Как устроена декомпозиция: одна ответственность на модуль

Идея простая. Каждый модуль terraform отвечает за один логический слой инфраструктуры и ничего не знает о соседях напрямую. Типичная нарезка для прод-сервиса:
  • network - VPC, подсети, шлюзы, таблицы маршрутизации. Меняется редко, ломать страшно.
  • cluster - управляемый Kubernetes или группа ВМ. Меняется чаще: версии, размеры нод.
  • database - управляемая БД, бэкапы, параметры. Меняется редко, данные критичны.
  • monitoring - дашборды, алерты, экспортеры. Меняется постоянно и почти безопасно.
Связь между ними идет не через прямые ссылки, а через контракт: модуль публикует то, что нужно соседям, в outputs, а потребитель принимает это во input-переменные. Сеть отдает наружу свой id и id подсетей. Кластеру они нужны, чтобы знать, где разворачиваться. Кластер отдает свой endpoint, мониторинг его потребляет. Получается граф зависимостей, где каждый узел знает только про свои входы и выходы, а не про потроха соседа.

Два главных критерия, по которым проводишь границу между модулями:
  • По частоте изменений. То, что трогаешь раз в полгода (сеть, БД), не должно жить вместе с тем, что меняешь каждую неделю (мониторинг, версии приложения). Иначе частые правки таскают за собой риск задеть стабильное.
  • По blast radius. Чем больнее уронить ресурс, тем сильнее его надо изолировать. Сеть и база - в отдельных модулях и часто в отдельных стейтах. Дашборд можно пересоздавать хоть десять раз в день.
Практика: компонуем модули через outputs и inputs

Покажу на Yandex Cloud. Сначала маленький модуль сети. Его задача - создать сеть и подсеть и отдать наружу их идентификаторы.

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

# modules/network/main.tf
variable "zone" {
  type    = string
  default = "ru-central1-a"
}

resource "yandex_vpc_network" "this" {
  name = "app-net"
}

resource "yandex_vpc_subnet" "this" {
  name           = "app-subnet"
  zone           = var.zone
  network_id     = yandex_vpc_network.this.id
  v4_cidr_blocks = ["10.10.0.0/24"]
}

output "network_id" {
  value = yandex_vpc_network.this.id
}

output "subnet_id" {
  value = yandex_vpc_subnet.this.id
}
Заметь: модуль ничего не знает про кластер или базу. Он просто создает сеть и честно объявляет в output то, что понадобится другим. Это и есть его публичный контракт.

Теперь модуль кластера. Он принимает id сети и подсети как input-переменные - то есть зависит от выходов network, но не от его внутренностей.

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

# modules/cluster/variables.tf
variable "network_id" { type = string }
variable "subnet_id"  { type = string }
variable "zone" {
  type    = string
  default = "ru-central1-a"
}

# modules/cluster/main.tf
resource "yandex_kubernetes_cluster" "this" {
  name       = "app-k8s"
  network_id = var.network_id

  master {
    version = "1.29"
    zonal {
      zone      = var.zone
      subnet_id = var.subnet_id
    }
    public_ip = true
  }

  service_account_id      = var.service_account_id
  node_service_account_id = var.service_account_id
}

variable "service_account_id" { type = string }

output "cluster_id" {
  value = yandex_kubernetes_cluster.this.id
}

output "cluster_endpoint" {
  value = yandex_kubernetes_cluster.this.master[0].external_v4_endpoint
}
И корневой модуль, который склеивает кубики. Вот тут видно всю красоту: выход одного модуля становится входом другого, а Terraform сам выстраивает правильный порядок применения по графу зависимостей.

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

# main.tf (корень)
module "network" {
  source = "./modules/network"
  zone   = "ru-central1-a"
}

module "cluster" {
  source             = "./modules/cluster"
  network_id         = module.network.network_id
  subnet_id          = module.network.subnet_id
  service_account_id = yandex_iam_service_account.k8s.id
}

module "database" {
  source     = "./modules/database"
  network_id = module.network.network_id
  subnet_id  = module.network.subnet_id
}
Сеть создается первой не потому, что ты так написал по порядку, а потому что cluster и database ссылаются на module.network.subnet_id. Terraform читает эти ссылки, строит ориентированный граф и выполняет узлы в нужной последовательности. Ты описываешь связи декларативно - инструмент сам разбирается, что за чем.

Тот же подход работает один в один в OpenTofu - это форк, синтаксис HCL и логика модулей идентичны, можно просто заменить бинарь terraform на tofu.

Типичные грабли
  • Дробление ради дробления. Модуль на один ресурс - это тоже антипаттерн. Если у тебя модуль из одной строки yandex_vpc_network, ты получил обертку без пользы и лишний слой переменных. Модуль должен инкапсулировать осмысленный кусок, а не каждую железку.
  • Скрытые связи через data вместо outputs. Соблазн велик: в модуле кластера сделать data-источник и найти сеть по имени. Не делай так. Это прячет зависимость от графа, Terraform не понимает порядок, и ты ловишь гонки при первом apply. Связь должна идти через явный input.
  • Слишком много стейтов. Маятник качается в обе стороны. Если каждый модуль вынести в отдельный стейт, ты больше не передаешь outputs напрямую - приходится тянуть их через remote state или data-источники, и сборка усложняется. Дроби стейты по blast radius, а не по числу модулей.
  • Протекающая абстракция в переменных. Если модуль принимает на вход двадцать переменных, его контракт раздут и им неудобно пользоваться. Прячь детали внутри, наружу выноси только то, без чего вызвать модуль нельзя.
Мини-лаба

Возьми любой свой монолитный конфиг (или собери учебный) и проделай руками:
  • Раздели его минимум на два модуля - network и cluster - в папке modules/.
  • В модуле network объяви outputs network_id и subnet_id.
  • В модуле cluster объяви соответствующие input-переменные и используй их.
  • В корне свяжи их: передай module.network.subnet_id в module "cluster".
  • Запусти terraform plan apply и посмотри в выводе порядок создания - сеть пойдет раньше кластера.
  • Теперь измени что-нибудь только в модуле cluster и снова сделай plan. Убедись, что ресурсы network в плане не дергаются. Это и есть уменьшенный blast radius в действии.
Контрольные вопросы
  • Какие два критерия используют, чтобы провести границу между модулями?
  • Через что модули обмениваются данными и почему нельзя связывать их через data-источник по имени?
  • Почему гигантский монолитный модуль замедляет terraform plan?
  • В чем антипаттерн модуля, оборачивающего ровно один ресурс?
Итог: что запомнить

Мелкие компонуемые модули - это не мода, а способ снизить риск и ускорить работу. Один модуль - одна ответственность. Граница проходит по частоте изменений и по blast radius: редкое и опасное (сеть, БД) отделяй от частого и безопасного (мониторинг). Связывай кубики через outputs и inputs - явный контракт, который Terraform превращает в граф зависимостей и применяет в правильном порядке. Не впадай в крайности: ни монолит на все, ни модуль на каждую строчку. Хороший модуль - тот, который не страшно поменять и приятно переиспользовать.
👍2 ❤️ 🔥1 😄 🤔1
Аватара пользователя
lilj00
Сообщения: 1
Зарегистрирован: 12 май 2026, 18:38

Re: Мелкие компонуемые модули вместо монолита

Сообщение lilj00 »

Спасибо, наконец дошло зачем вообще дробить. У меня как раз все в одном стейте и plan тянется по минуте, теперь понятно куда копать.
👍 ❤️ 🔥2 😄 🤔
Аватара пользователя
py_ninja
Сообщения: 1
Зарегистрирован: 02 июн 2026, 07:51

Re: Мелкие компонуемые модули вместо монолита

Сообщение py_ninja »

А подскажите, если я выношу network в отдельный стейт, то outputs уже через remote state тянуть? Или лучше пока в одном стейте оставить и не усложнять?
👍1 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
Код Terraform промышленного уровня: почему это долго
Следующая глава →
Тестируемые модули и проверки: validation, precondition, postcondition

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

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

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

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

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