Как откатить деплой сервиса
Что делать, когда новый релиз в prod оказался хуже предыдущего:
пошли 5xx, p99 деградировал, forwarder встал, consumer-lag растёт.
Эта страница — про откат деплоя (Docker-образа), не про откат
миграции БД. Миграция откатывается отдельно (чаще — forward-fix,
см. rollback-migration), и работает она
корректно только если была оформлена по expand-contract (см.
../conventions/db-pgx#expand-contract).
Общая процедура деплоя — deploy-service.
Содержание
- Решение: откат или форвард-fix
- Immediate mitigation
- Откат образа через Helm / kustomize
- Откат миграции — отдельная тема
- Синхронизация с feature flags
- Наблюдение после отката
- Post-mortem
- Что не делать
- Связанные разделы
Решение: откат или форвард-fix
Первое решение, которое принимает on-call, — откатывать или чинить вперёд. Критерии:
| Симптом | Откат или forward-fix |
|---|---|
| Fast-burn SLO alert (14.4× за 5m/1h) сработал после cutover | Откат немедленно, разбор потом |
| Растёт доля 5xx > 1% на нескольких endpoint’ах | Откат |
| Один endpoint вернулся 500-ми из-за очевидного null-pointer / panic | Откат (меньше риска, чем hotfix за 5 минут) |
| p99 вырос на 50%+ без видимых 5xx | Если SLO горит — откат; если нет — разобраться, потом форвард-fix |
| Outbox встал, forwarder не публикует | Откат |
| Consumer-группа ушла в rebalance-loop | Откат |
| Логически корректный релиз, но одна опечатка в endpoint’е (404 вместо 400) | Forward-fix в рабочее время |
| Миграция прошла, но код-релиз сломался | Откат кода (миграция остаётся, expand-совместима) |
Правило: если не уверен — откатывай. Откат успешного релиза стоит 5 минут — потери от долгого инцидента измеряются SLO- бюджетом и нервами пользователей.
Forward-fix допустим только когда:
- Root cause понятен за 2–3 минуты анализа.
- Фикс — одно изменение, которое можно merge’ить, CI прогоняется за <15 минут, staging успевает принять.
- Откат не быстрее (например, новая миграция contract-типа уже применена и prod-код под неё не работает в старом виде).
Во всех остальных случаях — откат, форвард-fix следующим релизом.
Immediate mitigation
До самого отката — 1–2 минуты, в которые стоит снизить blast-radius:
-
Объявить инцидент в on-call канале: «SEV-2:
<svc>деплойvX.Y.Z— fast-burn SLO, откатываем». Одна строка, не эссе. -
Остановить дальнейшие деплои. Если CI/CD раскатывает по тегам — заморозить ветку / tag’и:
# пример, конкретика — в infra-репо git tag -d vX.Y.Z # локально, для наглядности # плюс: lock в CI — pause deploys until further notice -
Если деплой ещё идёт (rolling в процессе) — можно прервать:
kubectl rollout pause deployment/<svc> -n backendЭто заморозит переключение pod’ов, часть реплик останется на старой версии — уже хоть что-то.
-
Если есть canary — снять 100% трафика с canary:
# зависит от mesh / Cilium Gateway API конфига, обычно патч weight = 0 -
Снять зависимые джобы, если они могут умножить ущерб (CronJob’ы, которые читают новую колонку из БД и падают —
kubectl patch cronjob <x> -p '{"spec":{"suspend":true}}').
После этого — откат.
Откат образа через Helm / kustomize
GitOps-first: правильный rollback — это commit в infra-репо,
возвращающий image.tag на предыдущее значение. Не helm rollback
в runtime — он рассинхронизируется с git, GitOps-sync (ArgoCD /
Flux) при следующей проверке восстановит новую версию и откат
откатится.
Шаблон через commit в infra-репо
-
Определить предыдущий рабочий тег:
# последний тег до инцидента git -C infra log -- charts/<svc>/values.prod.yaml --oneline | head -5 -
Revert-коммит:
git -C infra revert <sha-of-bad-deploy> --no-edit git -C infra pushGitOps-sync подхватит через минуту-две, Kubernetes запустит rolling update на старую версию.
-
Проверка, что pod’ы фактически откатились:
kubectl get pods -l app=<svc> -n backend \ -o jsonpath='{range .items[*]}{.spec.containers[0].image}{"\n"}{end}' # должны показывать старый тег, например ghcr.io/.../<svc>:v1.4.1
Быстрый rollback без GitOps (аварийный)
Если GitOps-sync медленный (>5 мин) или недоступен, допустим
прямой helm rollback c последующим commit’ом в infra-репо
сразу после стабилизации. Без commit’а нельзя оставлять —
следующий sync затрёт rollback.
helm history <svc> -n backend # найти revision до bad-deploy
helm rollback <svc> <revision> -n backend --wait --timeout 5mСразу после — revert-коммит в infra-репо, как в основном пути.
Подтверждение успешного отката
http_requests_total{code=~"5.."}возвращается к pre-deploy уровню за 1–5 минут.- SLO burn-rate fast-alert перестаёт срабатывать.
- Латенси p95/p99 возвращается к baseline.
/readyzна всех pod’ах старой версии — 200.
Если не возвращается — root cause не в образе, откат образа ничего не решил. Типичные причины:
- Миграция contract-типа успела применить constraint, старый код в него не пишет. См. следующий раздел.
- Упал shared upstream (БД primary, Kafka broker) — симптом
совпал с cutover’ом, но не причинно. См.
../troubleshooting/failure-modes-matrix.
Откат миграции — отдельная тема
Важная часть: откат деплоя не откатывает миграцию. Если релиз содержал expand-миграцию, она осталась применённой — и это правильно, старый код с expand-изменением работает.
Если релиз сломал expand-contract-правило (миграция применила
contract одновременно с новым кодом — например, SET NOT NULL на
колонку, которую старый код не заполняет), тогда откат кода
недостаточен: старый код нарушит NOT NULL constraint.
В таком случае:
- Сначала применить forward-fix-миграцию, снимающую
contract-ограничение (
ALTER COLUMN ... DROP NOT NULL). - Затем откатить образ.
Откат down-миграции в prod по умолчанию не используется — см.
rollback-migration. Штатный путь — новая
forward-fix-миграция, не migrate down.
Синхронизация с feature flags
Если новая логика была под feature flag (FEATURE_X=false в
pre-deploy) — сначала выключи флаг, потом разбирайся:
- Правка ConfigMap / env в infra-репо:
FEATURE_X: "false". kubectl rollout restart deployment/<svc> -n backend(или hot-reload, если поддерживается).- Наблюдай 5 минут — если симптом ушёл, root cause в коде под флагом. Образ не откатываешь; флаг остаётся выключенным до фикса.
- Если флаг выключен, а симптомы остаются — проблема в чём-то ещё, переходи к откату образа.
Это быстрее, чем rollback всего релиза, и сохраняет полезные изменения, которые успели поехать в этом же образе.
Наблюдение после отката
После отката — тот же цикл наблюдения, что при деплое:
- SLO burn-rate perестал ли срабатывать? Должен.
- Error-rate, p95/p99, outbox/consumer lag вернулись к baseline?
- Нет ли новых алертов, связанных с откатом (старый образ, например, не умеет читать свежую схему из БД)?
30 минут on-call на связи, даже если первые 5 минут всё выглядит зелёным.
Если откат не помог — см. §Откат образа через Helm / kustomize, подтверждение и возможные причины. Эскалация: lead-инженер backend
- owner downstream, если симптомы ушли в соседний сервис.
Post-mortem
Любой откат = incident review в ближайший рабочий день. Шаблон
живёт в infra-репо (docs/post-mortems/) либо в handbook
отдельной страницей (если её ещё нет — заведи отдельным PR).
Обязательный минимум:
- Timeline. Merge → deploy → первый симптом → решение откатить → полный rollback → end-of-incident. Время в UTC.
- Impact. Сколько пользователей, сколько запросов упало, сколько процентов SLO-budget’а сгорело.
- Root cause. Что именно было не так в коде / конфиге / миграции.
- What worked. Хорошая диагностика, быстрый откат, честный alert.
- What didn’t. Где alert запоздал, где staging не поймал, где CI пропустил.
- Action items. С owner’ом и сроком. «Добавить unit-тест на X» / «проверить expand-contract на PR-ревью» / «уменьшить chi- timeout с 60s до 30s».
Action items не хоронятся в документе. Каждый — issue в репо соответствующего сервиса, со ссылкой на post-mortem. Следующий post-mortem начинается с «что закрыли с прошлого раза».
Post-mortem — blameless. Не «кто выкатил» — а «какой процесс / инструмент / правило должен был это поймать, но не поймал».
Что не делать
- Не откатываться «ещё раз назад», минуя rollback. Если образ
v1.4.2плохой, аv1.4.1рабочий — откатываемся ровно наv1.4.1. Не прыгаем наv1.3.0— разрыв в версиях делает forward-fix невозможным. - Не делать
helm rollbackбез последующего commit’а в infra- репо. GitOps-sync через 5 минут вернёт bad-deploy. - Не пушить hotfix-tag прямо в prod без staging-прохода «по горячему». Staging дешевле, чем второй откат.
- Не удалять bad-deploy tag из git. Он нужен для post-mortem: найти PR, commits, ревьюеров. Остаётся в репо навсегда.
- Не пропускать post-mortem. «Откатились, всё работает, забыли» = тот же баг приедет через квартал.
- Не откатывать деплой, если root cause — shared upstream.
Пример: Kafka-brokers проседают, сервис пишет 5xx. Откат не
поможет — старая версия упадёт так же. Иди в
../troubleshooting/failure-modes-matrix. - Не «тихо» откатывать. Объявление в on-call канале — часть процедуры. Тихий откат мешает параллельным инцидентам и лишает команду контекста.
Связанные разделы
deploy-service— штатная процедура деплоя.rollback-migration— откат миграции через forward-fix (отдельно от отката образа).../conventions/db-pgx#expand-contract— почему правило expand-contract делает откат безопасным.../conventions/shutdown— как rolling update снимает трафик со старого pod’а.../conventions/slo-and-budget— burn-rate alert’ы как триггер отката.../troubleshooting/failure-modes-matrix— когда симптом выглядит как регресс релиза, но root cause в другом.../checklists/production-ready— чеклист для первого релиза сервиса.