Обновление положило прод: как откатиться за 4 минуты
Плановое обновление — то, что должно было пройти незаметно в техническое окно ночью в среду — уронило прод. Сервис не поднялся, либо поднялся и ведёт себя странно: то ли 500-е на части запросов, то ли данные не те, то ли просто тормозит так, что мониторинг уже разбудил дежурного. Дальше есть два сценария: команда, у которой заранее готов путь назад, откатывается за считанные минуты и разбирается спокойно; команда без этого плана тратит на восстановление час, два, а иногда и ночь, пытаясь на ходу понять, что вообще можно откатить и как.
Содержание
Первая реакция: паника или заготовленный план
Первые 60 секунд после того, как обновление положило прод, решают, сколько продлится инцидент — и решают не технические знания, а то, есть ли у команды заготовленный ответ на вопрос «что делаем прямо сейчас».
Неподготовленная команда в этот момент делает три вещи одновременно и неэффективно. Кто-то читает логи в одном терминале и параллельно гуглит текст ошибки. Кто-то уже открыл apt list --upgradable пытаясь понять, что вообще обновилось, потому что запись об обновлении велась «в голове» или в лучшем случае в тикете без деталей. Кто-то в чате пишет «а что если откатить», но откатывать нечего — старой версии пакета уже нет в кеше apt, старый образ приложения выкачен и удалён с диска ради места, а бэкап базы, если он и есть, лежит без отметки «сделан прямо перед этой миграцией». Решение принимается коллективным нервным консенсусом, а не по чек-листу, и это добавляет к простою не минуты, а десятки минут только на споры.
Подготовленная команда в тот же момент делает не «то же самое, но быстрее» — она делает принципиально другой набор действий:
- Первый — не чинит, а фиксирует состояние. Скриншот дашборда, копия последних 200 строк лога, timestamp начала деградации. Это нужно не для красоты постмортема, а чтобы не потерять контекст, если откат сотрёт улики.
- Второй сразу открывает runbook отката для этого конкретного сервиса — документ или скрипт, где по шагам расписано, что откатывать и в каком порядке. Не вспоминает, а читает.
- Роли распределены заранее: кто принимает решение «откатываем/чиним», кто исполняет команды, кто пишет статус в канал инцидентов. Никто не спрашивает «а кто у нас за это отвечает» в момент, когда сервис лежит.
Разница не в квалификации инженеров — часто это одни и те же люди в разных обстоятельствах. Разница в том, что подготовленная команда превратила решения в артефакты (документ, скрипт, снапшот) заранее, когда было спокойное время, а не пытается изобрести их под давлением с падающим сервисом и раздражённым бизнесом за спиной.
Быстрая диагностика: что именно сломалось
Прежде чем решать, откатываться или чинить вперёд, нужно за пару минут понять класс проблемы — от этого зависит, сработает ли откат вообще и насколько он будет быстрым. На практике почти всё сводится к трём категориям.
Несовместимость версии. Новая версия пакета, рантайма или библиотеки изменила поведение, которое приложение неявно от неё требовало. Классика — минорное обновление PostgreSQL меняет план запроса, обновление Node.js или PHP меняет поведение стандартной библиотеки, обновление зависимости в package-lock.json подтягивает несовместимый транзитивный пакет. Диагностируется быстро: сравните версии до/после.
# что реально обновилось в последнем apt-обновлении
grep " upgrade " /var/log/dpkg.log | tail -30
# для конкретного пакета — какая версия стояла, какая встала
apt-cache policy nginx
# для docker-образов — что изменилось в теге
docker images --digests | grep myapp
Изменившийся конфиг по умолчанию. Обновление пакета часто тащит с собой новый дефолтный конфиг или меняет поведение при отсутствии явно заданного параметра. Nginx, PHP-FPM, PostgreSQL, Redis — у всех при мажорных и части минорных апдейтов меняются дефолты (лимиты соединений, таймауты, формат логов, обязательные директивы). Если сервис не поднимается сразу после рестарта — первое подозрение сюда.
# что реально изменилось в конфиге пакета при обновлении
sudo find /etc -newer /var/log/dpkg.log -type f 2>/dev/null
# если конфиги под git (etckeeper/ansible) — прямой diff
cd /etc && git log --oneline -5 && git diff HEAD~1 HEAD
Если конфиги в /etc не версионируются вообще — это уже само по себе диагноз организационной проблемы, а не только техническая находка сейчас.
Миграция базы данных пошла не так. Самый тяжёлый случай, потому что откат кода после неудачной миграции схемы — это не откат кода. Если миграция уже успела изменить структуру таблиц (добавила колонку с NOT NULL без дефолта, переименовала поле, начала долгий ALTER TABLE с блокировкой), откат бинарника приложения на старую версию оставит вас с новой схемой и старым кодом, который её не понимает — это часто хуже, чем не откатываться вовсе.
# что реально накатилось из миграций (пример для Flyway)
flyway info
# то же для Liquibase
liquibase history
# для Django
python manage.py showmigrations --list
Разбор инструментов миграций и почему часть из них поддерживают безопасный откат down, а часть — только вперёд, разобран в статье про Flyway и Liquibase.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверОткатываться или чинить вперёд
Есть соблазн «просто быстро пофиксить» — поправить одну строчку конфига, перезапустить сервис и жить дальше. Иногда это правильно. Но по умолчанию, если у вас заранее готов путь отката, откат почти всегда быстрее и безопаснее, чем починка вперёд под давлением падающего прода. Три причины:
- Откат детерминирован, фикс — нет. Возврат к состоянию, которое час назад работало и было проверено, — известная точка. Правка «на живую» — это новая, непроверенная конфигурация, которую вы придумываете в момент стресса.
- Время на откат известно заранее (если он подготовлен), время на фикс — нет. Вы не знаете, одна ли это строчка или симптом более глубокой проблемы, пока не начнёте копать, а копать под давлением падающего SLA — плохая идея.
- Откат не добавляет новых переменных. Фикс вперёд, сделанный в панике, часто ломает что-то ещё, потому что не проходит ревью и тесты — вы просто переносите инцидент дальше по времени в новой форме.
Когда чинить вперёд оправданно: если проблема тривиальна и локализована (опечатка в env-переменной, забытый флаг), причина уже на 100% ясна, а откат самой миграции БД сложнее и рискованнее, чем точечный фикс-форвард. Также если отката попросту не существует (тот самый случай без снапшота и без версионирования) — тогда чинить вперёд не выбор, а единственный вариант, и именно это самая частая причина долгих инцидентов.
Практическое правило: если с момента обновления прошло больше 10-15 минут диагностики без ясной картины — откатывайтесь, а разбор первопричины делайте потом на копии данных, не на живом проде.
Механизм отката за 4 минуты
«Откатиться за 4 минуты» — это не magic-команда, а последовательность из заранее подготовленных, но простых шагов. Пример для типового случая: обновили systemd-сервис на VPS через docker compose.
# 1. Остановить текущую версию (10-15 сек)
docker compose down
# 2. Вернуть предыдущий тег образа — он должен быть уже на диске,
# а не только в реестре, иначе шаг превращается в скачивание
git -C /opt/myapp checkout HEAD~1 -- docker-compose.yml
docker compose up -d --force-recreate
# 3. Проверить здоровье
curl -sf http://127.0.0.1:8080/healthz || echo "ещё не готово"
docker compose logs -f --tail=50 app
Для отката пакета ОС, если версия закреплена (apt-mark hold) и старый .deb лежит в локальном кеше:
sudo apt-get install --allow-downgrades nginx=1.24.0-2ubuntu7.3
sudo systemctl restart nginx
Для отката на уровне всей виртуальной машины — если снапшот диска был снят прямо перед обновлением (это отдельный и самый надёжный уровень отката, потому что откатывает всё сразу: пакеты, конфиги, данные):
# LVM: откат к снапшоту тома
sudo lvconvert --merge /dev/vg0/myapp-snap-preupdate
sudo reboot
# libvirt/qcow2: откат к снапшоту виртуальной машины
sudo virsh snapshot-revert myapp-vm preupdate-2026-08-30 --running
Про то, почему именно qcow2-снапшоты бывают медленными на запись и как это учитывать при выборе такого способа отката, есть отдельный разбор — copy-on-write и qcow2.
Для отката миграции БД, если инструмент поддерживает down-скрипты и их писали заранее:
flyway undo # Flyway (Teams-функция, не во всех версиях)
liquibase rollback-count 1 # Liquibase — откат на 1 changeset назад
Обратите внимание: цифра «4 минуты» — это ориентир для простого случая (откат кода/образа при закреплённых версиях и уже скачанном предыдущем образе), а не универсальная гарантия. Реальное время у вас будет зависеть от размера образа, скорости диска, того, нужен ли рестарт БД — где-то это будет 2 минуты, где-то 10. Важна не конкретная цифра, а порядок величины: минуты, а не часы, и только при выполнении условий из следующего раздела.
Без чего откат за 4 минуты невозможен
Это главная мысль всей статьи: быстрый откат — не свойство инструмента, а следствие подготовки, сделанной до обновления, а не во время инцидента. Без трёх вещей заранее «откат за 4 минуты» физически не существует, и вместо него будет либо долгий откат, либо его отсутствие.
1. Снапшот или бэкап, снятый непосредственно перед обновлением. Не «бэкап есть, делается каждую ночь» — а конкретно тот, что снят прямо перед этим конкретным изменением, помеченный так, чтобы его можно было найти за секунды.
# минимальный чек-лист перед любым обновлением с риском для БД
sudo -u postgres pg_dump -Fc mydb > /backups/mydb-preupdate-$(date +%F-%H%M).dump
sudo virsh snapshot-create-as myapp-vm preupdate-$(date +%F-%H%M) --disk-only --atomic
Тот же принцип «снапшот перед изменением» работает независимо от конкретного инструмента бэкапа — важен не выбор софта, а сам факт, что копия снята прямо перед риском, а не «когда-то ночью по расписанию».
2. Зафиксированные версии пакетов. Если версии не закреплены явно, вы не узнаете, с какой версии откатываться, и не факт, что старая версия ещё доступна в кеше или репозитории к моменту, когда она понадобится.
# зафиксировать версию, чтобы apt upgrade её не трогал
sudo apt-mark hold nginx postgresql-16
# посмотреть, что удерживается
apt-mark showhold
# для docker — никогда не деплоить :latest, только конкретный дайджест/тег
image: myregistry/myapp@sha256:3f29a6...
3. Runbook отката и распределённые роли. Документ (даже на одну страницу), где написано: для этого сервиса откат — вот эта команда, снапшот лежит вот здесь, ответственный за решение — вот этот человек, ответственный за исполнение — вот этот. Без этого даже при наличии снапшота и закреплённых версий команда тратит время на выяснение, кто и что должен делать — а «4 минуты» превращаются в «20 минут на споры плюс 4 минуты на саму команду».
Организационная сторона готовности к инцидентам вообще, не только к обновлениям, подробно разобрана в статье про план реагирования на инциденты — часть той же логики: заранее прописанные роли и порядок действий решают в момент инцидента больше, чем экспертиза отдельного инженера.
Как подготовиться заранее к следующему разу
Постмортем без конкретных действий — это просто описание боли. Вот минимальный набор, который превращает следующее «обновление положило прод» в контролируемый эпизод на несколько минут, а не в ночное дежурство.
- Перед каждым обновлением с риском для данных — снапшот диска или дамп БД, помеченный временем и назначением. Правило простое: если откатывать одну команду дольше, чем снять снапшот, — снапшот снимайте всегда, без исключений «на этот раз и так нормально».
- Версии пакетов и образов — закреплены и видны в git.
apt-mark holdдля критичных пакетов ОС, конкретные теги/дайджесты для образов,docker-compose.ymlиAnsible-плейбуки в git с историей — откат конфига тогда буквальноgit checkout HEAD~1. - Runbook отката существует и его читали не только в день написания. Раз в квартал — короткая тренировка: имитировать неудачное обновление на тестовом сервере и засечь время реального отката. Первая тренировка почти всегда покажет, что «4 минуты» на бумаге превращаются в 20 на практике — и это ценная находка, которую лучше получить не во время реального инцидента.
- Технические окна и staged rollout там, где это возможно. Обновлять сначала staging, затем один инстанс из нескольких прод-нод, смотреть метрики, и только потом катить на все. Похожий принцип разобран в статье про canary deploy — постепенный вывод риска на малую долю трафика снижает саму вероятность, что придётся откатывать всё и сразу.
- Мониторинг, который ловит деградацию раньше пользователей. Алерт на рост 5xx, на не прошедший healthcheck, на аномальный рост времени ответа — чем раньше команда узнаёт о проблеме, тем короче окно между «стало плохо» и «первый шаг runbook-а начат».
- Ретро после каждого отката, даже удачного за 4 минуты — что можно было заметить раньше, что в runbook-е было неточным, что заняло больше времени, чем ожидалось. Без этого шага набор практик не улучшается со временем, а стагнирует на уровне «более-менее сработало один раз».
Инфраструктура тоже часть этой готовности: снапшот диска снимается быстро и без сюрпризов, если под ним современный SSD/NVMe и нормальный гипервизор, а не переполненный локальный диск, где virsh snapshot-create сам может подвесить машину на минуты в неподходящий момент.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Всегда ли надо снимать снапшот перед любым обновлением, даже мелким security-патчем?
Для критичных прод-сервисов — да, это дешевле, чем разбираться постфактум; для некритичных сервисов с простым откатом через пакетный менеджер снапшот может быть избыточен, но бэкап БД перед миграцией — почти всегда оправдан.
Что делать, если миграция БД уже частично применилась и упала на середине?
Сначала проверьте, транзакционна ли миграция (PostgreSQL с DDL в транзакции откатывает атомарно при ошибке в большинстве случаев; MySQL — нет, DDL там неявно коммитится). Если откат неатомарен — восстанавливайтесь из дампа, снятого перед миграцией, а не пытайтесь вручную «дочинить» частично применённую схему.
Можно ли откатиться, если предыдущий образ/пакет уже удалён с диска ради экономии места?
Технически да, если он остался в реестре или репозитории — но тогда откат превращается в скачивание и разворачивание заново, и «4 минуты» становятся 15-30 в зависимости от размера образа и скорости канала. Поэтому правило — держать хотя бы одну предыдущую версию локально до подтверждения, что новая стабильна.
Как выбрать, сколько версий назад хранить для быстрого отката?
Обычно достаточно одной предыдущей стабильной версии — той, что реально работала последней. Хранить глубже (2-3 версии) имеет смысл для сервисов, где между обновлениями проходит много времени и есть риск, что предпоследняя версия тоже была проблемной.
Нужен ли отдельный тестовый сервер, чтобы тренировать откат, или можно на проде?
Тренировать нужно вне прода — на отдельном VPS с той же ОС и версиями пакетов, что и боевой сервер, иначе тренировка не покажет реальных временных затрат и рисков.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →