
Когда серверы bare-metal, гипервизоры, облачные решения и десятки Kubernetes-кластеров живут вместе, навести порядок в ИТ-инфраструктуре становится задачей со звёздочкой. К тому же к гибридной инфраструктуре добавляется организационный слой: десятки автономных команд — backend, UI, data engineering, ML, platform — и у каждой свой бэкграунд, уровень зрелости и разный подход к описанию инфраструктуры через код (IaC).
Кто-то всегда пишет на HCL, кто-то предпочитает Python и JS, а кто-то привык работать с docker compose up –d. Задача инженера платформы в такой обстановке не в том, чтобы навязать «серебряную пулю», а в том, чтобы найти инструмент, который обеспечит контроль над состоянием инфраструктуры, позволит стандартизировать базовые паттерны, предсказуемо реагировать на изменения, которые внесли вручную, а еще не будет ломать уже существующие процессы.
Привет, Хабр! Меня зовут Вячеслав Швецов, я архитектор в команде MWS B2B Store. Это первый материал из цикла о построении инженерной платформы в гетерогенной среде. Мы будем разбирать инструменты, антипаттерны и ограничения при эксплуатации. В этом выпуске сравним подходы Terraform и Pulumi, а также рассмотрим управление состоянием, детекцию дрейфа инфраструктуры и практику управления инфраструктурой как кодом.
Terraform (HCL)
История Terraform фактически началась в 2011 году, когда AWS анонсировала CloudFormation: Митчелл Хашимото, сооснователь HashiCorp, опубликовал пост, в котором высоко оценил идею управления инфраструктурой с помощью кода, но отметил критический пробел — отсутствие open source облачно-независимого инструмента, который бы предоставлял единый рабочий процесс для любого провайдера.
Несколько лет идея оставалась нереализованной, пока растущая сложность мультиоблачных сред не сделала проблему острой для самой HashiCorp. В июле 2014 года компания выпустила Terraform 0.1 с поддержкой AWS и DigitalOcean, распространяющегося под открытой лицензией MPL 2.0.
В Terraform изначально заложили гибкую архитектуру на основе провайдеров, позволяющую расширять инструмент под любую платформу. Для описания инфраструктуры разработчики создали собственный простой язык — HCL.
Пример декларативного описания на HCL для создания виртуальной машины через libvirt:
terraform { required_providers { libvirt = { source = "dmacvicar/libvirt" version = "~> 0.7" } } } provider "libvirt" { uri = "qemu:///system" } resource "libvirt_volume" "disk" { ... } resource "libvirt_network" "net" { ... } resource "libvirt_cloudinit_disk" "commoninit" { ... } resource "libvirt_domain" "vm" { name = "test-vm" memory = 2048 vcpu = 2 disk { volume_id = libvirt_volume.disk.id } network_interface { network_id = libvirt_network.net.id hostname = "test-vm" } cloudinit = libvirt_cloudinit_disk.commoninit.id }
В августе 2023 года HashiCorp сменила лицензию Terraform с открытой (MPL 2.0) на Business Source License (BSL) 1.1 по экономическим причинам. Это решение вызвало раскол в сообществе и привело к созданию OpenTofu — полностью открытого аналога, развивающегося при поддержке Linux Foundation.
Pulumi (Code, YAML)
Pulumi появился в 2017 году как ответ на фундаментальное ограничение существующих IaC-инструментов, использовавших DSL вместо полноценных языков программирования (ЯП).
Сооснователи проекта — Джо Даффи (бывший архитектор .NET и Azure в Microsoft) и Эрик Раддер (экс-CTO Microsoft) — поставили цель объединить декларативную модель инфраструктуры с выразительностью и экосистемой современных ЯП.
Публичный запуск Pulumi состоялся в начале 2018 года. Инструмент вышел с поддержкой Python, TypeScript и Go, что позволило разработчикам управлять облачными ресурсами с помощью привычных паттернов (тесты, абстракции, пакеты). В отличие от конкурентов, Pulumi сделал ставку на «инфраструктуру как программный код», а не «инфраструктуру как конфигурацию», что привлекло команды с сильной инженерной культурой.
Бизнес-модель Pulumi, в отличие от HashiCorp, строится на сочетании полностью открытого исходного кода (Apache 2.0) и облачной SaaS-платформы. Монетизируется не сам инструмент, а удобство управления им в команде.
Ниже — пример кода на Python, создающий виртуальную машину через libvirt. Но писать код не обязательно. У Pulumi есть декларативный режим на базе YAML, похожий на привычный HCL. Это снижает порог входа для команд, которые предпочитают «описывать инфраструктуру», а не «программировать её».
Пример кода на Python:
import pulumi import pulumi_libvirt as libvirt net = libvirt.Network("net", mode="nat", domain="test.local", addresses=["192.168.100.0/24"]) disk = libvirt.Volume("disk", pool="default", format="qcow2", size=20 * 1024**3) cloudinit = libvirt.CloudInitDisk("init", pool="default", user_data="#cloud-config\nhostname: test-vm\npassword: changeme\nchpasswd: { expire: False }") vm = libvirt.Domain("vm", memory=2048, vcpu=2, disks=[{"volume_id": disk.id}], network_interfaces=[{"network_id": net.id, "hostname": "test-vm"}], cloudinit=cloudinit.id)
Пример конфигурации на Pulumi YAML:
name: kvm-vm runtime: yaml resources: vm: type: libvirt:index/domain:Domain properties: name: test-vm memory: 2048 vcpu: 2 ...
Pulumi YAML: декларативный подход вроде есть, а вроде нет
У Pulumi есть альтернатива коду — декларативный режим. Но на практике этот режим быстро упирается в жёсткие рамки и подходит только для решения простых задач:
Нет циклов и полноценных условий: нельзя использовать привычные
for,if/elseвнутри YAML. Чтобы динамически создавать однотипные ресурсы, обычно используют несколько стеков или внешние генераторы.Ограниченная логика вычислений: вычисления сильно урезаны, доступны только базовые встроенные строковые/массивные функции. Сложные преобразования данных не поддерживаются.
Управление конфигурацией и работа со state
Основные команды
После того как конфигурация описана, нужно ею воспользоваться. Оба инструмента следуют единому циклу:
инициализация окружения;
расчёт плана изменений;
применение конфигурации к реальной инфраструктуре.
В таблице собраны ключевые команды для создания, обновления и управления локальным состоянием инфраструктуры:
Действие | Terraform | Pulumi |
Инициализация |
|
|
Просмотр плана |
|
|
Применение конфигурации |
|
|
Обновление (idempotent) конфигурации |
|
|
Локальный state | Файл |
|
Экспорт state |
|
|
На первый взгляд Terraform и Pulumi решают идентичные задачи и даже позволяют выстраивать работу практически в одинаковом стиле. Однако за этой внешней схожестью скрываются принципиально разные архитектурные решения, модели управления состоянием, философии расширяемости и подходы к интеграции с процессами доставки.
Чтобы сделать взвешенный выбор для разнородной среды, недостаточно сравнить синтаксис или базовые команды. Необходимо заглянуть под капот и разобрать, как каждый инструмент обрабатывает граф зависимостей, реагирует на дрейф инфраструктуры, масштабируется при росте числа команд и вписывается в строгие инженерные практики. Именно эти особенности мы и разберём далее.
State и куда же обойтись без drift
Управление состоянием инфраструктуры — не просто техническая деталь реализации IaC, а фундаментальный механизм, который связывает декларативный код с физической реальностью инфраструктуры.
Кроме того, именно состояние позволяет детектировать дрейф инфраструктуры (drift), обеспечивать идемпотентность (повторяемость) операций и быстро восстанавливаться после инцидентов, откатывая инфраструктуру к известной стабильной версии. Пренебрежение управлением состоянием неизбежно превращает IaC из инструмента контроля в источник операционного хаоса, где каждый apply становится лотереей, а команды теряют доверие к автоматизации.
Хранение состояния
Состояние инфраструктуры хранится в специальном state-файле. По нему инструмент понимает, какие изменения нужно внести при следующем запуске.
State-файл выступает единым источником истины: без него инструмент не знает, какие ресурсы уже созданы, какие параметры у них изменены и как они зависят друг от друга. В командной среде корректное хранение состояния становится вопросом безопасности и согласованности: внешние бэкенды с механизмами блокировок предотвращают параллельные запуски, способные вызвать гонки состояний, двойное создание ресурсов или случайное уничтожение рабочих сервисов.
Terraform и Pulumi подходят к безопасности и хранению state-файла по-разному:
Параметр | Terraform | Pulumi |
Формат | JSON-файл | Encrypted checkpoint (JSON-структура) |
Локальное хранение | Файл в рабочей папке. Блокировка только через | Команда |
Сетевое хранилище | Любой бэкенд (S3, GCS, Consul, Terraform Cloud) | Pulumi Cloud, S3, GCS, Azure Blob, Consul, Kubernetes |
Секреты | Хранятся в открытом виде (рекомендуется внешний бэкенд с шифрованием) | Шифруются на лету (по умолчанию через Pulumi passphrase или cloud KMS) |
Примечание: локальный state удобен для песочниц, но в командной работе это антипаттерн. Без внешнего бэкенда вы теряете блокировку, историю версий и возможность совместной работы.
Здесь Pulumi выигрывает из коробки за счёт встроенного шифрования секретов, но требует явного управления passphrase/KMS.
Обработка drift (расхождения desired vs actual)
Оба инструмента выявляют расхождение реального и желаемого состояния инфраструктуры (drift инфраструктуры) через чтение API провайдера. Terraform делает это в рамках plan, Pulumi — в preview/refresh. Разница кроется в подходе:
Для Terraform единственный источник правды — state-файл. Любое ручное изменение воспринимается как аномалия, ошибка, которую нужно устранить.
Pulumi к ручным правкам относится более гибко. У него есть штатный инструмент синхронизации —
refresh. Он позволяет обновить state-файл под реальность, что удобнее в средах, где ручные правки неизбежны.
Механизм | Terraform | Pulumi |
Поиск изменений |
|
|
Синхронизация |
|
|
Мутация состояния: когда план не спасает
Идеология IaC предполагает, что state — зеркало реальности. На практике бывают ситуации, когда зеркало «трескается»: ресурс удалён вручную, модуль рефакторили или провайдер сломал идемпотентность. В таких случаях приходится вручную править state, чтобы избежать внезапного пересоздания живых ресурсов (destroy + create).
Задача | Terraform | Pulumi |
Убрать ресурс из state, не трогая реальную инфраструктуру |
|
|
Взять существующий ресурс под управление |
|
|
Переименовать/перенести ресурс в state (после рефакторинга) |
| Нет прямой команды. Экспорт → правка |
Принудительно пересоздать ресурс (без полного destroy) |
|
|
Тонкая ручная правка state |
|
|
Защита от случайного удаления |
|
|
Правила работы с мутациями состояний:
Всегда делайте бэкапы перед мутацией:
terraform state pull/pulumi stack export.Проверяйте state после изменений:
terraform plan/pulumi previewдолжны показатьNo changesили ожидаемые минимальные различия.Checkpoint-система Pulumi автоматически сохраняет предыдущие версии состояния, что даёт откат «одной командой». В Terraform history состояние нужно хранить вручную (например, через бэкенд с версионированием в S3).
Мутация state — крайняя мера. Используйте её для миграции legacy, восстановления после CI-сбоев или обхода багов провайдеров. Не заменяйте ею нормальный рабочий процесс.
Переиспользование кода: как не дублировать описание инфраструктуры
Упаковка инфраструктурного кода в модули или компоненты превращает разрозненные скрипты в управляемые строительные блоки платформы: модули инкапсулируют сложность реализации за чёткими интерфейсами, разделяют ответственность между платформенными и продуктовыми командами, упрощают тестирование и аудит, делая инфраструктуру предсказуемой, масштабируемой и безопасной.
Без модулей даже самый продвинутый IaC-инструмент быстро деградирует в «скриптовый хаос», где каждое изменение требует героических усилий.
Terraform: модули + обёртки
Модули (
modules/) — базовый механизм. Параметризуются черезvariable, возвращаютoutput. Поддерживаютfor_each,count,depends_on.Registry — реестр с готовыми провайдерами от HashiCorp и комьюнити. Удобно, но требует аудита версий.
Terragrunt — стандарт для DRY (Don’t Repeat Yourself) в Terraform. Позволяет один раз настроить общие параметры (например,
backend,provider,remote_state) и использовать их во всех проектах одной строкой кода черезincludeиdependency. Особенно полезно, когда команды разные, но много однотипных окружений (stage/prod/dev).
Ограничения: HCL — это DSL, а не язык программирования. Поэтому нет поддержки наследования, unit-тестов, сложная отладка.
Pulumi: компоненты + пакеты
Компоненты (
ComponentResource) — классы/функции, инкапсулирующие группу ресурсов. Можно писать на Python, TS, Go, C#. Это полноценное ООП/функциональное программирование с наследованием, dependency injection, mock-тестами.Пакеты — публикация компонентов в приватном реестре. Команды подключают компоненты как зависимости, не видя внутренней реализации.
YAML — декларативный подход. Поддерживает
${config.*},${resources.*}, но не даёт логики. Для переиспользования кода используются внешние конфигурации, CI-генерация или переход к языковому стеку при росте сложности.
Ограничения: требуются знания ЯП от авторов компонентов. YAML-стеку не хватает гибкости для сложных паттернов.
Сравнение подходов к анализу кода инфраструктуры
По мере накопления кода IaC он неизбежно перестаёт быть просто набором конфигурационных файлов и превращается в корпоративный актив, требующий управления.
Рост объёмов, масштабирование команд и увеличение количества сред делают ручное сопровождение и ad-hoc-проверки невозможными. Это закономерно подталкивает организации к внедрению централизованного анализа: статической валидации, контроля соответствия политикам безопасности и стандартам. Terraform и Pulumi подходят к этой задаче совершенно по-разному:
Параметр | Terraform | Pulumi |
Простота анализа кода | Высокая. Код написан в простом текстовом формате. Сканеры безопасности легко читают его без запуска самой программы | Средняя/низкая. Чтобы полностью проверить инфраструктуру, код сначала нужно запустить. Сложнее статический парсинг. Из-за гибкости языков сканеры часто ошибаются или пропускают скрытые баги |
Инструменты проверки | Богатый выбор готовых инструментов для автоматической проверки | Можно использовать стандартные тесты для языков программирования (unit-тесты) |
Почему HCL предпочтителен для нас
Главные причины, почему мы выбрали Terraform и HCL:
Предсказуемая структура и готовность к AST-анализу:
HCL имеет фиксированный синтаксис, строгую иерархию (
resource,module,variable,data,locals) и отсутствие произвольной логики.Парсеры легко строят абстрактное синтаксическое дерево (AST), что упрощает написание линтеров, валидаторов и анализаторов.
В отличие от языков общего назначения (Python, TypeScript в Pulumi), здесь нет динамического импорта, рефлексии или побочных эффектов при парсинге.
Детерминированный
planв машиночитаемом формате:terraform plan –jsonиterraform show –jsonвыдают структурированный снимок будущих изменений: создаваемые/удаляемые ресурсы, изменённые атрибуты, зависимости, метаданные провайдеров.Платформы могут парсить этот JSON для:
оценки стоимости или проверки квотирования;
детекции дрейфа инфраструктуры.
Зрелая экосистема Policy as Code и линтеров:
Checkov, tfsec, Terrascan, KICS, TFLint — все нативно поддерживают HCL.
Правила пишутся декларативно, покрывают CIS, NIST, PCI-DSS, GDPR, внутренние стандарты компаний.
Интеграция с реестром провайдеров: схемы ресурсов публикуются как JSON, что позволяет легко валидировать атрибуты.
Модель зависимостей и граф ресурсов:
HCL автоматически строит DAG (directed acyclic graph) на основе явных и косвенных зависимостей.
Terraform graph и визуализаторы (Rover, graphviz) дают прозрачную карту архитектуры.
Модульность и контракты:
variablesс типами/описаниями/дефолтами,outputs,required_providers,terraform {}создают предсказуемость.Анализаторы могут принудительно проверять:
версионирование провайдеров и модулей;
обязательные теги/метки;
переменные, которые используются в качестве контракта;
запрет на использование определённых ресурсов.
Заключение
Terraform и Pulumi — это мощные, зрелые движки, но важно понимать их архитектурные границы. Оба инструмента предлагают кастомизируемый фреймворк, а не коробочное решение. Они дают вам примитивы для описания ресурсов, управления состоянием и детекции дрейфа инфраструктуры, но не отвечают на вопросы организации: как стандартизировать структуру репозиториев, как управлять зависимостями между десятками модулей, как применять политики безопасности на все окружения одновременно и как предотвращать появление «инсталляций-снежинок» в гетерогенной среде.
На практике вы всё равно будете строить собственную инженерную платформу поверх этих инструментов: писать обёртки, настраивать CI/CD-пайплайны для валидации state, внедрять policy-as-code и выстраивать культуру работы с инфраструктурой. Ни Terraform, ни Pulumi не решают проблему «снежинок» из коробки — они лишь предоставляют инструменты, которыми эту проблему можно решить при наличии дисциплины и правильной архитектуры.
В следующей статье цикла мы разберём, как закрыть этот пробел. Рассмотрим atmos.tools, Terramate и другие решения, работающие поверх Terraform и Pulumi. Покажем, как они обходят проблемы на уровне платформы, превращая разрозненные скрипты в управляемую, версионируемую и предсказуемую инфраструктуру.
Более того, можно вообще отказаться от ручной сборки платформы из десятков разномастных инструментов и обратить внимание на среды, где типовые B2B-решения для IaC уже унифицированы. В MWS мы применили такой подход при создании собственного маркетплейса B2B-решений: предлагаем развёртывание по клику на инфраструктуре клиента независимо от её архитектуры. Вы можете разместить свой B2B-продукт в нашей экосистеме.
