Terraform и Ansible: state, идемпотентность и границы ответственности

Блок про инфраструктуру как код, где почти всегда спрашивают про state — и почти всегда на нём же спотыкаются.

Вопрос: «Зачем Terraform хранит state? Что произойдёт, если два инженера применят конфигурацию одновременно, и как этого избежать?»

Что на самом деле проверяет интервьюер

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

Развёрнутый ответ: зачем нужен state

Terraform сопоставляет три вещи: ваш код (желаемое состояние), state-файл (что Terraform создавал в прошлый раз) и реальность в облаке (её он получает через API при refresh). План — это разница между ними.

Без state Terraform не знал бы, что aws_instance.web из кода — это конкретная машина i-0abc123. Он бы либо создавал дубликаты, либо не мог удалять: удаление ресурса из кода означает «удали то, что раньше было создано по этому адресу», а узнать это можно только из state. Дополнительно state хранит зависимости между ресурсами (нужно для корректного порядка удаления) и кэширует атрибуты, чтобы не опрашивать API по каждому полю.

terraform plan      # показать разницу: код ↔ state ↔ реальность
terraform apply     # привести реальность к коду и обновить state

terraform state list                       # что Terraform считает своим
terraform import aws_instance.web i-0abc    # взять под управление существующий ресурс
terraform state rm aws_instance.web         # забыть ресурс, НЕ удаляя его в облаке

Проблема одновременного применения

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

Решение — remote backend с блокировкой. State хранится централизованно (S3, GCS, Terraform Cloud), а на время apply берётся эксклюзивная блокировка: второй инженер получит сообщение об ожидании вместо гонки.

terraform {
  backend "s3" {
    bucket         = "acme-tfstate"
    key            = "prod/network/terraform.tfstate"
    region         = "eu-central-1"
    encrypt        = true
    use_lockfile   = true
  }
}

Что ещё стоит сказать про state, чтобы ответ выглядел зрелым:

  • State содержит секреты в открытом виде — пароли БД, приватные ключи, токены попадают туда как атрибуты ресурсов. Поэтому обязательны шифрование бакета, строгий доступ и запрет коммитить state в git.
  • Включите версионирование бакета. Испорченный state восстанавливается откатом на предыдущую версию — это самая частая аварийная процедура.
  • Разделяйте state по окружениям и доменам (сеть, кластер, приложения). Один гигантский state означает долгий plan и общий радиус поражения при ошибке.

Вопрос 2: что такое идемпотентность

Идемпотентность — свойство операции давать один и тот же результат независимо от того, применили её один раз или десять. Второй terraform apply без изменений в коде должен сказать «No changes» и ничего не тронуть.

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

- name: Установить и настроить nginx
  hosts: web
  become: true
  tasks:
    - name: Пакет установлен
      ansible.builtin.package:
        name: nginx
        state: present            # идемпотентно: повторный запуск ничего не делает

    - name: Конфиг на месте
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        mode: "0644"
      notify: reload nginx        # handler сработает ТОЛЬКО при изменении файла

    - name: Сервис запущен и в автозагрузке
      ansible.builtin.service:
        name: nginx
        state: started
        enabled: true

  handlers:
    - name: reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Главный враг идемпотентности — модуль shell или command: Ansible не знает, что делает ваша команда, и выполнит её при каждом запуске. Отсюда правило: использовать специализированные модули, а если без shell никак — добавлять creates:, removes: или changed_when:.

Вопрос 3: Terraform или Ansible

Ловушка для тех, кто считает их конкурентами. Они решают разные задачи и на практике работают в связке.

КритерийTerraformAnsible
Подходдекларативныйпроцедурный (с идемпотентными шагами)
Задачаprovisioning: создать сети, кластеры, базы, балансировщикиconfiguration management: настроить систему внутри машины
Состояниехранит statestate не хранит, сверяется с фактом на хосте
Агентобращается к API провайдерапо SSH, агент не нужен
Удалениеумеет (destroy)только если вы явно описали

Типичная связка: Terraform поднимает VPC, узлы и managed-базы, Ansible доводит конфигурацию на машинах. В мире Kubernetes роль Ansible часто берут на себя Helm и GitOps-инструменты (Argo CD, Flux), а Terraform остаётся для облачных ресурсов вокруг кластера. Отдельно стоит упомянуть подход immutable infrastructure: вместо донастройки живых серверов пересобирается образ (Packer) и заменяется целиком — тогда configuration management нужен только на этапе сборки образа.

Типичные ошибки кандидатов

  • Держат state локально или коммитят его в git — с секретами внутри.
  • Не знают про блокировку и не могут объяснить, что случится при параллельном apply.
  • Правят ресурсы руками в консоли облака, а потом удивляются дрейфу конфигурации и агрессивному плану.
  • Путают terraform state rm (забыть ресурс) и terraform destroy (удалить его на самом деле).
  • Пишут плейбуки на сплошных shell-командах, теряя идемпотентность.
  • Противопоставляют Terraform и Ansible, вместо того чтобы объяснить разделение зон ответственности.

Как ответить кратко (20–30 секунд)

«State — это карта соответствия между ресурсами в коде и реальными объектами у провайдера, плюс их зависимости. Без него Terraform не смог бы понять, что удалять и что обновлять, и плодил бы дубликаты. Если два инженера применяют одновременно, записи затирают друг друга и появляются потерянные ресурсы — поэтому state держим в remote backend с блокировкой, с шифрованием и версионированием, никогда не в git: там лежат секреты открытым текстом. Идемпотентность означает, что повторный apply без изменений ничего не делает; в Ansible это достигается модулями «приведи к состоянию» вместо голых shell-команд. Terraform создаёт инфраструктуру, Ansible настраивает систему внутри — они дополняют друг друга.»

Проверьте себя
1. Зачем Terraform нужен state-файл?
AЧтобы кэшировать скачанные провайдеры и ускорять plan
BЧтобы сопоставлять ресурсы из кода с реальными объектами у провайдера, хранить их зависимости и понимать, что нужно изменить или удалить
CЧтобы хранить историю всех выполненных команд apply
DЧтобы шифровать переменные окружения
2. Почему state-файл нельзя коммитить в git?
AОн слишком велик для системы контроля версий
BОн содержит атрибуты ресурсов, включая пароли и ключи, в открытом виде, а параллельная работа требует блокировки, которой git не даёт
CФормат state несовместим с текстовым diff
DTerraform отказывается работать со state внутри репозитория
3. Какая задача в связке решается Ansible, а не Terraform?
AСоздать VPC, подсети и managed-базу в облаке
BНастроить пакеты, конфиги и сервисы внутри уже существующей машины
CУдалить всё окружение одной командой
DХранить состояние облачных ресурсов между запусками