Жизненный цикл релиза и ревизии

Собираем полную картину жизни релиза: от первой установки до удаления и как Helm решает, что именно менять в кластере.

Релиз проходит состояния pending-installdeployed → (pending-upgradedeployed)* → uninstalled. Каждая успешная операция фиксируется новой ревизией со снимком.

Что вообще такое релиз и зачем ему ревизии

Релиз — это конкретная установка чарта в конкретный namespace под конкретным именем. Один и тот же чарт можно установить десятки раз: web-prod, web-staging, web-pr-142 — это три независимых релиза с собственной историей. Helm не хранит состояние в каком-то внешнем сервере: вся информация о релизе лежит прямо в кластере, по умолчанию в виде Secret-ов в том же namespace (драйвер хранилища настраивается через HELM_DRIVER — secret, configmap или sql). Именно поэтому два инженера с одним kubeconfig видят одну и ту же историю: источник истины — кластер, а не локальная машина.

Каждая ревизия — это полный снимок того, что Helm применил: отрендеренные манифесты, использованные values и метаданные чарта. Снимок полный, а не дельта, и в этом смысл: для отката Helm не пересчитывает прошлое состояние, а берёт готовый снимок ревизии N и применяет его как новый. Поэтому откат — это, строго говоря, не «шаг назад», а «накат старого снимка под новым номером ревизии». Понимание этой детали снимает кучу вопросов: почему после rollback номер ревизии не уменьшается, а растёт, и почему откат тоже создаёт запись в истории.

Состояния релиза

СтатусЧто значит
pending-installinstall запущен, ещё не завершён
deployedтекущая активная ревизия, всё ок
pending-upgradeupgrade в процессе
failedоперация упала
supersededревизия заменена более новой
uninstalledрелиз удалён (виден при --keep-history)

Что делает upgrade «под капотом»: трёхстороннее слияние

Самый важный механизм. При upgrade Helm не просто применяет новые манифесты — он вычисляет трёхстороннее слияние (three-way merge) между:

  • old — манифесты предыдущей ревизии (что Helm развернул в прошлый раз),
  • new — манифесты новой ревизии (что Helm хочет развернуть),
  • live — текущее состояние объектов в кластере (вдруг кто-то правил руками).

Helm считает разницу и применяет только её. Это объясняет два важных поведения: (1) поля, которые исчезли между old и new, Helm удалит из ресурса; (2) ручные изменения (live ≠ old) Helm учитывает начиная с версии 3 — раньше двухстороннее слияние их затирало.

Зачем вообще нужен третий участник — live? Представьте, что вы выкатили Deployment с двумя репликами, а затем HPA (автоскейлер) подкрутил их до десяти. В прошлой ревизии Helm записал replicas: 2, в новой — тоже replicas: 2 (вы это поле не трогали). Двухстороннее слияние сравнило бы old и new, увидело «изменений нет» и оставило бы кластер в покое — десять реплик уцелели бы. Трёхстороннее слияние ведёт себя так же в этом случае: поле не менялось между ревизиями, значит дельты по нему нет, и живое значение десять не перетирается. Но если бы вы сами поменяли в новой ревизии replicas на 4, дельта появилась бы, и Helm применил бы вашу волю поверх работы HPA. Это тонкий момент: Helm уважает то, что он явно не контролирует, но настаивает на том, что вы изменили намеренно.

Обратная сторона удаления исчезнувших полей — частый сюрприз новичков. Убрали из шаблона блок resources.limits? Helm не «забудет» про него, а активно вычистит лимиты из живого Deployment, потому что увидит: поле было в old, нет в new — значит надо удалить. Это правильно (чарт — источник истины), но неочевидно, если ждать, что «чего нет в шаблоне, того Helm не касается». Касается, если это поле он сам туда поставил в прошлый раз.

Когда three-way merge не спасает: kubectl apply --force и patch-аннотации

Three-way merge работает на уровне полей через стратегическое слияние JSON, и у него есть слепые зоны. Иммутабельные поля (например, spec.selector у Deployment или clusterIP у Service) изменить через upgrade нельзя — кластер отвергнет патч, и вы получите ошибку вида «field is immutable». Лечится это только пересозданием ресурса, чего Helm сам не делает из осторожности. Другой случай — ресурсы, которые активно правит сторонний контроллер: если оператор постоянно переписывает аннотации, ваш upgrade и его реконсиляция будут бесконечно «бодаться». В таких ситуациях либо выводят поле из-под управления Helm, либо договариваются, кто хозяин ресурса.

Идемпотентность

