Представьте, что вы настроили сервер в облаке: накликали мышкой нужные галочки в веб-консоли, создали базу данных, открыли пару портов. Всё работает. А через месяц нужно поднять точно такую же копию для тестов — и вы уже не помните, какие именно галочки нажимали. Знакомо? Именно эту боль решает Infrastructure as Code (инфраструктура как код, сокращённо IaC).

Идея простая: вместо того чтобы настраивать облако руками, вы описываете желаемую инфраструктуру в текстовых файлах — а специальная программа сама создаёт всё по этому описанию. Эти файлы можно положить в git, показать коллеге на ревью, откатить к прошлой версии. Никакого «а что я там нажимал» — есть код, и по нему видно, что должно быть.

руками накликали в консоли работает через месяц копия для тестов что нажимали, не помнит никто кодом описание в файлах git и ревью apply та же копия по команде

Разница не в том, что получилось сейчас, а в том, что будет через месяц: настройку мышью нельзя повторить, откатить и показать коллеге, а описание в файлах можно применить снова и получить ту же инфраструктуру.

Обязательно

Что такое инфраструктура как код

Инфраструктура — это всё, на чём работает ваше приложение: виртуальные машины, базы данных, сети, хранилища, правила доступа. Раньше всё это создавали вручную через панель управления или серию команд. Подход IaC переносит это в файлы, которые читаются и человеком, и машиной.

Аналогия: рецепт блюда. Можно готовить «на глаз» и каждый раз получать чуть разный результат, а можно записать точный рецепт — граммы, минуты, температура. По рецепту блюдо повторит и другой человек, и вы через год. Файл инфраструктуры — это и есть такой рецепт для облака.

Что это даёт на практике:

  • Воспроизводимость. Одним и тем же файлом разворачиваете идентичные окружения для разработки, тестов и продакшена.
  • История изменений в git. Видно, кто, когда и зачем добавил базу данных — обычный git log.
  • Ревью. Изменение инфраструктуры проходит через pull request, как любой код.
  • Откат. Что-то сломалось — возвращаетесь к прошлой рабочей версии файла.
  • Никакого «забыл, что нажимал»: источник правды один — файл.

Базовые сведения о самом облаке — в статье основы AWS.

Чего инфраструктура как код не делает

Границу стоит провести сразу, потому что новички ждут от инструмента больше, чем он умеет, и потом не понимают, где стыки.

Инфраструктурный инструмент создаёт ресурсы, а не настраивает их изнутри. Terraform поднимет виртуальную машину, сеть, диск и права. Что происходит внутри машины — установка пакетов, правка файлов настроек, сервисы — это другая задача, и называется она управлением конфигурацией: Ansible, Chef, Puppet, а чаще всего просто заранее собранный образ машины. Признак, что граница нарушена: в коде инфраструктуры появляется длинный скрипт первичной загрузки, который ставит половину системы.

И он не выкатывает приложение. Новая версия кода, откат, постепенная замена экземпляров — это конвейер доставки. Смешивать это с инфраструктурой вредно по двум причинам: приложение выкатывается десять раз в день, а инфраструктура меняется раз в месяц, и держать их в одном цикле означает гонять план по всей инфраструктуре ради смены тега образа. Признак нарушения: terraform apply на каждый коммит приложения.

Как разделяют на практике:

ЗадачаЧем делают
Сеть, права, базы, кластеры, очередиинфраструктурный инструмент (Terraform, CloudFormation, CDK)
Содержимое машины и её пакетызаранее собранный образ (Packer) или управление конфигурацией
Содержимое контейнераDockerfile и сборка образа
Версия приложения в кластереконвейер доставки, манифесты, оператор доставки
Схема базы данныхмиграции в составе приложения или отдельной задачей

Серая зона есть, и стоит знать, где она. Ресурсы уровня приложения (правило маршрутизации, тема очереди, параметр в хранилище настроек) можно описывать и инфраструктурным инструментом, и вместе с приложением — правило простое: кто чаще меняет, тот и владеет. Если тема очереди появляется вместе с новой функцией сервиса, она живёт в его репозитории; если это общая шина на всю компанию — в инфраструктурном.

Declarative против imperative

Это главная развилка в мире IaC, и понять её важно с самого начала. Есть два способа описать, чего вы хотите.

Imperative (императивный) — вы описываете КАК, по шагам: «создай сервер, потом подключи к нему диск, потом открой порт 80». Это последовательность команд. Похоже на навигатор, который диктует каждый поворот: «через 200 метров направо».

Declarative (декларативный) — вы описываете ЧТО хотите получить в итоге: «должен существовать сервер с диском и открытым портом 80». А уже инструмент сам решает, какие шаги нужны, чтобы привести реальность к этому состоянию. Это как сказать водителю адрес — а маршрут он построит сам.

Большинство популярных инструментов IaC — декларативные. Terraform и CloudFormation работают именно так. Например, в Terraform на языке HCL вы описываете конечное состояние — хранилище в облаке:

resource "aws_s3_bucket" "example" {
  bucket = "my-unique-bucket-name"

  tags = {
    Name        = "My bucket"
    Environment = "Dev"
  }
}

То же самое в CloudFormation выглядит как YAML-шаблон:

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  ExampleBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-unique-bucket-name
      Tags:
        - Key: Name
          Value: My bucket

Заметьте: ни там, ни там нет слов «создай», «потом», «если уже есть — пропусти». Вы просто описали, что хранилище должно существовать с такими тегами. Решать, создавать его или менять существующее, будет сам инструмент.

Особняком стоит CDK (Cloud Development Kit). Его пишут как обычную программу — на TypeScript, Python, Java или других языках. Но это не делает его императивным: код CDK не создаёт ресурсы напрямую, а синтезирует declarative-шаблон CloudFormation, который потом и применяется. То есть CDK — это удобная обёртка, дающая привычный язык программирования поверх декларативного движка:

Команда cdk synth превратит такую программу в шаблон CloudFormation (в человекочитаемом виде его можно посмотреть как YAML), а cdk deploy его применит. Подробный разбор каждого инструмента — в статьях про Terraform, CloudFormation и CDK.

Инфраструктура уже есть: как завести её в код

Учебные примеры начинаются с чистого листа, а в жизни почти всегда есть накликанное руками: сеть, база, пара машин, права. Вопрос «как это завести в код» встаёт в первый же день, и у него есть штатный ответ — импорт.

Что делает импорт. Он не создаёт ресурс и не меняет его — он записывает существующий ресурс в состояние инструмента, после чего тот начинает считать его своим. Дальше вы описываете этот ресурс в коде так, чтобы план показывал «изменений нет»: это и есть критерий успеха.

В Terraform это можно сделать командой или, что удобнее, блоком в самом коде:

import {
  to = aws_s3_bucket.reports
  id = "acme-reports-prod"
}

resource "aws_s3_bucket" "reports" {
  bucket = "acme-reports-prod"
}
terraform plan     # покажет, что будет импортировано и чем описание отличается от реальности
terraform apply    # выполнит импорт

У CloudFormation есть своя операция импорта ресурсов в стек, у CDK — она же под капотом.

Как это делают на практике, чтобы не утонуть. Импортировать всё сразу — верный способ застрять на неделю. Работающий порядок такой:

  1. Начинают с того, что редко меняется и мало от чего зависит: хранилища объектов, реестры, группы параметров.
  2. Идут снизу вверх по зависимостям: сначала сеть, потом то, что в ней живёт.
  3. По одному ресурсу за раз, каждый раз доводя план до «изменений нет». Импортированный ресурс почти всегда отличается от описания в десятке мелочей (теги, версии, значения по умолчанию) — их либо переносят в код, либо намеренно игнорируют.
  4. Ресурсы, которые нельзя трогать, сразу защищают от удаления (об этом ниже), потому что импортированная база — теперь объект, который инструмент может удалить по вашей же ошибке в коде.
  5. Что-то оставляют вне кода осознанно и записывают это решение: учётные записи людей, ресурсы чужих команд, эксперименты.

Инструменты, которые ускоряют. Есть генераторы, которые читают существующую инфраструктуру и выдают заготовки кода (terraformer, экспорт шаблона у CloudFormation, cdk migrate). Результат у них черновой — имена, структура и модули будут не те, — но как отправная точка на большом контуре это экономит дни. Правило одно: сгенерированное обязательно приводят к своему виду и доводят план до нуля изменений.

И честное ожидание по времени. Перевод существующего контура среднего размера в код — это недели, а не вечер, и делается он параллельно обычной работе. Отсюда практическая стратегия, которой пользуются чаще всего: всё новое сразу в коде, старое импортируют по мере того, как к нему приходится прикасаться. Через полгода в коде окажется то, что действительно живёт, а мёртвое так и останется в стороне — и это нормально.

Что такое state

Чтобы декларативный инструмент мог понять, что нужно изменить, ему нужна память. Эта память называется state (состояние) — это «слепок» того, что инструмент знает о реальной инфраструктуре: какие ресурсы он создал, с какими настройками и идентификаторами.

Аналогия: список покупок с галочками. Вы пришли в магазин со списком (это ваш код — чего вы хотите) и отмечаете, что уже лежит в корзине (это state — что уже есть). Сравнив одно с другим, вы точно знаете, что ещё докупить.

Когда вы запускаете terraform plan, Terraform делает ровно это — но с важной поправкой. Сначала он сходит в облако и освежит свои записи (это называется refresh), а сравнивать ваш код будет уже с тем, что там на самом деле, а не с тем, что записано в state. Разница в том и в другом случае выглядит одинаково — что добавит, что изменит, что удалит, — но благодаря освежению план заметит и то, что кто-то поменял мимо Terraform: если коллега удалил хранилище руками через консоль, план предложит создать его заново. Только после terraform apply изменения применяются по-настоящему.

terraform plan
terraform apply

У CloudFormation роль state играет сам стек (stack) на стороне AWS — облако само хранит, какие ресурсы относятся к этому шаблону. У Terraform state — это отдельный файл (terraform.tfstate), который в командной работе хранят не на ноутбуке, а в общем месте, например в объектном хранилище, чтобы у всех была одна версия правды.

В state лежат ваши пароли

Одно свойство состояния важнее всех остальных, и о нём лучше узнать до первого запуска, а не после.

Состояние хранит значения всех атрибутов созданных ресурсов — в открытом виде. Не только идентификаторы, а всё: пароль, который вы сгенерировали для базы, содержимое созданного секрета, ключ доступа, приватная часть сертификата, строка подключения. Инструмент обязан их помнить, иначе он не сможет сравнить желаемое с текущим.

Отсюда практические правила, которые нельзя обойти:

  • Файл состояния — секрет. Его нельзя коммитить в репозиторий, нельзя выкладывать в общий доступ, нельзя отправлять в чат «посмотри, что там». Утечка состояния равносильна утечке паролей всей описанной инфраструктуры.
  • Удалённое хранилище состояния — с шифрованием и ограниченным доступом. Права на чтение этого хранилища равны правам на все пароли; список тех, у кого они есть, должен быть коротким.
  • Ничего чувствительного не создают через инфраструктурный код без нужды. Пароль базы лучше сгенерировать в хранилище секретов и передавать ссылку, чем генерировать в коде инфраструктуры и хранить значение в состоянии.
  • Пометка sensitive не помогает. Она лишь скрывает значение в выводе плана; в файле состояния оно лежит как есть. Это частое заблуждение.
  • Резервные копии состояния — тоже секрет, с теми же требованиями.

Подробный разбор того, где держать состояние, как его блокировать и что делать с секретами в конвейере, — в статье про состояние и доставку.

Повторный запуск ничего не ломает: idempotency

Страшное слово idempotency (идемпотентность) на деле означает очень понятную вещь: повторный запуск без изменений в коде ничего не ломает и ничего не дублирует.

Аналогия: выключатель света со словами «выключено» вместо «переключить». Сколько раз ни нажми «выключено» — свет останется выключенным. А если бы кнопка была «переключить», каждое нажатие меняло бы состояние, и результат зависел бы от числа нажатий.

