Деплой и инфра
Infrastructure as Code: план, файл состояния и drift
Два инженера выкатывают хотфикс в пятницу. Оба запускают terraform apply со своих ноутбуков на один и тот же проект с разницей в девяносто секунд. Удалённого лока нет — состояние лежит в S3-бакете, но таблицу лока никто не подключил. Второй apply читает файл состояния, который первый ещё не дописал, считает план против полузаписанной реальности и пересоздаёт балансировщик, который уже существовал. Прод теряет соединения четыре минуты, а terraform.tfstate в бакете теперь расходится с тем, что реально есть в AWS. Авария была не из-за плохого конфига. Она была из-за двух писателей без лока.
Декларативно: ты описываешь пункт назначения, а не маршрут
Клики в облачной консоли — это императив: ты выполняешь шаги, а результат живёт только в провайдере и в твоей памяти. Infrastructure as Code переворачивает это: ты пишешь желаемое конечное состояние в файлах под версионным контролем (HCL для Terraform/OpenTofu, настоящий TypeScript/Go/Python для Pulumi), а инструмент сам выясняет шаги, чтобы туда прийти. Ты не говоришь «создай VPC, потом subnet, потом NAT gateway». Ты заявляешь, что эти ресурсы существуют с такими свойствами, а инструмент считает граф зависимостей и порядок.
В этом весь выигрыш. Поскольку желаемое состояние — это текст в git, твои окружения становятся воспроизводимыми (поднять идентичный staging из того же модуля), ревьюируемыми (изменения инфры идут через pull request как любой код) и аудируемыми (диф и история blame говорят, кто что и когда менял). Консоль не даёт ничего из этого — изменение там не оставляет ни ревью, ни дифа, ни истории, кроме тонкого audit log.
Цена декларативности — идемпотентность: запустить apply дважды должно сходиться к тому же результату, а не плодить дубликаты. Инструмент гарантирует это только потому, что помнит, что уже построил, — а для этого и нужен файл состояния.
Цикл plan/apply — это движок дифа
Ядро — две команды. terraform plan читает три вещи — твой конфиг (желаемое состояние), записанный файл состояния (что инструмент построил в прошлый раз) и реальность (он рефрешит её, опрашивая провайдер) — и печатает диф: что он создаст, изменит или удалит. Пока ничего не происходит. terraform apply исполняет этот диф и записывает новую реальность обратно в файл состояния.
Перечитай: plan и apply оба сначала делают refresh, согласовывая файл состояния с живым провайдером, прежде чем что-то считать. Именно на этом шаге refresh всплывает drift, и поэтому файл состояния — не опциональная бухгалтерия: это карта от символьных имён твоего конфига (aws_lb.web) к реальным id провайдера (arn:aws:elasticloadbalancing:...). Потеряй эту карту — и инструмент больше не знает, на какой реальный ресурс ссылается твой код.
Вход plan | Что он представляет | Если он неверен… |
|---|---|---|
Конфиг (файлы .tf) | Желаемое состояние — куда ты хочешь прийти | Plan предлагает не то изменение; ловится на ревью PR |
Файл состояния (terraform.tfstate) | Последняя известная реальность + карта id↔ресурс | Инструмент теряет реальные ресурсы → пересоздаёт или сиротит их |
| Refresh (запрос к живому провайдеру) | Фактическая реальность прямо сейчас | Drift появляется в плане как неожиданные изменения |
Файл состояния — это сердце и опасность
Всё хорошее в IaC проходит через файл состояния, и всё опасное — тоже. Три свойства заставляют сеньора обращаться с ним бережно.
Во-первых, он может хранить секреты в открытом виде. Terraform, OpenTofu и Pulumi сериализуют атрибуты ресурсов в состояние — поэтому если пароль базы, API-ключ или сгенерированный сертификат стал output или атрибутом, он лежит в файле незашифрованным по умолчанию. У любого с доступом на чтение этого бакета есть твои секреты. Митигация — никогда не гонять секреты через состояние как outputs: писать их сразу в secrets manager во время apply, а приложения пусть забирают их в рантайме. OpenTofu добавил встроенное шифрование состояния, чтобы это закалить; Pulumi шифрует секретные значения на каждый stack как first-class фичу.
Во-вторых, он должен жить в удалённом backend, а не на ноутбуке. Локальное состояние означает, что реальностью владеет один человек и команда не может работать совместно. Стандарт — удалённый backend (S3, GCS, облачный backend Terraform/OpenTofu) — с включённым versioning, чтобы повреждённое или обрезанное состояние можно было откатить к последней хорошей версии.
В-третьих, он должен быть залочен. Это то, что заканчивает инциденты.
Локинг: почему параллельные apply портят состояние
Запись в файл состояния не атомарна на уровне команды. Если два apply бегут одновременно, оба читают старое состояние, оба считают планы против него и оба пишут обратно — второй затирает первого, и теперь файл не описывает ни реальность. Фикс — лок: перед любой операцией записи backend берёт эксклюзивный лок (для S3 нативный lockfile через use_lockfile = true теперь дефолтный путь, а DynamoDB остаётся валидным легаси-механизмом), и любой второй apply ждёт или быстро падает с Error acquiring the state lock вместо гонки.
Сеньор знает острые края. terraform apply -lock=false отключает это и так ты воспроизводишь аварию из Hook нарочно. terraform force-unlock существует для устаревших локов от упавшего запуска — но запустить его, пока другой apply активно пишет, оставляет состояние полуобновлённым и повреждённым. Дисциплина в CI: concurrency group, чтобы джобы не пересекались, -lock-timeout (скажем 10m), чтобы легитимные запущенные apply ждали, а не падали, и plan после любого forced unlock, чтобы проверить консистентность состояния перед следующим apply.
Почему это работает
«Почему просто не дифить против живого облака каждый раз и не выбросить файл состояния?» Потому что refresh говорит только текущие атрибуты ресурсов, которые инструмент уже знает, — он никак не может узнать, что балансировщик с именем web в твоём аккаунте — это тот, которым управляет твой блок aws_lb.web, а не созданный руками или другой командой. Файл состояния — это карта идентичности. Без неё plan не отличит «измени этот ресурс» от «создай новый», и именно так отсутствующий файл состояния ведёт к дублирующейся инфраструктуре.
Drift: когда реальность уходит в сторону
Drift — это когда реальный мир расходится с файлом состояния, почти всегда потому, что кто-то поменял инфру out-of-band (клик в консоли, аварийный патч aws cli, другой инструмент). Следующий plan рефрешит, видит разницу и сообщает о ней. Теперь перед тобой решение сеньора: ручное изменение верное (тогда обнови конфиг, чтобы следующий apply его не откатил) или нежеланное (тогда дай apply восстановить заявленное состояние)?
Ловушка — тихий откат. Кто-то руками правит правило security group в инциденте в 2 ночи; никто не обновляет код; рутинный apply во вторник тихо удаляет правило, потому что его нет в желаемом состоянии. IaC всегда загоняет drift обратно к декларации — это и фича, и foot-gun. Детектируй его осознанно через terraform plan -refresh-only (безопаснее голого refresh, который перезаписывает состояние, не показывая тебе), в идеале по расписанию, чтобы drift ревьюили до того, как apply тихо его разрешит.
Более глубокое лекарство — immutable infrastructure: перестань править серверы руками и вместо этого заменяй их — запеки новый образ, выкати, уничтожь старый. Когда ничего не правится на месте, поверхности для drift гораздо меньше, а откат — это «выкати предыдущий образ», а не «вспомни каждую ручную правку».
Коллега сделал ручной фикс в консоли во время инцидента. Следующий terraform plan теперь показывает изменение, откатывающее его. Что делает сеньор?
Два инженера запускают terraform apply на один проект одновременно, без настроенного лока состояния. В чём ключевой риск?
Почему файл состояния обычно должен жить в удалённом backend, а не на ноутбуке инженера?
Расставь, что происходит во время одного безопасного terraform apply:
- 1 Взять удалённый лок состояния, чтобы никакой другой apply не мог писать параллельно
- 2 Refresh: опросить живой провайдер, чтобы обновить файл состояния текущей реальностью
- 3 Посчитать диф между желаемым конфигом и обновлённым состоянием — план
- 4 Исполнить план на провайдере, создавая/меняя/уничтожая ресурсы
- 5 Записать новую реальность обратно в файл состояния и отпустить лок
- 01Объясни коллеге, почему файл состояния — одновременно источник истины и крупнейшая опасность в Terraform-сетапе.
- 02Что такое drift, как детектировать его безопасно и почему рутинный apply может сделать его опасным?
Infrastructure as Code заменяет клики в консоли версионными декларациями желаемого состояния, делая окружения воспроизводимыми, ревьюируемыми и аудируемыми. Движок — это диф: plan рефрешит против живого провайдера, сравнивает твой конфиг с записанным файлом состояния и показывает, что он создаст, изменит или уничтожит; apply исполняет этот диф и пишет новую реальность обратно. Файл состояния — карта идентичности от твоего конфига к реальным id ресурсов, что делает его источником истины и в равной мере опасностью: он может хранить секреты в открытом виде, он может повредиться, а параллельные записи гонятся. Поэтому он живёт в версионном залоченном удалённом backend, никогда не несёт секреты, которые можно вместо этого отправить в secrets manager, и с ним обращаются как с продовой базой. Drift — реальность, уходящая в сторону после ручного изменения, — всплывает в следующем plan; детектируй его осознанно через refresh-only и решай намерение до apply, потому что IaC всегда согласует обратно к декларации и может тихо откатить аварийный фикс. Склоняйся к immutable infrastructure, чтобы дрейфить было нечему изначально.