2026 г.

Инфраструктура как код

Проект ЦИТадель

Облачные API позволяют создавать сети, виртуальные машины и управляемые сервисы программно, а Kubernetes показывает, как контроллер приводит фактическое состояние к желаемому. Инфраструктура как код (Infrastructure as Code, IaC) переносит на инфраструктуру инженерные практики разработки: описание изменений в файлах, контроль версий, ревью, автоматические проверки и воспроизводимый процесс применения. Вместо последовательности ручных действий команда хранит проверяемое описание и использует инструмент для вычисления и выполнения необходимых операций.

Код не становится единственным источником сведений об инфраструктуре. Для работы также нужны состояние инструмента, версии провайдеров и модулей, образы, секреты и данные внешних систем. Поэтому IaC даёт воспроизводимый процесс, но не обещает, что один и тот же файл через несколько лет создаст побитово одинаковый результат. Разберём границы этого обещания, два основных класса инструментов, состояние и дрейф, GitOps и способы ограничить риск автоматизации. Практикум выполняется локально, без облачного аккаунта.

1. Серверы-снежинки и конфигурационный дрейф

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

IaC переносит желаемую конфигурацию из памяти людей и пошаговых инструкций в версионируемое описание. Изменение можно обсудить до применения, связать с задачей и повторить на другой площадке. Однако история Git не является резервной копией инфраструктуры: возврат старой версии кода не восстанавливает удалённый диск, данные базы или прежнюю версию внешнего API. Для воспроизводимости нужны зафиксированные зависимости и артефакты, а для восстановления — отдельные резервные копии и проверенная процедура.

2. Два жанра инструментов

В IaC полезно различать два основных класса задач:

  • Управление ресурсами (provisioning) создаёт и связывает виртуальные машины, сети, балансировщики, управляемые базы и права доступа. OpenTofu и Terraform читают декларации, строят граф зависимостей и через провайдеры обращаются к API целевых систем. OpenTofu возник как открытый форк Terraform; форматы и провайдеры во многом совместимы, но совместимость конкретных версий следует проверять, а не считать бессрочной.
  • Управление конфигурацией (configuration management) устанавливает пакеты, размещает файлы, управляет пользователями и службами внутри уже существующей системы. Ansible обычно подключается по SSH, WinRM или сетевому API и выполняет упорядоченные задачи. Многие модули описывают конечное состояние и идемпотентны, но модули command, shell, сторонние модули и сам плейбук таких свойств автоматически не получают.

Граница выражается вопросами «что существует?» и «как настроено содержимое?», но она не абсолютна: Ansible умеет создавать облачные ресурсы, а провайдеры OpenTofu и Terraform — управлять отдельными настройками сервисов. Выбор определяется жизненным циклом объекта и тем, какой инструмент будет его единственным владельцем. Сборка образов и декларации Kubernetes образуют соседние слои. Контейнеры и управляемые сервисы уменьшают объём настройки машин, но конфигурационное управление остаётся нужным для ОС, специализированных служб и наследуемых систем.

3. Декларативность, план, идемпотентность

IaC не обязательно декларативен: программу, которая вызывает облачный SDK, тоже можно версионировать и проверять. Однако OpenTofu и Terraform в основном используют декларативную модель, а Ansible сочетает описания конечного состояния с явным порядком задач. Для безопасного применения важны три свойства:

  • Декларация описывает управляемое желаемое состояние и связи между объектами. Инструмент определяет порядок операций по графу зависимостей. Неподконтрольные данные, вспомогательные императивные команды (provisioners) и побочные эффекты внешних API остаются за границей этой модели.
  • План сопоставляет конфигурацию, предыдущее состояние и прочитанные провайдером свойства реальных объектов. Он показывает предполагаемые создания, изменения, замены и удаления. Часть значений может оставаться неизвестной до применения, а реальность между планом и применением способна измениться. Спекулятивный план из ревью нужно пересчитать; для автоматизации создают сохранённый план и применяют именно его. Сам файл плана защищают наравне с состоянием, поскольку он может содержать чувствительные значения.
  • Идемпотентность и сходимость означают, что после достижения желаемого состояния следующий запуск не должен вносить изменений. Это требование к декларации, провайдерам и используемым модулям, а не безусловная гарантия инструмента. Изменяемые источники данных, нестабильные API и императивные команды могут давать новый результат при каждом запуске, поэтому применять «на всякий случай» без чтения плана нельзя.

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

4. Состояние и дрейф

Декларация говорит о ресурсе service.web, а облако — об объекте с конкретным идентификатором. Соответствие, последние известные атрибуты и служебные данные хранятся в состоянии (state). Отсюда следуют несколько правил:

  • Командное состояние хранят в удалённом хранилище (backend) с контролем доступа, шифрованием, версионированием и резервным восстановлением. Блокировку нужно проверить отдельно: хранилище лишь предоставляет такую возможность, и не каждый вариант её поддерживает. Окружения и независимые домены изменений обычно получают разные состояния и учётные данные, чтобы ошибка одного применения не затронула остальные.
  • Состояние и сохранённые планы считаются чувствительными. Пометка sensitive скрывает значение в обычном выводе, но не обязательно удаляет его из этих файлов. Возможность вообще не сохранять секрет зависит от версии инструмента и схемы провайдера. OpenTofu умеет шифровать состояние и планы на стороне клиента; для Terraform защита удалённого состояния определяется выбранным хранилищем. Ключи шифрования тоже требуют резервирования и ротации.
  • Дрейф обнаруживается в пределах модели провайдера. Обычный план обновляет представление об уже управляемых объектах и может показать ручную правку. Он не найдёт неизвестный состоянию ресурс, проигнорированный атрибут или изменение, которое API не возвращает. План -refresh-only показывает, как фактическое состояние расходится с записанным, но не решает, следует ли вернуть объект к коду или принять аварийное изменение в код.

Для аварийных ручных действий используют регламент аварийного доступа break-glass: ограниченный временный доступ, журналирование, последующее ревью и явное согласование кода с выбранным итоговым состоянием. Периодические планы помогают искать дрейф, но должны выполняться с минимальными правами и не применяться автоматически только потому, что нашли различие.

5. Как это структурируют

Повторяемые блоки выделяют в модули с небольшим интерфейсом и явными входами и выходами. Модуль не должен скрывать всю платформу за десятками переключателей: композиция нескольких понятных модулей обычно легче проверяется. Версии внешних модулей и провайдеров ограничивают, выбранные версии провайдеров и контрольные суммы фиксируют в .terraform.lock.hcl. Образы также задают неизменяемым хешем (digest) или иным контролируемым идентификатором; одной фиксации HCL недостаточно.

Dev, stage и prod могут использовать общие версионируемые модули, но обычно имеют отдельные корневые конфигурации, состояния, учётные данные и правила одобрения. Это позволяет продвигать проверенную версию модуля между окружениями, не связывая их одним применением. Различия параметров должны быть явными и оправданными, а не спрятанными в копиях файлов.

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

6. GitOps: круг замыкается

OpenGitOps формулирует четыре свойства: желаемое состояние выражено декларативно; его версии неизменяемы и сохраняют историю; агент автоматически получает декларации из источника; агент непрерывно наблюдает фактическое состояние и пытается его согласовать. Git — обычный, но не единственно возможный источник версий. Argo CD и Flux реализуют этот подход прежде всего для Kubernetes.

Слияние изменения означает только появление новой желаемой версии. Контроллер ещё должен получить её и успешно применить, а проверки — подтвердить работоспособность. В Argo CD автоматическая синхронизация, удаление исчезнувших из Git объектов (prune) и исправление ручного дрейфа (selfHeal) настраиваются отдельно. У каждого режима свой риск: например, ошибочное удаление файла при включённом prune становится удалением ресурса.

git revert создаёт новую декларацию с прежним содержимым, но не гарантирует обратимость операции. Схема базы, удалённые данные и несовместимый образ требуют собственной стратегии возврата или движения вперёд. GitOps также не отменяет административный доступ: контроллер сам обладает значительными правами, а людям нужен контролируемый аварийный путь. Поэтому защищают репозиторий, учётные данные и цепочку поставки деклараций.

Применение OpenTofu или Terraform конвейером после слияния использует те же практики версионирования и ревью, но обычно остаётся дискретным push-процессом, а не GitOps в строгом смысле с автоматическим получением и непрерывным согласованием. Эти модели можно сочетать: один слой создаёт платформу, другой управляет размещёнными на ней приложениями.

7. Ошибка тоже масштабируется

Автоматизация быстро и последовательно выполняет как правильные, так и ошибочные изменения. Защита строится несколькими независимыми слоями:

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

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

8. Практикум: IaC без облачного аккаунта

Для основной части понадобятся OpenTofu или Terraform и локальный Docker Engine. Ниже используются команды tofu; при работе с Terraform замените имя команды. Доступ к сокету Docker фактически даёт широкие полномочия на хосте, поэтому выполняйте опыт на отдельной машине или с rootless Docker и не подключайте сокет к недоверенным контейнерам.

Задание 1. Декларация, сохранённый план и применение. Создайте пустой каталог и файл main.tf:

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "4.5.0"
    }
  }
}

provider "docker" {}

variable "nginx_image" {
  type        = string
  description = "Неизменяемое имя образа с хешем sha256"
}

locals {
  instances = {
    blue  = 8080
    green = 8081
  }
}

resource "docker_image" "nginx" {
  name         = var.nginx_image
  keep_locally = true
}

resource "docker_container" "web" {
  for_each = local.instances
  name     = "citadel-iac-${each.key}"
  image    = docker_image.nginx.image_id
  must_run = true

  ports {
    ip       = "127.0.0.1"
    internal = 80
    external = each.value
  }
}

Сначала загрузите выбранный образ nginx и получите неизменяемое имя из RepoDigests:

$ docker pull nginx:alpine
$ docker image inspect nginx:alpine \
    --format '{{index .RepoDigests 0}}'

Запишите полученное имя в terraform.tfvars, например nginx_image = "nginx@sha256:...". Это отделяет декларацию от изменяемого тега. Затем выполните:

