Модули Terraform: как структурировать переиспользуемую инфраструктуру
Практика3 минРедакция Innovialabs
Коротко
Хороший модуль Terraform описывает одну цельную часть инфраструктуры через небольшой набор типизированных входов и полезных выходов, версионируется и закрепляется, а вызывается из тонких конфигураций окружений с раздельным state.
Коротко
Модуль в Terraform — это просто папка с файлами .tf. Любая конфигурация уже корневой модуль, а переиспользуемые модули — это папки, которые вызываются блоком module.
Хороший модуль:
- описывает одну цельную вещь: сеть, базу данных с бэкапами, сервис с балансировщиком;
- принимает несколько типизированных входов с разумными значениями по умолчанию;
- возвращает outputs, которые реально нужны другим частям стека;
- версионируется, чтобы окружения обновлялись осознанно, а не случайно.
Окружения (dev, staging, prod) — это тонкие корневые конфигурации, которые вызывают одни и те же модули с разными значениями и хранят раздельный state.
Входы, выходы и контракт модуля
# modules/postgres/variables.tf
variable "name" {
type = string
description = "Instance name prefix"
}
variable "instance_size" {
type = string
default = "small"
}
variable "backup_retention_days" {
type = number
default = 7
validation {
condition = var.backup_retention_days >= 1
error_message = "Retention must be at least one day."
}
}
# modules/postgres/outputs.tf
output "endpoint" {
value = aws_db_instance.this.address
}
output "password_secret_arn" {
value = aws_secretsmanager_secret.db.arn
sensitive = true
}
Относитесь к переменным и выходам как к публичному API:
- Указывайте
typeиdescription; добавляйтеvalidationдля значений, в которых легко ошибиться. - Объединяйте поля в одну object-переменную, только если они действительно связаны.
- Помечайте секреты
sensitive = trueи помните, что они всё равно попадают в state, поэтому защищайте бэкенд state. - Не объявляйте блоки
providerвнутри переиспользуемых модулей — пусть провайдеры передаёт вызывающая сторона.
Версионирование
При вызове модуля из Git-репозитория или реестра версию можно закрепить:
module "db" {
source = "git::https://github.com/acme/terraform-modules.git//postgres?ref=v1.3.0"
name = "shop-prod"
instance_size = "large"
backup_retention_days = 14
}
- Помечайте релизы семантическими версиями: ломающие изменения входов или адресов ресурсов — новая мажорная версия.
- Обновляйте сначала dev, потом staging, потом prod, каждый раз читая
terraform plan. - При переименовании ресурсов внутри модуля добавляйте блоки
moved, чтобы Terraform обновил адреса, а не пересоздал ресурсы. - Закрепляйте версии провайдеров в
required_providersи коммитьте lock-файл.
Разумная структура репозитория
infra/
modules/
network/
postgres/
app-service/
envs/
dev/
main.tf
backend.tf
terraform.tfvars
staging/
prod/
- Каждая папка в
envs/— отдельный корень со своим state и настройками бэкенда. Ошибка в dev не заденет state продакшена. - Различия между окружениями живут в переменных, а не в скопированных ресурсах.
- Большие окружения делите на несколько стеков (сеть, данные, приложения), когда план становится медленным или рискованным. Значения между ними передавайте через outputs и data sources.
- Workspaces подходят для почти одинаковых окружений, но отдельные папки нагляднее показывают различия и упрощают разграничение доступа.
Как безопасно использовать модули из реестра
Публичные модули экономят время, но вы запускаете чужой код со своими облачными доступами.
- Выбирайте модули проверенных или активно поддерживаемых авторов с живыми issues и релизами.
- Всегда закрепляйте точную версию, не полагайтесь на последнюю.
- Читайте исходники и план: какие ресурсы и IAM-права создаёт модуль?
- Проверьте значения по умолчанию, особенно шифрование, публичный доступ и логирование.
- Для критичной инфраструктуры подумайте о форке или зеркале модуля в своей организации.
Антипаттерны
- Переабстрагированные модули: «универсальный» модуль с десятками флагов, который умеет создать что угодно. Его сложнее читать, чем обычные ресурсы, а любое изменение рискует задеть всех пользователей.
- Модули-обёртки, которые лишь переименовывают аргументы одного ресурса и ничего не добавляют.
- Глубокая вложенность: модули, вызывающие модули на несколько уровней вниз, делают план непонятным.
- Скопированные окружения, которые со временем расходятся.
- Захардкоженные значения вроде региона, ID аккаунтов или CIDR внутри переиспользуемого модуля.
- Один гигантский state на всё, так что каждый apply затрагивает всю компанию.
Хорошее правило: пишите модуль, когда у него есть минимум два реальных применения или когда он закрепляет стандарт, который нужно соблюдать везде одинаково.