1. Декларативная модель и жизненный цикл ресурсов в Terraform
Декларативная модель и жизненный цикл ресурсов в Terraform
Скрипт развертывания ста серверов падает на пятьдесят седьмом из-за сетевого таймаута. Ошибка исправлена, но запускать скрипт заново страшно: он попытается заново создать первые пятьдесят шесть машин, что приведет к конфликтам имен, ошибкам API и непредсказуемому состоянию системы. Инженеру приходится вручную комментировать строки кода или писать сложную логику проверок if exists. Эта ситуация — классический тупик императивного подхода к инфраструктуре, который неизбежно возникает при масштабировании.
Решение этой проблемы лежит в смене парадигмы: переходе от описания шагов к описанию финального результата.
Императивный и декларативный подходы
Разница между императивной и декларативной моделями управления инфраструктурой заключается в том, где находится фокус контроля сложности.
Императивный подход отвечает на вопрос «Как сделать?». Инженер пишет точные инструкции: вызови API, создай сеть, подожди 10 секунд, если успешно — создай виртуальную машину, если нет — выведи ошибку. Вся логика обработки состояний, зависимостей и ошибок ложится на плечи автора кода. Bash-скрипты, Ansible (в режиме ad-hoc команд) или скрипты на Python с использованием SDK облачного провайдера — это императивные инструменты.
Пример императивной логики (псевдокод):
Декларативный подход отвечает на вопрос «Что должно получиться?». Инженер описывает желаемое конечное состояние системы (Desired State). Инструмент сам вычисляет текущее состояние (Actual State), сравнивает их и генерирует минимально необходимый набор императивных команд для приведения реальности к желаемому виду.
Пример декларативной логики на HashiCorp Configuration Language (HCL):
В этом коде нет проверок на существование сети. Если сеть уже есть, Terraform ничего не сделает. Если её нет — создаст. Если она есть, но называется иначе — переименует (или пересоздаст, в зависимости от возможностей API провайдера). Сложность вычисления дельты между «как есть» и «как надо» делегируется ядру Terraform.
Идемпотентность как следствие декларативности
Декларативная модель неразрывно связана со свойством идемпотентности. Операция является идемпотентной, если многократное её применение дает тот же результат, что и однократное, без возникновения побочных эффектов.
В Terraform выполнение команды применения конфигурации сто раз подряд на неизменной инфраструктуре приведет к изменениям только при первом запуске. Остальные девяносто девять раз Terraform сообщит, что инфраструктура уже соответствует описанному коду. Это радикально снижает страх перед повторным запуском пайплайнов развертывания после сбоев.
Математически логику работы декларативного движка можно выразить так:
Где — это набор конкретных действий (Create, Update, Delete), необходимых для устранения разницы.
Жизненный цикл Terraform-конфигурации
Процесс приведения инфраструктуры к желаемому состоянию строго регламентирован и разбит на фазы. Понимание того, что происходит под капотом на каждом этапе, критически важно для безопасной работы в production-средах.
1. Инициализация (terraform init)
Код Terraform сам по себе не умеет создавать ресурсы. HCL — это просто язык разметки. Взаимодействие с реальными облаками осуществляют плагины — провайдеры (Providers).На этапе инициализации Terraform анализирует код, находит блоки provider (например, Yandex Cloud, AWS, GitHub) и скачивает нужные бинарные файлы плагинов в локальную скрытую директорию .terraform. Также на этом этапе настраивается подключение к хранилищу состояния (State backend). Без успешной инициализации дальнейшая работа невозможна.
2. Планирование (terraform plan)
Самый важный этап с точки зрения безопасности. Terraform выполняет операцию Read: опрашивает API облачного провайдера, чтобы узнать реальное положение дел (Actual State), сравнивает его с кодом (Desired State) и выводит подробный отчет о том, что он собирается сделать.В выводе команды plan используются специальные символы:
* + (зеленый) — ресурс будет создан.
* - (красный) — ресурс будет удален.
* ~ (желтый) — ресурс будет изменен на месте (In-place update).
* -/+ (красно-зеленый) — ресурс будет удален и создан заново (Replacement). Это происходит, когда изменяется параметр, который API облака не позволяет менять на лету (например, зона доступности виртуальной машины).
Чтение вывода terraform plan — обязательный навык. Пропуск этого шага и слепое применение изменений часто приводит к инцидентам, когда из-за опечатки в коде Terraform решает удалить и пересоздать базу данных.
3. Применение (terraform apply)
На этом этапе Terraform берет вычисленный план и начинает отправлять императивные запросы к API провайдера. Важно понимать, чтоapply по умолчанию включает в себя скрытый plan, требуя от пользователя подтверждения (ввода yes), если план не был сохранен в файл заранее.В CI/CD пайплайнах обычно используется связка:
terraform plan -out=tfplan (генерация и сохранение плана).terraform apply tfplan (применение строго сохраненного плана, чтобы исключить ситуацию, когда за время между планированием и применением кто-то изменил инфраструктуру вручную).4. Уничтожение (terraform destroy)
Специальный случай применения, эквивалентный удалению всех описанных в коде ресурсов. Terraform корректно удаляет ресурсы в порядке, обратном их созданию, соблюдая все зависимости.Граф зависимостей (DAG)
В императивном скрипте порядок создания ресурсов определяется порядком строк в файле. В декларативном HCL порядок блоков кода не имеет значения. Terraform самостоятельно вычисляет, в какой последовательности нужно вызывать API.
Для этого ядро Terraform строит направленный ациклический граф (Directed Acyclic Graph, DAG). Узлы графа — это ресурсы, а ребра — зависимости между ними.
Зависимости бывают двух типов:
yandex_compute_instance.web использует yandex_vpc_network.my_net.id. Terraform понимает: чтобы получить ID сети, сеть нужно сначала создать. Следовательно, ВМ зависит от сети.depends_on. Используются редко, когда зависимость есть логически, но не выражена через передачу атрибутов. Например, приложение на ВМ при старте скачивает файл из S3-бакета. ВМ не использует ID бакета в конфигурации, но если бакет не будет создан первым, приложение упадет.Построение графа дает мощнейшее преимущество — параллелизм. Если Terraform видит в графе ветви, не зависящие друг от друга (например, три разные виртуальные машины в одной подсети), он будет создавать их одновременно. По умолчанию Terraform запускает до 10 параллельных потоков (регулируется флагом -parallelism), что кардинально ускоряет развертывание объемных инфраструктур по сравнению с последовательными скриптами.
Управление жизненным циклом: блок lifecycle
Стандартный алгоритм Terraform (создать, обновить на месте, либо удалить-и-создать-заново) покрывает 90% задач. Однако для критичных production-систем этого бывает недостаточно. Для тонкой настройки поведения используется специальный блок lifecycle, который встраивается внутрь ресурса.
create_before_destroy
По умолчанию, если изменение требует пересоздания ресурса (Replacement), Terraform сначала удаляет старый ресурс, а затем создает новый. Для базы данных или балансировщика нагрузки это означает гарантированный даунтайм.
Установка create_before_destroy = true инвертирует этот процесс. Terraform сначала создаст новый ресурс с новыми параметрами, дождется его готовности, и только потом удалит старый.
!Механизм create_before_destroy
Этот паттерн идеален для реализации Zero-Downtime Deployment (ZDD), но требует осторожности. Новый и старый ресурсы будут существовать одновременно в течение короткого времени. Если ресурс требует уникального имени (например, доменное имя или статический IP), одновременное существование двух объектов с одинаковыми уникальными параметрами вызовет ошибку API облака.
prevent_destroy
Защита от случайного удаления, известная как «защита от дурака». Если ресурс помечен этим флагом, любая операция terraform plan, которая подразумевает удаление ресурса (явное через destroy, или неявное из-за пересоздания), завершится ошибкой еще на этапе вычислений.
Это обязательная практика для stateful-ресурсов: баз данных, хранилищ объектов (S3), дисков с критичными данными. Чтобы удалить такой ресурс, инженеру придется осознанно зайти в код, удалить блок prevent_destroy, закоммитить изменения и только потом выполнить удаление.
ignore_changes
Декларативная модель требует, чтобы код был единственным источником истины. Но в реальности инфраструктура может изменяться извне. Вспоминаем концепцию дрейфа конфигурации: если кто-то вручную добавит тег на сервер, Terraform при следующем запуске заметит расхождение и удалит этот тег, чтобы привести реальность в соответствие с кодом.
Но иногда дрейф легитимен. Классический пример — автомасштабирование (Autoscaling).
Допустим, Terraform создает группу виртуальных машин с начальным размером size = 3. Дальше в дело вступает облачный балансировщик, который из-за высокой нагрузки увеличивает размер группы до 5.
Если запустить Terraform сейчас, он увидит: в коде 3, по факту 5. План покажет удаление двух машин, что приведет к падению production под нагрузкой.
Мета-аргумент ignore_changes указывает Terraform игнорировать расхождения в определенных атрибутах.
Теперь Terraform создаст группу из трех машин, но в будущем перестанет обращать внимание на изменение атрибута size, полностью делегировав управление размером внешнему автоскейлеру.
Декларативный подход Terraform — это не просто удобный синтаксис. Это сложный математический аппарат, который вычисляет разницу состояний и строит оптимальный граф выполнения. Понимание того, как формируется план, как работают неявные зависимости и как переопределить стандартный жизненный цикл ресурсов, отличает начинающего пользователя IaC от инженера, способного безопасно управлять инфраструктурой масштаба Яндекса.