Именно так ведут себя декларативные инструменты. Вы описали «нужно одно хранилище». Запустили — хранилище создалось. Запустили ещё раз тот же код, не меняя его — инструмент сравнит код со state, увидит, что всё уже на месте, и ничего не сделает. Не появится второе хранилище, ничего не сломается.

terraform apply
terraform apply

Второй apply на неизменённом коде завершится сообщением вроде «0 added, 0 changed, 0 destroyed». Это и есть идемпотентность в действии — и именно благодаря ей запускать IaC безопасно хоть каждый день: лишнего он не натворит.

Где идемпотентность ломается

Обещание «второй запуск не даёт изменений» верно в большинстве случаев и не во всех. Полезно знать, где именно оно не работает, потому что иначе вы будете подозревать себя.

Ресурсы, которые провайдер не умеет сравнивать. У части ресурсов провайдер не читает обратно все свойства (это называется отсутствием поддержки чтения), и тогда он не знает, изменилось ли что-то. Проявляется по-разному: план каждый раз показывает изменение, которого нет, или наоборот — реальное изменение не видно вовсе.

Генерируемые значения. Ресурс, у которого имя или пароль создаются случайно при каждом планировании, будет бесконечно пересоздаваться. Лечится тем, что случайность выносят в отдельный ресурс со своим состоянием (random_password и его аналоги) — тогда значение генерируется один раз и запоминается.

Внешние изменения. Ресурс, который меняет кто-то другой, — самый частый случай. Классика: число копий в группе масштабирования, которое меняет автомасштабирование, или тег, который навешивает корпоративная политика. Инструмент видит расхождение и старательно возвращает своё значение — то есть каждый запуск гасит работу другого механизма.

Побочные эффекты провайдера. Часть ресурсов при применении выполняет действия, которые нельзя отменить и повторить безболезненно (пересоздание базы вместо изменения параметра, замена машины при смене типа). Идемпотентность тут сохраняется формально — итог одинаковый, — а вот цена второго запуска совсем другая, и план об этом честно предупреждает словом «replace».

Два инструмента, которые закрывают эти случаи и заодно отвечают на вопрос из следующего раздела про дрейф:

ignore_changes — «это свойство меняет не я, не трогай его». Обязательно для числа копий под автомасштабированием, для тегов от внешних систем, для версий образов, которые обновляет конвейер:

resource "aws_autoscaling_group" "app" {
  desired_capacity = 2

  lifecycle {
    ignore_changes = [desired_capacity, tag]
  }
}

prevent_destroy — «этот ресурс нельзя удалять, даже если из кода следует обратное». Ставится на всё, чья потеря необратима: база, хранилище с данными, ключ шифрования:

resource "aws_db_instance" "orders" {
  lifecycle {
    prevent_destroy = true
  }
}

Это единственная защита от ошибки, которая случается со всеми: переименовали ресурс в коде, и инструмент честно решил, что старый надо удалить, а новый создать. У баз и хранилищ такое движение означает потерю данных; с этой пометкой применение просто остановится с ошибкой, и вы успеете подумать.

Когда реальность разошлась с кодом: drift

А теперь представьте: кто-то из коллег зашёл в веб-консоль и поменял настройку руками — например, переключил тип сервера. В коде по-прежнему написано одно, а в реальности уже другое. Вот это расхождение и называется drift (дрейф): реальность разошлась с кодом.

Drift опасен тем, что незаметен. Файлы выглядят как раньше, но облако работает иначе. А при следующем apply инструмент может «откатить» ручную правку обратно к тому, что записано в коде, — и сломать то, что коллега чинил вручную.

Поэтому в инструментах есть способ обнаружить дрейф. В Terraform это запуск, который только сверяет реальность с записями, ничего не меняя ни в инфраструктуре, ни в самих записях:

terraform plan -refresh-only

Terraform сходит в облако, посмотрит фактическое состояние ресурсов и сообщит: «объекты изменились вне Terraform». Обратите внимание на plan: у той же операции есть форма terraform apply -refresh-only, и она инфраструктуру тоже не трогает, но записывает увиденное в state — то есть молча принимает ручную правку за новое желаемое состояние. Для «просто посмотреть» нужен именно plan.

В CloudFormation для того же есть отдельная команда обнаружения дрейфа. Работает она в два приёма: первая команда только запускает проверку и возвращает её идентификатор, результат забирают второй:

aws cloudformation detect-stack-drift --stack-name my-stack
aws cloudformation describe-stack-resource-drifts --stack-name my-stack

Лечение дрейфа простое по идее: либо вернуть реальность к коду (применить шаблон заново), либо внести ручную правку в код, чтобы он отражал новое желаемое состояние. Главное правило, которое экономит нервы: меняем инфраструктуру через код, а не руками в консоли — тогда дрейфу просто неоткуда взяться.

Цена подхода и когда его не брать

Список преимуществ выглядит убедительно, и честности ради стоит назвать цену — она реальная и особенно заметна на старте.

Медленнее кликов, пока не станет быстрее. Создать одну машину в консоли — две минуты. Описать её кодом, настроить хранилище состояния, разобраться с провайдером и правами — полдня в первый раз. Выигрыш появляется со второй средой и с третьим изменением, а не сразу.

Появляется ревью и очередь. Изменение инфраструктуры теперь проходит через запрос на изменение, и «быстро поправить руками» становится нарушением. Это правильно и это замедляет срочные правки — к чему надо быть готовым, а не обнаруживать в инциденте.

Версии ломаются. Провайдер обновился и поменял поведение, инструмент обновился и требует другой синтаксис, модуль из реестра выпустил несовместимую версию. Без фиксации версий и файла блокировки один и тот же код на двух машинах даёт разный результат. Это отдельная дисциплина, которой в кликах не было.

Состояние — новая сущность, которую можно потерять. Файл состояния надо где-то хранить, блокировать, беречь как секрет и уметь чинить, когда он разошёлся с реальностью. Это не сложно, но это ещё одна вещь, за которой следят.

Первый час в новом проекте уходит не на дело. Инициализация, бэкенд, права, версии — и только потом первый ресурс.

Когда не брать вовсе. Одноразовый эксперимент, который завтра удалят. Разовая правка в чужом контуре, который вы не ведёте. Ресурсы, у которых нет провайдера или он слабый (тогда код превратится в набор вызовов командной строки, и лучше честно оставить это скриптом). И учебная работа, где цель — понять сервис, а не воспроизвести его: кликнуть в консоли и посмотреть, что получилось, для этого быстрее.

Когда точно брать. Больше одной среды. Больше одного человека. Инфраструктура, которую придётся воспроизвести (новый регион, авария, новый заказчик). Требование аудита «кто и когда изменил». И простой критерий, который работает лучше рассуждений: если вы второй раз повторяете одну и ту же последовательность кликов — пора.

Где это применяется

Infrastructure as Code — это фундамент, на котором стоит почти вся современная эксплуатация облака. Несколько типичных мест, где вы с этим столкнётесь:

  • Автоматический деплой. IaC — естественная часть пайплайнов: код инфраструктуры применяется так же автоматически, как и код приложения. См. принципы пайплайна и стратегии релизов.
  • Одинаковые окружения. Разработка, тесты и продакшен поднимаются из одних файлов — отличаются только параметрами.
  • Kubernetes. Манифесты Kubernetes — это тоже декларативное описание желаемого состояния, та же философия «опиши ЧТО, а не КАК». Начать стоит с основ Kubernetes и деплоя и конфигурации.

Типичные ошибки начинающих, которых легко избежать, если помнить материал выше:

  • Правят инфраструктуру руками «по-быстрому» — и получают drift, который потом всплывает в самый неподходящий момент. Любое изменение — через код.
  • Теряют или не делят state. В команде state должен лежать в общем месте, иначе двое разработчиков случайно затрут изменения друг друга.
  • Боятся повторно запускать apply. Из-за идемпотентности повторный запуск на неизменённом коде безопасен — бояться нечего.
  • Путают imperative и declarative и пытаются писать «по шагам» там, где инструмент ждёт описания конечного состояния.

Что учить дальше: возьмите один инструмент и пройдите его целиком — например, Terraform как самый универсальный, CloudFormation как родной для AWS или CDK, если ближе обычный язык программирования. Затем посмотрите, как state и доставка устроены в командной работе, в статье про state и доставку, и как всё это связывается с автоматическими релизами в разделе про ветвление и релизы.

Дополнительно: при первом чтении можно пропустить

Глубже: завести существующее под управление: importрасширенное

Все примеры выше начинаются с чистого листа, а первая настоящая задача другая: облако уже есть, его накликали руками за два года, и теперь это нужно описать кодом, не пересоздавая. Для этого у инструментов есть импорт: связать существующий ресурс с описанием в коде и записать его в state, ничего не меняя в облаке.

В Terraform импорт делают блоком в коде с версии 1.5: import { to = aws_s3_bucket.assets, id = "shop-assets" }, а terraform plan -generate-config-out=generated.tf сам напишет черновик описания ресурса по его реальному состоянию. Дальше черновик правят до читаемого вида и добиваются пустого плана: plan не предлагает ни одного изменения, значит код совпал с реальностью, и только после этого блок импорта убирают. Старая команда terraform import адрес id делает то же самое, но описание приходится писать руками целиком. В CloudFormation существующий ресурс заводят в стек через набор изменений типа импорт с обязательным DeletionPolicy: Retain, чтобы ошибка не удалила то, что импортировали; в CDK та же операция называется cdk import.

Что здесь важно. Импорт идёт по одному ресурсу, у каждого свой формат идентификатора, и он написан в документации провайдера у каждого типа. Зависимости (группа безопасности, на которую ссылается машина) импортируют раньше зависимых. Массовый импорт сотен ресурсов делают инструментами, которые генерируют код по аккаунту целиком, и результат всё равно читают глазами: он описывает всё, включая мусор. Разумный порядок: сначала то, что собираетесь менять, потом остальное по мере надобности, а список «пока не под управлением» ведут явно, иначе через год никто не знает, что кодом, а что нет. И сразу после импорта настраивают проверку дрейфа, о которой раздел выше, потому что руками править привыкли, и привычка переживёт импорт.

Глубже: проверки инфраструктурного кодарасширенное

Конвейер из plan и apply пропускает половину ошибок, которые ловятся до плана, и все, которые план не показывает. Проверок четыре уровня, и внедряют их в этом порядке.

Форма. terraform fmt -check и terraform validate ловят синтаксис и опечатки в именах атрибутов за секунды, без доступа к облаку; cfn-lint делает то же для шаблонов CloudFormation, tflint находит устаревшие аргументы и несуществующие типы машин. Это первый шаг конвейера, и он бесплатный.

Политика и безопасность. Сканеры читают код и находят то, что план покажет как обычное изменение: бакет с публичным доступом, группу безопасности с 0.0.0.0/0 на порт базы, диск без шифрования, роль с *. Для Terraform это checkov и trivy config (бывший tfsec), для CloudFormation cfn_nag и тот же checkov. Свои правила компании («тег команды обязателен», «только эти регионы») пишут на OPA и проверяют conftest по JSON плана; у коммерческих платформ для этого свои языки политик. Правила начинают с десятка критичных, иначе первый прогон даёт пятьсот предупреждений и сканер выключают.

Стоимость. Изменение, которое поднимает счёт на тысячу долларов в месяц, в плане выглядит как одна строка с другим типом машины. Оценщик стоимости (infracost) читает план и пишет в pull request разницу в деньгах до и после, и это единственная проверка, которую читают все.

Тесты. Модуль, который используют десять команд, тестируют как код: terraform test (с версии 1.6) поднимает ресурсы по сценарию и проверяет утверждения, Terratest делает то же на Go с реальным облаком, у CDK есть модуль утверждений, который проверяет синтезированный шаблон без развёртывания. Тесты с реальными ресурсами дороги и медленны, поэтому их гоняют для модулей, а не для каждого окружения.

Порядок в конвейере: форма, линтер, сканер политик, план, стоимость, и тесты отдельно по модулям. Каждый шаг падает и останавливает слияние; предупреждения, которые не останавливают, через месяц никто не читает.

Глубже: какой инструмент брать: Terraform, OpenTofu, CloudFormation, CDK и Pulumiрасширенное

