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 у вас нет внутреннего напряжения — значит, вы всё сделали правильно.
Хотите узнать больше? Изучите другие статьи из раздела: