Жизненный цикл релиза и ревизии
Собираем полную картину жизни релиза: от первой установки до удаления и как Helm решает, что именно менять в кластере.
Релиз проходит состояния
pending-install→deployed→ (pending-upgrade→deployed)* →uninstalled. Каждая успешная операция фиксируется новой ревизией со снимком.
Что вообще такое релиз и зачем ему ревизии
Релиз — это конкретная установка чарта в конкретный namespace под конкретным именем. Один и тот же чарт можно установить десятки раз: web-prod, web-staging, web-pr-142 — это три независимых релиза с собственной историей. Helm не хранит состояние в каком-то внешнем сервере: вся информация о релизе лежит прямо в кластере, по умолчанию в виде Secret-ов в том же namespace (драйвер хранилища настраивается через HELM_DRIVER — secret, configmap или sql). Именно поэтому два инженера с одним kubeconfig видят одну и ту же историю: источник истины — кластер, а не локальная машина.
Каждая ревизия — это полный снимок того, что Helm применил: отрендеренные манифесты, использованные values и метаданные чарта. Снимок полный, а не дельта, и в этом смысл: для отката Helm не пересчитывает прошлое состояние, а берёт готовый снимок ревизии N и применяет его как новый. Поэтому откат — это, строго говоря, не «шаг назад», а «накат старого снимка под новым номером ревизии». Понимание этой детали снимает кучу вопросов: почему после rollback номер ревизии не уменьшается, а растёт, и почему откат тоже создаёт запись в истории.
Состояния релиза
| Статус | Что значит |
pending-install | install запущен, ещё не завершён |
deployed | текущая активная ревизия, всё ок |
pending-upgrade | upgrade в процессе |
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 не стёр базу.
Частые ошибки
- Ждать, что без
--waitHelm дождётся подов. Он дождётся лишь приёма манифестов API; «deployed» ≠ «работает». - Ручной
kubectl edit. Three-way merge учтёт его, но это всё равно дрейф; источником истины должен быть чарт. - Бесконечная история. Без
--history-maxкопятся Secret-снимки.
Итог
- Каждая операция — новая ревизия со снимком; статусы описывают фазу жизни релиза.
- Upgrade считает трёхстороннее слияние (old/new/live) и применяет только разницу, учитывая ручные правки.
--wait --atomic --timeout— безопасный деплой;--history-maxограничивает историю;resource-policy: keepбережёт данные.