Дрифт, refresh и обнаружение расхождений

Дрифт (drift) — это расхождение между тем, что записано в state, и тем, что реально в облаке. Кто-то поправил ресурс вручную — и Terraform об этом ещё не знает.

Дрифт неизбежен там, где у людей есть доступ в консоль. Вопрос не «случится ли он», а «обнаружите ли вы его до того, как Terraform что-нибудь сломает, выравнивая реальность под код».

Представьте: state говорит, что у инстанса тип t2.micro, но кто-то в панике увеличил его до t3.large через консоль во время инцидента. Теперь реальность ≠ state. Это и есть дрифт. Если запустить apply вслепую, Terraform вернёт t2.micro обратно — и снова уронит сервис.

Обнаружение дрифта

# опросить реальность и показать ТОЛЬКО дрифт, без изменений
terraform plan -refresh-only

# применить обновление state под реальность (с подтверждением)
terraform apply -refresh-only

# старая команда terraform refresh -- DEPRECATED с 0.15.4
# обновляла state молча, без показа diff -- опасно

Команда terraform refresh устарела: она тихо переписывала state под реальность, не показав, что именно изменилось. Современный способ — plan -refresh-only: он опрашивает облако и показывает расхождения, но ничего не применяет. А apply -refresh-only позволяет осознанно принять реальность в state.

Как работает под капотом

Обнаружение дрифта — это сравнение трёх словарей: код, state, реальность. refresh-only сравнивает state с реальностью; обычный plan добавляет ещё и код. Смоделируем все три источника:

code    = {"type": "t2.micro", "tags": {"env": "prod"}}
state   = {"type": "t2.micro", "tags": {"env": "prod"}}
reality = {"type": "t3.large", "tags": {"env": "prod"}}   # кто-то поменял вручную!

def drift(state, reality):
    return {k: (state.get(k), reality.get(k))
            for k in set(state) | set(reality) if state.get(k) != reality.get(k)}

def plan(code, reality):
    return {k: (reality.get(k), code.get(k))
            for k in set(code) | set(reality) if code.get(k) != reality.get(k)}

print("ДРИФТ (state vs реальность):", drift(state, reality))
print("PLAN  (код vs реальность):  ", plan(code, reality))
print("-> apply вернёт type обратно в t2.micro")

«Попробуй сам ▶» — дрифт показывает, что реальность ушла; обычный plan показывает, что apply вернёт всё под код. Иногда это не то, что вам нужно.

Частые ошибки

  • Слепой apply при дрифте. Terraform молча откатит ручные правки под код — и снова сломает то, что чинили вручную.
  • Использовать deprecated refresh. Он меняет state без показа diff — легко не заметить тихую перезапись.
  • Игнорировать дрифт. Накопившиеся ручные правки превращают каждый apply в лотерею.

Best practices

  • Регулярно прогоняйте plan -refresh-only (в CI по расписанию) — раннее обнаружение дрифта.
  • Если ручная правка верна — перенесите её в код, затем apply. Если нет — пусть Terraform выровняет.
  • Ограничьте доступ людей в консоль на запись: меньше рук — меньше дрифта.

Разбор глубже

Дрифт бывает разной природы, и реакция на него отличается. Бывает аварийный дрифт: во время инцидента инженер вручную увеличил инстанс, чтобы пережить нагрузку. Бывает дрифт от другого инструмента: автоскейлер сам меняет число серверов. А бывает дрифт от чужой команды, которая трогает общий ресурс. Для каждого случая стратегия своя: аварийную правку обычно переносят в код и закрепляют; изменения автоскейлера игнорируют через lifecycle { ignore_changes = [...] }, чтобы Terraform не воевал с ним; а несанкционированные правки откатывают обычным apply.

На уровне процесса зрелые команды настраивают периодическое обнаружение дрифта: по расписанию (например, ночью) CI прогоняет plan -refresh-only по всем окружениям и присылает отчёт, если реальность разошлась с кодом. Это превращает дрифт из неприятного сюрприза во время следующего деплоя в управляемый сигнал, который команда видит заранее. Альтернатива — узнать о накопившихся ручных правках в самый неподходящий момент, когда обычный apply внезапно захочет откатить десяток изменений, о которых никто не помнит.

Имеет смысл различать и то, что Terraform может обнаружить, и то, что ему недоступно. Дрифт по управляемым полям ресурса (тип инстанса, правила группы безопасности) он увидит при refresh. Но изменения вне его модели — например, файлы внутри сервера или данные в базе — не отслеживаются вовсе, потому что Terraform управляет формой инфраструктуры, а не её содержимым. Это здоровое разделение ответственности: за форму отвечает Terraform, за содержимое — другие инструменты (конфигурационный менеджмент, миграции, приложение). Понимание этой границы избавляет от ложных ожиданий, будто refresh покажет вообще любое изменение в системе.

Итог: дрифт — расхождение state и реальности; plan -refresh-only безопасно его показывает, а старый refresh устарел. Решайте осознанно: вернуть под код или принять в код. Дальше — операции над самим state.

Проверьте себя
1. Что такое configuration drift?
AОшибка синтаксиса в HCL
BРасхождение между state и реальной инфраструктурой (например, ручная правка в консоли)
CЗамедление apply
DКонфликт версий провайдера
2. Какая команда безопасно показывает дрифт, ничего не меняя?
Aterraform apply
Bterraform plan -refresh-only
Cterraform destroy
Dterraform refresh