Zero-downtime деплой веб-приложения: как обновлять прод без остановки сервиса
Перезапуск процесса на проде всегда занимает какое-то время — вопрос в том, видит ли это время пользователь. Zero-downtime деплой убирает этот разрыв: несколько проверенных стратегий и пара базовых требований к коду позволяют выкатывать релизы хоть каждый день, не планируя технические окна по ночам.

Деплой веб-приложения выглядит просто: залить новый код и перезапустить процесс. Но между моментом, когда старый процесс остановлен, и моментом, когда новый принял первый запрос, проходит время — и именно в этом промежутке рвутся активные соединения, а балансировщик ещё какое-то время продолжает слать трафик туда, где уже никто не отвечает. Пользователь в этот момент видит обрыв соединения, пустой ответ или 502, и ему совершенно не важно, что через три секунды всё снова заработает: для него сайт был недоступен.
Zero-downtime деплой — не один конкретный приём, а набор практик, которые убирают этот промежуток из уравнения: запрос пользователя никогда не попадает в момент, когда ни старая, ни новая версия ещё не готовы его принять. Команде, которая сопровождает веб-приложение в проде и предсказуемо обновляет его без ночных авралов, такие практики нужны не как разовый трюк перед важным релизом, а как стандартная часть процесса — именно этим и занимается команда YUSMP Group, которая развивает и поддерживает веб-приложения уже после запуска.
В этом материале разберём три основные стратегии деплоя без простоя, что должно быть готово в приложении до того, как любая из них заработает, и отдельно — почему обновление схемы базы данных требует своего подхода, который ни одна стратегия деплоя кода сама по себе не решает.
Почему обычный деплой роняет пользователей
Самый наивный способ выкатить обновление — остановить процесс приложения, заменить код и запустить заново. На время между остановкой и стартом сервис физически не отвечает: операционная система закрывает слушающий сокет, и новые соединения получают отказ, а не ответ.
Даже если инстансов несколько и балансировщик продолжает работать, проблема не исчезает полностью. Балансировщик узнаёт, что бэкенд умер, не мгновенно, а по результату следующей проверки здоровья — обычно это секунды, а не миллисекунды. Всё это время часть трафика продолжает идти на уже остановленный инстанс и получает обрыв соединения. Запросы, которые успели начаться до остановки, — особенно долгие операции и постоянные соединения вроде WebSocket, — просто обрываются на середине, а не завершаются корректно.
Три стратегии zero-downtime деплоя — что выбрать
Все рабочие подходы к деплою без простоя решают одну и ту же задачу — не дать трафику попасть на недоступный инстанс, — но делают это по-разному и с разной ценой. Прежде чем выбирать конкретную стратегию, полезно сравнить их по четырём параметрам.
| Стратегия | Простой | Скорость отката | Стоимость инфраструктуры | Когда подходит |
|---|---|---|---|---|
| Rolling update | Нет, при верных health checks | Средняя — нужен повторный деплой предыдущей версии | Низкая — не нужны лишние серверы сверх уже имеющихся | Стандартный выбор для большинства веб-сервисов |
| Blue-Green | Нет | Мгновенная — переключить роутинг обратно | Высокая — держим два полных окружения параллельно | Когда критична скорость отката, а бюджет на инфраструктуру есть |
| Canary | Нет | Быстрая — остановить раскатку на малом проценте трафика | Средняя — чаще в рамках того же окружения, что и rolling | Когда нужно проверить версию на реальном трафике с минимальным риском |
Rolling update — обновление по одному инстансу за балансировщиком
Rolling update заменяет инстансы приложения по одному или небольшими группами: новый инстанс стартует, проходит проверку готовности, после чего балансировщик начинает слать на него трафик, и только тогда останавливается один из старых. В любой момент времени часть инстансов работает на старой версии, часть — на новой, а пользователь не видит ни одного провала. Это самая дешёвая и самая распространённая стратегия: она не требует удвоения инфраструктуры и встроена практически в любой оркестратор или даже в простой скрипт деплоя поверх нескольких серверов.
Blue-Green — когда важен мгновенный откат
Blue-green держит два полностью идентичных окружения: одно (например, blue) принимает весь прод-трафик, второе (green) получает новую версию и проходит проверку без единого реального пользователя. Когда green готов, роутинг переключается на него целиком — обычно сменой записи в балансировщике или DNS. Если что-то пошло не так, откат — это обратное переключение роутинга, а не повторный деплой старой версии, поэтому он занимает секунды. Цена — постоянное содержание второго полного окружения, что ощутимо для инфраструктурного бюджета.
Canary — как проверить новую версию на реальном трафике без риска
Canary-релиз выпускает новую версию не на всех пользователей сразу, а на небольшой процент реального трафика — часто 1-5%, — и сравнивает метрики ошибок, задержки и бизнес-показателей новой версии с остальной частью системы. Если метрики в норме, процент постепенно увеличивается до ста; если нет, раскатка останавливается, и пострадавшими оказываются только пользователи из canary-доли. Этот подход ловит проблемы, которые физически не воспроизводятся на стейджинге, потому что зависят от реальных данных и реальной нагрузки, но требует системы наблюдаемости, которая умеет сравнивать срезы метрик по версиям, а не только смотреть общий график.
Что должно быть готово до любой стратегии: health checks и graceful shutdown
Ни одна из трёх стратегий не работает сама по себе — все они опираются на два базовых механизма в самом приложении.
Health check, или точнее readiness-проверка, должна реально проверять готовность инстанса принимать трафик, а не просто отвечать «процесс жив». Полезная readiness-проверка смотрит на доступность зависимостей — соединение с базой данных, кэшем, очередью — и отдаёт отрицательный ответ, если хотя бы одна критичная зависимость недоступна. Проверка, которая всегда возвращает 200, пока жив сам процесс, не защищает ни от чего: балансировщик будет слать трафик на инстанс, который технически запущен, но не может обработать ни один реальный запрос.
Graceful shutdown — это обработка сигнала остановки (обычно SIGTERM) так, чтобы процесс не умирал мгновенно. Правильная последовательность: получить сигнал, перестать объявлять себя готовым для новых запросов (чтобы readiness-проверка начала проваливаться и балансировщик убрал инстанс из ротации), дождаться завершения уже начатых запросов — это называется drain соединений, — и только после этого завершить процесс. Между объявлением «я больше не готов» и фактической остановкой обычно закладывают небольшую паузу в несколько секунд: балансировщику и системе обнаружения сервисов нужно время, чтобы узнать об этом и перестать слать новый трафик, прежде чем инстанс исчезнет физически.
Отдельная головная боль — обновление базы данных без простоя
Стратегии деплоя решают, как плавно заменить код приложения. Они ничего не говорят о том, что делать со схемой базы данных, а это отдельная и часто более коварная проблема: во время rolling-деплоя старая и новая версия кода какое-то время работают одновременно с одной и той же базой, и схема обязана обслуживать обе версии сразу.
Рабочий ответ — паттерн expand-contract: сначала добавить новое поле или таблицу, не трогая старое, задеплоить код, который умеет работать с обоими вариантами схемы, перенести данные в фоне, и только потом, отдельным шагом, убрать старое. Прямое переименование колонки или смена её типа одной командой ломает совместимость для той версии кода, которая ещё не обновилась. Подробно все четыре шага этого паттерна и конкретные безопасные приёмы для разных операций разобраны в материале о миграциях базы данных без даунтайма.
Второй инструмент в этой же задаче — feature flag. Если новая логика должна включиться вместе с новой структурой данных, но сам код уже раскатывается постепенно через rolling update, флаг даёт возможность развести по времени «код задеплоен» и «функциональность включена»: включить новую логику можно только после того, как миграция данных полностью завершена на всех инстансах, а не в момент, когда последний под с новым кодом случайно стартовал первым.
Чек-лист перед первым zero-downtime релизом
Прежде чем полагаться на zero-downtime деплой в проде, стоит явно пройтись по короткому списку готовности.
- За балансировщиком работает минимум два инстанса приложения, а не один
- Readiness-проверка реально проверяет зависимости, а не просто факт, что процесс жив
- Код корректно обрабатывает SIGTERM и дренирует активные соединения перед остановкой
- Есть план для изменений схемы БД — expand-contract, а не прямые разрушающие миграции
- Во время деплоя мониторинг показывает срез ошибок по версии, а не только общий график
- Есть чёткий план отката, проверенный заранее, а не придуманный в момент инцидента
- Стратегия (rolling, blue-green или canary) прогнана на стейджинге хотя бы один раз до прода
- Для canary определён порог метрик, при котором раскатка автоматически останавливается
Частые вопросы
Что такое zero-downtime деплой простыми словами?
Способ выкатить новую версию кода так, чтобы ни один пользователь не увидел обрыв соединения, 502 или пустой ответ. Новая версия принимает трафик постепенно, а старая продолжает обслуживать запросы, пока не освободится полностью.
В чём разница между rolling update и blue-green деплоем?
Rolling update обновляет инстансы по одному внутри одного окружения, постепенно заменяя старую версию новой. Blue-green держит два полностью независимых окружения и переключает весь трафик разом, поэтому откат — это обратное переключение, а не повторный деплой.
Что такое canary-релиз и зачем он нужен?
Canary-релиз выпускает новую версию на небольшой процент реального трафика и сравнивает метрики ошибок с остальной частью системы. Он нужен, чтобы поймать проблему, которая не воспроизводится на стейджинге, прежде чем она коснётся всех пользователей.
Можно ли сделать zero-downtime деплой без Kubernetes?
Да. Принцип не привязан к оркестратору: достаточно двух и более инстансов приложения за балансировщиком, который умеет health checks, и дисциплины graceful shutdown. Kubernetes автоматизирует эту механику, но rolling update можно собрать вручную на любом стеке.
Как обновить схему базы данных без простоя сервиса?
Через паттерн expand-contract: сначала добавить новое поле, не трогая старое, задеплоить код, который работает с обеими версиями схемы, перенести данные, и только потом убрать старое поле отдельным шагом. Прямое переименование или смена типа колонки без этой схемы ломает совместимость.
Что такое graceful shutdown и почему без него downtime всё равно будет?
Graceful shutdown — это обработка сигнала остановки, при которой процесс перестаёт принимать новые запросы, но дожидается завершения уже начатых. Без него любая стратегия деплоя просто обрывает активные соединения в момент остановки старого инстанса, какой бы умной ни была раскатка трафика вокруг него.
Сколько стоит переход на zero-downtime деплой для небольшого проекта?
Для rolling update на небольшом сервисе — почти ничего сверх уже имеющихся двух инстансов и настройки health checks у балансировщика. Основные затраты не в инфраструктуре, а во времени на то, чтобы привести код к состоянию, когда старая и новая версия могут работать одновременно. Подробный практический разбор с примерами конфигурации есть и на Хабре.



