Источники и версионирование модулей
Модуль можно подключить из локальной папки, git-репозитория или Terraform Registry. От источника зависит, как фиксировать версию — и от этого напрямую зависит воспроизводимость деплоя.
Незакреплённая версия модуля — это бомба. Сегодня plan чистый, завтра автор выпустил новую версию, и ваш prod внезапно хочет пересоздать половину сети. Пиньте версии в проде всегда.
Аргумент source говорит Terraform, откуда взять модуль. Варианты: локальный путь (./modules/x), git (git::https://...), Terraform Registry (org/name/provider) и другие. Способ закрепления версии зависит от источника.
Источники и пины версий
# локальный -- версии нет, берётся as-is
module "net" { source = "./modules/network" }
# Registry -- версия через отдельный аргумент version
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.5" # pessimistic: >=5.5.0, <6.0.0
}
# Git -- версия В САМОМ URL через ?ref=тег
module "app" {
source = "git::https://github.com/org/mod.git?ref=v1.4.0"
}
Ключевая тонкость: аргумент version работает только с Registry. Для git и других источников версия фиксируется внутри source (через ?ref=). Операторы ограничений: = 1.2.0 (точно), ~> 5.5 (любой 5.x от 5.5), >= 2.0 (диапазон).
Как работает под капотом
Terraform берёт доступные версии модуля и выбирает максимальную, удовлетворяющую ограничению. Смоделируем разрешение версии под оператор ~>:
def parse(v):
return tuple(int(x) for x in v.split("."))
available = ["5.4.0", "5.5.0", "5.5.3", "5.8.1", "6.0.0", "6.1.0"]
def resolve_pessimistic(available, base):
# ~> 5.5 означает >=5.5.0 и < 6.0.0
lo = parse(base)
upper_major = lo[0] + 1
ok = [v for v in available
if parse(v) >= lo and parse(v)[0] < upper_major]
return max(ok, key=parse) if ok else None
print("~> 5.5 выберет:", resolve_pessimistic(available, "5.5"))
print("~> 6.0 выберет:", resolve_pessimistic(available, "6.0"))
# что НЕ подойдёт под ~> 5.5
excluded = [v for v in available if parse(v)[0] >= 6]
print("исключены 6.x:", excluded)
«Попробуй сам ▶» — ~> 5.5 берёт свежайший 5.x (5.8.1), но не пускает 6.0.0: мажорный апгрейд может ломать совместимость.
Частые ошибки
- version у git-модуля. Аргумент
versionдля git игнорируется — версия только через?ref=. - Незакреплённая ветка.
?ref=mainв проде — плавающая цель, plan меняется между запусками. - Слишком широкий диапазон в проде.
>= 5.0однажды притянет ломающее обновление.
Best practices
- В проде — точные пины (
= 1.4.0или?ref=v1.4.0), апгрейд осознанный и после тестов. - В dev можно
~>для управляемой гибкости — ловить минорные обновления заранее. - Для приватных модулей используйте private registry или git с тегами; для публичных — Terraform Registry с семвером.
Разбор глубже
Полезно понимать, как Terraform физически достаёт модуль из каждого источника. Для локального пути он просто читает файлы с диска — поэтому изменения в локальном модуле видны сразу, без переинициализации. Для git и Registry модуль скачивается при terraform init в папку .terraform/modules/ и дальше используется оттуда. Это значит, что после правки удалённого модуля или смены пина версии нужно заново выполнить terraform init (иногда с флагом -upgrade), иначе Terraform продолжит работать со старой скачанной копией.
Семантическое версионирование — это контракт между автором и потребителем модуля. Patch-версия (1.2.3) обещает только исправления багов без изменения поведения. Minor (1.3.0) добавляет новые возможности обратносовместимо. Major (2.0.0) предупреждает: что-то ломается, читайте changelog. Поэтому ограничение ~> 1.2 безопасно пускает патчи и миноры, но защищает от мажорного апгрейда, который может потребовать правок. В проде версии пинят жёстко и поднимают осознанно после тестирования, а changelog модуля становится обязательным чтением перед апгрейдом.
Полезно завести командное соглашение о том, откуда вообще берутся модули. Распространённый зрелый подход — внутренний (private) реестр или выделенный git-репозиторий с модулями, где каждый релиз помечен тегом по семверу. Потребители подключают модули по тегам, а не по ветке main, что делает каждый деплой воспроизводимым и защищённым от случайных правок «в апстриме». Публичные модули из Registry удобны для старта и типовых задач, но в серьёзных проектах их часто оборачивают в собственные модули-фасады, чтобы зафиксировать версию, добавить корпоративные политики и не зависеть напрямую от внешнего сопровождающего.
Итог: источник определяет способ пина: Registry — через version, git — через ?ref=. В проде пиньте жёстко ради воспроизводимости. Дальше — как из модулей собирать многослойную архитектуру.