← Kubernetes: рабочие нагрузки
7 мин · средне · Урок 4 из 11

Rollback Deployment

Где Kubernetes хранит историю релизов и что на самом деле делает kubectl rollout undo с ReplicaSet.

Откуда берётся история

Deployment - объект Kubernetes, который держит нужное число одинаковых Pod. Pod - наименьшая единица запуска, внутри неё работает контейнер. ReplicaSet - объект, который следит за числом Pod.

Каждое обновление Deployment создаёт новый ReplicaSet. Старый не удаляется сразу. Он остаётся в кластере со значением replicas: 0. Набор таких неактивных ReplicaSet и есть история.

Сколько неактивных ReplicaSet хранится, задаёт поле spec.revisionHistoryLimit. По умолчанию это 10.

Посмотреть историю:

kubectl rollout history deploy/app
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

Каждая запись - отдельный ReplicaSet со своей версией шаблона Pod. Сам Deployment помнит активную ревизию в annotation deployment.kubernetes.io/revision. Annotation - произвольная пометка на объекте в формате ключ-значение.

Посмотреть конкретную ревизию:

kubectl rollout history deploy/app --revision=1

Команда покажет полный шаблон того обновления: образ, env, ports.

Что делает kubectl rollout undo

kubectl - командная утилита для управления Kubernetes. Без аргументов команда undo откатывает на предыдущую ревизию N-1:

kubectl rollout undo deploy/app

С указанием конкретной ревизии:

kubectl rollout undo deploy/app --to-revision=1

После отката Deployment запускает обычный плавный переход в обратную сторону. Битый ReplicaSet сжимается, целевой растёт.

Глубже: что происходит с ReplicaSet при откате

тот же объект, новый номер ревизии до отката RS №1 rev 1 · replicas 0 RS №2 rev 2 · replicas 3 активна плохая версия rollout undo RS №1 hash шаблона совпал RS №2 сжимается до нуля новый ReplicaSet не создаётся после отката RS №1 rev 3 · replicas 3 RS №2 rev 2 · replicas 0 revision 1 пропала из истории
ReplicaSet тот же самый, меняются только replicas и номер ревизии.

На практике достаточно того, что выше: undo вернул рабочий образ, Pod снова Running. Дальше идёт механика под капотом, если интересно, как именно Deployment хранит ревизии.

Это место путает почти всех. Кажется, что откат создаёт новый ReplicaSet с нуля. На деле Deployment переиспользует уже существующий старый ReplicaSet.

Что происходит по шагам:

  1. Активен ReplicaSet №2 (revision=2, плохая версия).
  2. Выполняется rollout undo.
  3. Deployment копирует шаблон из revision=1 в свой spec.template.
  4. Hash (контрольная сумма шаблона) от этого шаблона совпадает с hash у ReplicaSet №1 - он ещё лежит в кластере, свёрнутый в ноль. Поэтому Deployment не создаёт новый RS, а масштабирует №1 обратно вверх.
  5. ReplicaSet №2 сжимается до 0. ReplicaSet №1 растёт до целевых replicas.
  6. ReplicaSet №1 получает новый номер ревизии: его аннотация deployment.kubernetes.io/revision меняется с 1 на 3.

В итоге rollout history покажет revision=2 (плохая) и revision=3 (откат). Revision=1 пропадёт из списка, но RS не удалён: это тот же самый ReplicaSet №1, просто теперь под номером 3.

Причина в том, как устроен Deployment. Он хранит актуальный spec.template и hash от него. Новый шаблон даёт новый hash и новый ReplicaSet. Откат возвращает старый шаблон, hash совпадает с уже существующим ReplicaSet, и тот переиспользуется.

Проверяют так:

kubectl get rs -l app=app -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.metadata.annotations.deployment\.kubernetes\.io/revision}{"\n"}{end}'

Команда выведет список ReplicaSet с их revision. До отката там будут revision 1 и 2, после отката - 2 и 3.

Можно ли откатить откат

Да. После отката активна revision=3, а в истории осталась revision=2 с плохой версией. Команда kubectl rollout undo снова создаст revision=4, скопировав шаблон из revision=2. Это вернёт ту самую плохую версию.

Откатиться на revision=1, если её уже вытеснили из истории, нельзя. Записи больше нет, шаблон не сохранился. ReplicaSet не хранит шаблонов старше тех, что остались в истории.

kubectl rollout status и pause

После отката переход не завершён мгновенно. Идёт плавный переход:

kubectl rollout status deploy/app --timeout=120s

Команда блокируется до завершения. Нулевой код выхода означает, что откат прошёл. Ненулевой - таймаут или ошибку.

Если переход зависает, помогут две команды:

kubectl rollout pause deploy/app   # остановить
kubectl rollout resume deploy/app  # возобновить

pause замораживает текущее состояние и не даёт контроллеру запускать новые переходы. Это удобно при отладке.

Какая ревизия активна прямо сейчас:

kubectl get deploy app -o jsonpath='{.metadata.annotations.deployment\.kubernetes\.io/revision}'

Домашка

Теорию закрепляет задание на своём кластере: 3 шага с проверкой. Кластер поднимается по треку Kubernetes локально, задания лежат в репозитории k8s-practice.

./run.sh start k8s-rollback     # развернуть стартовое состояние
./run.sh verify k8s-rollback    # проверить текущий шаг

Коротко

Прошли материал до конца? Отметьте урок пройденным.