Конфигурация Terraform проходит планирование и создаёт связанную облачную инфраструктуру с состоянием
DevOps

Terraform: инфраструктура как код, plan, state и безопасные изменения

Подробно разбираем Terraform: providers, resources, modules, plan и apply, state, locking, drift, import, CI/CD, secrets, moved blocks, for_each и восстановление.

Содержание

Terraform описывает инфраструктуру в конфигурации и сравнивает желаемое состояние с объектами провайдера. Вы объявляете сеть, виртуальную машину, DNS-запись или другой ресурс, а Terraform строит граф зависимостей и предлагает набор действий.

Главная сложность — не синтаксис HCL, а управление состоянием и изменениями. Ошибка адреса ресурса или общего state способна удалить production. Профессиональный процесс требует review plan, блокировки, разделения сред, минимальных прав и проверенного восстановления.

Provider связывает Terraform с API

Provider — плагин, который знает схему ресурсов и обращается к API облака, SaaS или другой системы. Terraform Core читает конфигурацию, строит граф и вызывает provider для чтения и изменения объектов.

Resource описывает управляемый объект. Data source только читает существующие данные для использования в конфигурации. Разница важна: data не даёт Terraform право владеть жизненным циклом объекта.

Provider имеет собственную версию независимо от Terraform CLI. Изменение provider может изменить схему, defaults и поведение. Указывайте допустимый диапазон в required_providers, а lock-файл .terraform.lock.hcl коммитьте.

Конфигурация описывает связи

Упрощённый пример показывает структуру AWS-ресурса. Он не готов к запуску: AMI зависит от региона, сеть и безопасность должны быть определены отдельно.

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

variable "instance_ami" {
  type        = string
  description = "Approved AMI identifier for the selected region"
}

resource "aws_instance" "web" {
  ami           = var.instance_ami
  instance_type = "t3.micro"

  tags = {
    Name       = "web-production"
    ManagedBy  = "terraform"
    Repository = "infrastructure"
  }
}

output "web_instance_id" {
  value = aws_instance.web.id
}

Ссылка aws_instance.web.id создаёт зависимость там, где другой ресурс её использует. Не добавляйте depends_on без нужды: явная ссылка уже формирует порядок, а лишняя зависимость уменьшает параллелизм и скрывает модель.

AMI передаётся переменной, но сама переменная не доказывает безопасность образа. Нужен процесс обновления, сканирования и одобрения. Размер instance из примера — только демонстрация синтаксиса, не рекомендация для нагрузки.

Основной цикл init, plan, apply

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

terraform fmt -check
terraform init
terraform validate
terraform plan -out=tfplan
terraform show tfplan
terraform apply tfplan

fmt -check проверяет формат, init загружает providers и настраивает backend, validate проверяет внутреннюю структуру. plan -out сохраняет бинарный план, а apply применяет именно его.

Файл tfplan может содержать чувствительные значения. Не публикуйте его как общедоступный artifact. После apply выполните прикладную проверку: доступность endpoint, метрики, права и журнал изменений.

Если plan показывает неожиданное destroy или replace, не применяйте. Разберите атрибут, вызвавший замену, адрес state и изменения provider. Флаг автоматического одобрения не исправляет опасный план.

State связывает адреса с объектами

Terraform должен помнить, что aws_instance.web соответствует конкретному ID у провайдера. Это соответствие хранится в state вместе с известными атрибутами и зависимостями.

State — чувствительный файл. Даже поле, отмеченное sensitive, лишь скрывается в части вывода; значение может оставаться в state. Поэтому:

  • используйте remote backend с шифрованием;
  • ограничьте чтение и изменение;
  • включите версии или snapshots;
  • применяйте locking;
  • журналируйте доступ;
  • не храните state в Git;
  • проверяйте восстановление отдельной копии.

Backend locking предотвращает одновременный apply одного state. Принудительное снятие lock допустимо только после подтверждения, что прежний процесс завершён. Иначе два процесса запишут расходящиеся состояния.

Разделение сред уменьшает радиус ошибки

Production и staging не должны делить один state. Используйте отдельные backend keys, каталоги или другие ясные границы. Terraform workspaces меняют state при одной конфигурации, но не всегда дают достаточную изоляцию прав и review.

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

Передавайте между state только необходимые outputs через контролируемый механизм. Не превращайте remote state в способ читать все секреты соседней команды.

Drift и refresh

Drift — расхождение между конфигурацией и фактом. Он возникает после ручной правки, автоматического изменения провайдером, аварийного восстановления или работы другого инструмента.

Plan обычно обновляет известные данные перед расчётом и показывает расхождение. Возможны три решения:

  1. вернуть объект к конфигурации;
  2. изменить конфигурацию согласно одобренному факту;
  3. перестать управлять объектом через Terraform.

Не исправляйте drift автоматическим apply без чтения. Ручное усиление firewall может быть аварийной мерой, и возврат к старому правилу снова откроет доступ.

Периодический read-only plan помогает обнаруживать drift, но его результаты нужно маршрутизировать ответственному, а credentials ограничивать.

Import принимает существующий ресурс

Import связывает существующий объект с адресом Terraform. Он не создаёт автоматически качественную конфигурацию. После импорта нужно описать аргументы, выполнить plan и добиться отсутствия неожиданных изменений.