Сравнение инструментов рассыпано по концовкам статей раздела в виде «а ещё посмотрите». Соберём в одном месте с критериями, потому что выбор живёт годами.

Terraform и OpenTofu. Terraform в 2023 году сменил лицензию с открытой на BUSL: пользоваться можно, а строить на нём конкурирующий продукт нельзя. В ответ сообщество под крылом Linux Foundation сделало OpenTofu, форк с открытой лицензией, совместимый по языку, провайдерам и state; расходятся они на новых возможностях (у OpenTofu раньше появилось шифрование state), и модуль, написанный сегодня, работает у обоих. Для компании выбор между ними это вопрос лицензионной политики и того, какой из них поддерживает выбранная платформа запуска, а не языка.

CloudFormation. Родной для AWS, state держит сам AWS, откат при ошибке делает автоматически. Только AWS, новые сервисы появляются с задержкой, шаблоны многословны, а типовые операции (циклы, условия) выражаются хуже, чем в HCL.

CDK. Тот же CloudFormation, но пишется на TypeScript, Python или Java с конструктами высокого уровня, где одна строка разворачивается в десяток ресурсов с разумными умолчаниями. Хорош для команд, которые живут в AWS и пишут на этих языках; цена та же, что у CloudFormation, плюс слой абстракции, через который иногда приходится смотреть на синтезированный шаблон.

Pulumi. Настоящие языки программирования, как в CDK, но для любого провайдера, и со своим state, который либо в их облаке, либо в вашем бакете. Берут, когда команда хочет тесты, типы и переиспользование как в обычном коде и работает не только с AWS.

Критерии по порядку важности. Провайдеров больше одного или это возможно через три года: Terraform или OpenTofu, либо Pulumi. Команда из разработчиков, а не из инженеров инфраструктуры, и код на одном языке: CDK в AWS, Pulumi вне его. Нужен state без своей эксплуатации и автоматический откат: CloudFormation. Найм и готовые модули: у Terraform самое большое сообщество и реестр модулей, и это аргумент, пока остальное равно. Смешивать можно: Terraform для сети и учётных записей, CDK для сервисов одной команды, но каждый ресурс принадлежит одному инструменту, иначе они спорят за него через drift.

Коротко

  • Инфраструктура как код: описание желаемого состояния в файлах, история и ревью в git, одинаковые окружения из одних файлов.
  • Декларативные инструменты (Terraform, CloudFormation) описывают что, а не как; CDK пишется как программа, но синтезируется в тот же декларативный шаблон.
  • State это память инструмента о том, что он создал; у Terraform это файл, который делят и блокируют, у CloudFormation его держит AWS. В файле состояния лежат все значения атрибутов, включая пароли: это секрет, пометка sensitive его не шифрует, а права на хранилище состояния равны правам на пароли.
  • Повторный apply без изменений кода ничего не делает; ручные правки дают дрейф, который ловят plan -refresh-only и проверкой дрейфа стека. Идемпотентность ломают несравниваемые свойства, генерируемые значения и внешние изменения: лечится ignore_changes, а необратимое защищают prevent_destroy.
  • Существующее заводят под управление импортом: блок import с генерацией кода, пустой план как критерий, зависимости раньше зависимых, список неуправляемого явно. Существующую инфраструктуру заводят импортом: по одному ресурсу, доводя план до «изменений нет», начиная с простого и защищая важное от удаления; всё новое сразу в коде, старое — по мере касания.
  • Конвейер проверяет форму, линтер, сканер политик, план, стоимость изменений и тесты модулей; каждый шаг останавливает слияние.
  • Выбор инструмента: Terraform или OpenTofu и Pulumi для нескольких провайдеров, CDK для команд разработчиков в AWS, CloudFormation за state и откат у AWS; каждый ресурс принадлежит одному инструменту.
  • Инструмент создаёт ресурсы, но не настраивает машину изнутри и не выкатывает приложение: эти задачи закрывают образ машины, сборка контейнера и конвейер доставки.
  • Цена подхода: полдня на первый ресурс, ревью вместо быстрых правок, дисциплина версий и состояние как новая сущность; для одноразового эксперимента это не нужно.

Что почитать дальше