Повторный upgrade с теми же значениями всё равно создаёт новую ревизию, но diff пустой — кластер не меняется. Это нормально и безопасно. А вот ревизия инкрементируется всегда, поэтому в истории могут быть «пустые» апгрейды.

helm upgrade web ./chart -n prod -f prod.yaml   # rev 5, без изменений в кластере
helm history web -n prod

Ожидание готовности: --wait

По умолчанию Helm считает операцию успешной, как только API принял манифесты, не дожидаясь, пока поды реально поднимутся. Флаг --wait заставляет ждать готовности ресурсов (Deployment стал доступен, поды Ready) до --timeout:

helm upgrade --install web ./chart -n prod   -f prod.yaml --wait --timeout 5m --atomic

Связка --wait --atomic — золотой стандарт прод-деплоя: дождаться готовности, а при неудаче откатиться. Стоит разобрать, что именно делает --atomic, потому что это не магия, а вполне конкретный сценарий. Если upgrade с --atomic не уложился в --timeout или какой-то ресурс так и не стал готов, Helm сам запускает rollback на предыдущую успешную ревизию и помечает неудавшуюся как failed. Без --atomic релиз застрял бы в состоянии failed с наполовину выкаченными ресурсами, и разгребать пришлось бы вручную. С --atomic вы возвращаетесь в заведомо рабочее состояние, как будто плохого деплоя и не было — почти. «Почти» — потому что хуки, успевшие отработать, не откатятся (об этом в следующем уроке), и побочные эффекты вроде уже отправленных уведомлений тоже останутся.

Здесь же кроется неприятная ловушка: застрявший pending-upgrade. Если процесс helm upgrade убили на середине (CI отвалился по таймауту, оборвалась сеть), релиз остаётся в состоянии pending-upgrade навсегда — Helm считает, что операция всё ещё идёт, и блокирует новые upgrade ошибкой «another operation is in progress». Лекарство — helm rollback на последнюю deployed-ревизию, что переводит релиз в чистое состояние, после чего можно деплоить снова.

Откат: когда и как

Откат пригождается, когда новая версия задеплоилась успешно (поды поднялись, --wait прошёл), но в проде обнаружилась логическая проблема, которую тесты не поймали. Команда helm rollback web 4 -n prod возьмёт снимок ревизии 4 и применит его как новую ревизию. Важная мысль: откат полезен ровно настолько, насколько детальна история. Если вы передеплоивали релиз с --history-max 1, откатываться будет некуда. И наоборот — если между «хорошей» и «плохой» версией прошло десять промежуточных апгрейдов, откат на конкретную ревизию вернёт ровно её values и манифесты, а не «среднее по больнице».

helm history web -n prod          # выбрать ревизию для отката
helm rollback web 4 -n prod --wait  # накатить снимок ревизии 4

Лимит истории

История ревизий растёт бесконечно, засоряя кластер Secret-ами. Ограничьте:

helm upgrade --install web ./chart -n prod   -f prod.yaml --history-max 10

Старые ревизии сверх лимита удаляются. По умолчанию лимит 10.

Как удаление учитывает порядок

При uninstall Helm удаляет ресурсы в порядке, обратном установке, и уважает веса и политику. Ресурсы с аннотацией helm.sh/resource-policy: keep не удаляются при uninstall — так защищают PVC с данными, чтобы случайный helm uninstall не стёр базу.

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

  • Ждать, что без --wait Helm дождётся подов. Он дождётся лишь приёма манифестов API; «deployed» ≠ «работает».
  • Ручной kubectl edit. Three-way merge учтёт его, но это всё равно дрейф; источником истины должен быть чарт.
  • Бесконечная история. Без --history-max копятся Secret-снимки.

Итог

  • Каждая операция — новая ревизия со снимком; статусы описывают фазу жизни релиза.
  • Upgrade считает трёхстороннее слияние (old/new/live) и применяет только разницу, учитывая ручные правки.
  • --wait --atomic --timeout — безопасный деплой; --history-max ограничивает историю; resource-policy: keep бережёт данные.
Проверьте себя
1. Что такое трёхстороннее слияние при upgrade?
AСлияние трёх чартов
BСравнение old (прошлая ревизия), new (новая) и live (текущее в кластере) для вычисления разницы
CОбъединение трёх namespace
DТри попытки apply
2. Что делает флаг --wait при upgrade?
AЗамедляет деплой намеренно
BЖдёт фактической готовности ресурсов (поды Ready) до --timeout
CЖдёт ввода пользователя
DНичего
3. Как защитить PVC от удаления при helm uninstall?
AНикак
BАннотацией helm.sh/resource-policy: keep на ресурсе
CУдалить из templates/
DФлагом --no-delete