Import сначала проверяют в копии state или отдельной среде. Неверный ID и адрес способны связать production-объект не с тем ресурсом.

Удаление resource из файла не означает «перестать управлять»: по умолчанию Terraform предложит destroy. Чтобы сохранить объект вне управления, применяют поддерживаемую операцию удаления адреса из state и фиксируют владельца. Манипуляции state требуют backup и peer review.

Рефакторинг без пересоздания

Переименование resource меняет его адрес. Без подсказки Terraform видит удаление старого и создание нового. moved сообщает соответствие:

moved {
  from = aws_instance.web
  to   = aws_instance.application
}

Сначала добавьте moved block и новое имя, изучите plan без destroy/create, примените, затем удаляйте compatibility-историю по принятой политике.

Переход от одиночного resource к for_each тоже меняет адрес. Стабильные ключи map безопаснее индексов count, когда список меняется: удаление элемента в середине count способно сдвинуть адреса остальных.

Lifecycle меняет опасные решения

prevent_destroy блокирует запланированное удаление конкретного ресурса. Это дополнительный барьер, но его можно убрать тем же commit, поэтому политика CI и права важнее.

create_before_destroy пытается создать замену раньше удаления. Это требует возможности одновременно иметь оба объекта: уникальное имя, квота и сеть могут помешать.

ignore_changes скрывает выбранный drift. Оно уместно, если атрибутом действительно управляет другой владелец, но широкое игнорирование маскирует важные изменения. Документируйте причину и границу ответственности.

Modules — интерфейс, а не копипаст

Module группирует ресурсы и предоставляет variables/outputs. Полезный module выражает устойчивую конструкцию, например приватную сеть с правилами, а не оборачивает один resource всеми его аргументами.

Версии внешних modules фиксируют. Перед обновлением читают diff и plan. Module имеет доступ к provider credentials через вызывающую конфигурацию, поэтому сторонний код равен доверенному исполняемому коду.

Не передавайте секрет через обычный output без необходимости. sensitive = true уменьшает случайный вывод, но значение остаётся доступным Terraform и может попасть в state.

CI/CD для Terraform

Pipeline для merge request выполняет format, validate, статические проверки и plan с read-доступом где возможно. Plan прикрепляют к MR в защищённом виде. Apply запускается после approval из защищённой ветки с отдельной ролью.

Не выдавайте pull request из fork production credentials. Разделяйте роль планирования и применения, используйте краткоживущую identity CI вместо долгого access key.

Одновременные MR строят планы относительно текущего state. После применения первого второй план устаревает; его нужно пересчитать перед apply. Сохранённый plan полезен, но не вечен.

Production apply должен иметь один координируемый путь. Ручной локальный запуск с административными ключами разрушает audit trail и блокировку процесса.

Ошибка apply и частичный результат

Terraform не выполняет распределённую транзакцию. Если создание третьего ресурса завершилось ошибкой, первые два могли уже появиться и попасть в state. Не запускайте destroy вслепую.

Сначала сохраните state и логи, выполните новый plan, проверьте фактические объекты и причину. Идемпотентный повтор часто продолжит работу, но только после понимания промежуточного состояния.

Rollback инфраструктуры не всегда означает применение старого commit. Если старый код требует удалить базу или изменить неизменяемый атрибут, «откат» разрушителен. Для данных нужен отдельный план: snapshots, совместимые миграции и восстановление.

Наблюдаемость и политика

Записывайте автора, commit, plan, время apply, изменённые ресурсы и результат smoke-теста. Сигналы облачного audit log связывайте с pipeline identity. Аномальная ручная операция должна быть заметна.

Policy as Code способна запретить публичный доступ, отсутствие тегов или нешифрованный диск. Политика проверяет формализованные признаки, но не понимает назначение системы целиком. Review остаётся обязательным.

Стоимость оценивают до apply через архитектуру и доступные инструменты, но estimate не гарантирует счёт: трафик и динамическое потребление зависят от эксплуатации.

Когда Terraform подходит

Terraform полезен для API-управляемой инфраструктуры с желанием review и повторяемости. Он хуже подходит к императивной настройке содержимого ОС — здесь часто применяют cloud-init, образ или Ansible.

Не управляйте одним объектом одновременно Terraform, консолью и другим IaC. Назначьте источник истины. Иногда нативный инструмент облака или Kubernetes operator лучше отражает жизненный цикл. Выбор делают по provider quality, состоянию, blast radius и способности команды восстановиться.

Источники

Рекламное местоВаша компания здесьРазместить рекламу

Самопроверка

Проверьте, что материал усвоен

Ответьте на все вопросы. Результат сохранится только в этом браузере и будет учтён в статистике прочитанных материалов.

01Почему ручное удаление ресурса создаёт drift?
02Зачем использовать moved block при переименовании адреса?

Разбираем коротко

Частые вопросы

Хранит ли Terraform реальные облачные ресурсы в state?

Нет. State хранит соответствие адресов конфигурации удалённым объектам и их известные атрибуты. Сами виртуальные машины, сети и базы остаются у провайдера.

Гарантирует ли сохранённый terraform plan точный результат apply?

Plan фиксирует рассчитанные действия для конкретной конфигурации и state, но удалённая среда может измениться, а часть значений определяется только при apply. План снижает неопределённость, но не отменяет проверку.

Можно ли коммитить terraform.tfstate?

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