$ tofu init
$ tofu fmt -check
$ tofu validate
$ tofu plan -out=lab.plan
$ tofu show lab.plan
$ tofu apply lab.plan
$ curl http://127.0.0.1:8080/
$ curl http://127.0.0.1:8081/

Файл .terraform.lock.hcl фиксирует выбранный пакет провайдера и его контрольные суммы — его следует хранить вместе с конфигурацией. Каталог .terraform/, состояние и lab.plan в Git не добавляют. Даже в лаборатории план применяйте только после чтения.

Задание 2. Сходимость и дрейф. Повторный tofu plan -detailed-exitcode должен завершиться кодом 0 и сообщить об отсутствии изменений; код 2 означает непустой план, код 1 — ошибку. Остановите один контейнер командой docker stop citadel-iac-green. Новый план должен увидеть изменение must_run, а применение — снова запустить контейнер. Затем удалите его командой docker rm -f citadel-iac-green: план предложит создать отсутствующий объект. Проверьте восстановление и объясните, почему неизвестный состоянию посторонний контейнер этот план не обнаружит.

Задание 3. Состояние и безопасный рефакторинг. Выполните tofu state list и tofu state show 'docker_container.web["blue"]'; исходный JSON вручную не редактируйте. Переименуйте блок ресурса из web в frontend. План воспримет новые адреса как другие объекты и предложит замену — не применяйте её. Добавьте документированное перемещение:

moved {
  from = docker_container.web
  to   = docker_container.frontend
}

Повторный план должен показать перемещение адресов без удаления контейнеров. Сохранение блока moved в репозитории делает рефакторинг воспроизводимым для других копий состояния, в отличие от локальной команды state mv.

Задание 4. Конфигурационное управление. На отдельной тестовой ВМ создайте плейбук Ansible, который устанавливает пакет, формирует конфигурацию модулем template и через обработчик (handler) перезапускает службу только при изменении файла. Сначала выполните ansible-playbook --syntax-check, затем --check --diff и обычный запуск. Второй обычный запуск должен дать changed=0. Измените управляемый файл вручную, снова запустите --check --diff и проверьте, что Ansible показывает ожидаемое исправление до применения. Не используйте для опыта рабочий сервер: установка пакета и перезапуск службы являются настоящими изменениями.

Задание 5*. GitOps. В локальном кластере kind или minikube установите Argo CD по официальной инструкции и создайте отдельный доступный ему репозиторий с небольшим Deployment. Для учебного приложения явно включите автоматическую синхронизацию, selfHeal: true и, только после проверки границ приложения, prune: true. Измените число реплик коммитом и дождитесь состояния Synced/Healthy. Затем выполните kubectl scale: при включённом selfHeal контроллер должен вернуть число реплик к декларации. Удалите объект из Git и отдельно зафиксируйте действие prune. Объясните, почему успешный merge ещё не доказывает успешную выкатку.

Задание 6*. Воспроизводимость и границы RTO. После сохранения наблюдений выполните tofu destroy, создайте новый план и восстановите два контейнера, измеряя время до успешных HTTP-проверок. Это время характеризует только восстановление описанных ресурсов. Оно не включает получение резервных данных, восстановление состояния из удалённого хранилища, DNS и внешние зависимости, поэтому не является RTO всей системы. В отчёте перечислите эти исключения и предложите, как включить их в следующее учение.

Итоги

  • IaC делает желаемую конфигурацию версионируемой и проверяемой, но воспроизводимость зависит также от состояния, версий инструментов, артефактов, секретов и внешних API.
  • Управление ресурсами отвечает преимущественно за существующие объекты, конфигурационное управление — за их содержимое; границу проводят по жизненному циклу и владению.
  • План — предварительное вычисление, а не гарантия результата. Сходимость зависит от декларации, провайдера и отсутствия неконтролируемых побочных эффектов.
  • Удалённое состояние требует проверенной блокировки, разграничения доступа, шифрования и восстановления. План показывает только тот дрейф, который видит провайдер у известных состоянию объектов.
  • Модули, отдельные состояния окружений, зафиксированные зависимости и отсутствие секретов в коде уменьшают расхождения и радиус ошибки.
  • GitOps требует декларативной, версионируемой конфигурации, автоматического получения и непрерывного согласования. merge и revert сами по себе не гарантируют выкатку и возврат данных.
  • Безопасность автоматизации обеспечивают независимые слои: минимальные права, проверки и политика, ревью плана, постепенная раскатка, наблюдаемость и испытанное восстановление.

Литература

  1. K. Morris, "Infrastructure as Code," 3rd ed., O'Reilly, 2025.
  2. OpenTofu: планирование, хранение и блокировка состояния, шифрование состояния и планов, фиксация зависимостей.
  3. Terraform: чувствительные и временные данные, рефакторинг модулей и провайдер Docker.
  4. Ansible: check mode и diff mode и свойства плейбуков.
  5. Принципы OpenGitOps и Argo CD: автоматическая синхронизация и self-heal.
  6. Материалы сайта: «Kubernetes: идея и устройство», «Облака: устройство и модели», «Облака на практике», «Наблюдаемость», «Непрерывность и восстановление».
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией