Статьи

Terraform без боли: модули, state и здравый смысл

Terraform часто начинают использовать как простой инструмент: один main.tf, пара ресурсов, terraform apply — и инфраструктура готова. Но как только проект переживает первый рост, появляется команда, окружения и требования к стабильности, становится ясно: Terraform либо дисциплинируют, либо он начинает мстить.

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

Почему «просто Terraform» перестаёт работать

Terraform отлично масштабируется технически, но плохо прощает архитектурные ошибки. Основные проблемы обычно возникают не из-за самого инструмента, а из-за того, как именно его используют:

• State хранится локально и случайно затирается
• Модули становятся монолитами «на все случаи жизни»
• Переменные растут как снежный ком
• Любое изменение приводит к неожиданному destroy

Все эти симптомы говорят об одном: Terraform используется как набор ресурсов, а не как язык описания инфраструктуры.

Модули — это интерфейсы, а не папки с кодом

Самая частая ошибка — делать модуль как «копипасту ресурсов». Хороший Terraform-модуль ближе по духу к библиотеке, чем к конфигурационному файлу.

Что отличает хороший модуль:

• Он решает одну инфраструктурную задачу
• Имеет минимальный и понятный публичный интерфейс
• Не знает ничего о конкретном окружении
• Не требует читать код, чтобы понять, как его использовать

Плохой модуль — это когда без variables.tf на 200 строк и README ничего не работает.

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

Один из самых полезных вопросов, который можно себе задать:

“Буду ли я использовать этот модуль в другом проекте?”

Если ответ «нет» — скорее всего, это не переиспользуемый модуль, а часть конкретной конфигурации, и это нормально.

Практичный подход к структуре

Обычно хорошо работает такая иерархия:

root-модуль — связывает всё вместе, знает про окружение
domain-модули — VPC, Kubernetes, databases, IAM
low-level модули — один ресурс или тесно связанная группа

Например:

VPC — отдельный модуль
EKS — отдельный модуль
Ingress Controller — отдельный модуль, а не часть EKS

Переменные: меньше — лучше

Terraform не ограничивает количество переменных, но это ловушка. Чем больше переменных у модуля, тем сложнее им пользоваться и тестировать.

Несколько рабочих принципов

• Переменная должна влиять на поведение, а не на стиль
• Если значение почти всегда одинаковое — это local, а не variable
• boolean-флаги — источник неявной логики, используйте осторожно

Плохой пример:
Хороший пример:
Такой подход хорош, когда набор фич расширяем и используется в for_each или local.
Для сложной конфигурации лучше использовать object с явной структурой.

Outputs — это контракт между модулями

output — это не «на всякий случай». Это публичный API модуля.
Если вы экспортируете всё подряд, вы ломаете инкапсуляцию, усложняете refactoring, привязываете потребителей к внутренней реализации.

Что стоит выносить в output

• Идентификаторы ресурсов, которые реально используются снаружи
• Значения, которые нужны другим модулям
• Ничего «про запас»

State — главный актив Terraform

State — это не техническая деталь, а единственный источник правды о вашей инфраструктуре. Потеряли state — потеряли контроль.

Базовые правила управления state

• Никакого локального state в командной работе
• Только remote backend
• versioning включён всегда
• Блокировка state обязательна
• state всегда зашифрован

Для большинства команд стандартом становится S3 + DynamoDB или Terraform Cloud. Не из-за моды, а из-за блокировок и истории изменений.

Один state — одно окружение

Попытки хранить dev, staging и prod в одном state почти всегда заканчиваются плохо. Разделение окружений — это не удобство, а безопасность.

Практический вариант

• Отдельный backend на окружение
• Одинаковые модули
• Разные значения переменных

Так вы можете:

• Безопасно применять изменения
• Тестировать миграции
• Не бояться случайного destroy

Terraform plan — не формальность

В зрелых командах terraform plan читают так же внимательно, как pull request. Это единственный момент, когда Terraform честно говорит, что он собирается сделать.

Полезная практика — сохранять terraform plan как артефакт и применять ровно его, а не пересобранную версию. Это убирает целый класс ошибок, когда «между plan и apply что-то поменялось»: переменные, версии модулей, состояние окружения или даже сам код. Особенно это критично в CI/CD пайплайнах и сценариях с ручным апрувом, где между этапами может пройти время и apply выполняет уже другой контекст, чем тот, под который был рассчитан plan.

Изменения без сюрпризов

Terraform не любит имплицитность.

Что снижает риск неожиданных изменений:

• Явные depends_on, когда есть неочевидные зависимости
• Минимальное использование count с логикой
• Аккуратная работа с for_each и ключами
• Осознанное использование lifecycle

Особенно опасно менять ключи в for_each: для Terraform это другой ресурс, а не «обновление».

Версионирование модулей обязательно

Использовать модуль без версии — всё равно что подключать библиотеку без фиксации версии.

Даже если модули лежат в одном репозитории, версия — это сигнал:

• Что изменилось
• Можно ли обновляться
• Чего ожидать от apply

Документация — не формальность

Если модуль нельзя понять за 5 минут по README, его не будут использовать правильно.

Минимум, который должен быть:

• Назначение модуля
• Пример использования
• Описание переменных
• Что он не делает

Terraform — это про предсказуемость

Хороший Terraform-код скучный. Он не удивляет, не ломается неожиданно и не требует шаманства. Модули похожи друг на друга, state хранится надёжно, а изменения происходят контролируемо.
Если после terraform apply у вас нет внутреннего напряжения — значит, вы всё сделали правильно.


Хотите узнать больше? Изучите другие статьи из раздела:
2026-01-28 11:00 DevOps