Real-Time Backend
Graceful Deployment
Плановый деплой в 02:00 UTC. 340 000 пользователей видят красный индикатор и потерянные сообщения. HTTP-сервисы пережили бы незаметно. WebSocket потребует другого подхода.
- GitHub при неправильном деплое в 2023 оборвал 340K WebSocket-соединений одновременно - без graceful shutdown reconnect-шторм положил chat-сервис на 3 минуты
- Discord при деплое chat-сервисов использует maxUnavailable: 1 и 10-минутный drain - деплой занимает 2-3 часа, но ни одно соединение не обрывается принудительно
- Netflix добавил sleep 15 в preStop hook и полностью устранил 503-ошибки на 2-5 секунд при каждом деплое - Netflix Zuul gateway описал это в Engineering Blog
Zero-downtime: почему WebSocket сложнее HTTP
**GitHub, 2023.** Плановый деплой в 02:00 UTC. Разработчики отправили новую версию на production, старые поды начали умирать. 340 000 WebSocket-соединений - оборвались мгновенно. Для HTTP это было бы незаметно: клиент просто делает новый запрос. Для WebSocket клиент видит красный индикатор и потерянные события реального времени.
HTTP stateless: каждый запрос независим, перезапуск сервера незаметен. WebSocket stateful: соединение живёт часами, в нём накоплен state (subscriptions, cursors, in-flight сообщения). Убить под = убить все соединения на нём.
Zero-downtime для WebSocket требует трёх вещей работающих вместе: клиент умеет переподключаться с восстановлением state, сервер умеет делать graceful shutdown (дать соединениям завершиться), балансировщик умеет дрейфовать трафик без разрыва существующих соединений.
Kubernetes по умолчанию даёт поду 30 секунд на graceful shutdown (terminationGracePeriodSeconds). Для WebSocket-сервисов с долгоживущими соединениями это нужно увеличивать до 60-300 секунд в зависимости от типичной продолжительности сессии.
Почему zero-downtime deployment сложнее для WebSocket, чем для HTTP?
Connection Draining: дать соединениям завершиться
Connection draining - это период между "pod помечен на удаление" и "pod уничтожен". В этот период pod перестаёт принимать новые соединения, но существующие продолжают работать. Балансировщик видит под как draining и не направляет новый трафик, но не обрывает старый.
Проблема: Kubernetes сначала посылает SIGTERM поду, и параллельно начинает убирать его из endpoints. Но это не атомарно - балансировщик может продолжать слать трафик несколько секунд после SIGTERM. Поэтому preStop hook с sleep нужен: он даёт время балансировщику синхронизировать состояние до того, как pod начнёт отклонять соединения.
Netflix обнаружил в 2019, что без preStop sleep при деплое возникали ошибки 503 на 2-5 секунд. Добавление `sleep 15` в preStop полностью устранило проблему. Это стандартная практика для всех HTTP и WebSocket сервисов в K8s.
Зачем нужен preStop hook с sleep в Kubernetes для WebSocket-сервисов?
Rolling Updates для WebSocket: maxUnavailable и surge
Rolling update заменяет поды постепенно: 1 старый pod удаляется, 1 новый стартует, проходит healthcheck, следующий старый удаляется. Для stateless HTTP - идеально. Для WebSocket - нюансы.
При удалении пода все его соединения переходят через reconnect на оставшиеся поды. Если деплоится 50% подов одновременно - нагрузка на оставшиеся удваивается на время reconnect-шторма. При 100K соединений на кластере и 50% maxUnavailable - оставшиеся 50% подов получают 100K входящих reconnect одновременно.
Discord при деплое chat-сервисов использует maxUnavailable: 1 с 10-минутным drain timeout. При 100+ подах деплой занимает 2-3 часа, но ни одно соединение не обрывается принудительно. Для критичных real-time сервисов медленный деплой - это feature, не bug.
Почему для WebSocket-сервисов рекомендуется maxUnavailable: 1 вместо дефолтного 25%?
Blue-Green Deployment для WebSocket
Blue-green запускает полностью новую версию (green) параллельно со старой (blue). Когда green готов - трафик переключается целиком. Для HTTP это мгновенно. Для WebSocket - нет: существующие соединения на blue нужно дождаться или переключить.
Два варианта: **hard cutover** (переключить всё, старые соединения рвутся, клиенты reconnect'ятся на green) и **soft migration** (новые соединения идут на green, старые на blue продолжают работать, blue выключается после drain).
Проблема blue-green: вдвое больше ресурсов на время деплоя. При 100 подах нужно 200. Для небольших кластеров это может быть невозможно. Альтернатива - canary: направить 5% новых соединений на green, убедиться в корректности, постепенно увеличивать.
Twitch использует комбинацию: blue-green на уровне регионов (переключить целый регион), rolling update внутри региона. Это даёт быстрый rollback (переключить DNS региона обратно) без reconnect-шторма на весь кластер.
Blue-green deployment мгновенно переключает все WebSocket-соединения без потерь
Blue-green переключает только маршрутизацию новых соединений; существующие соединения должны или дождаться drain, или принудительно reconnect'иться
В отличие от HTTP где каждый запрос независим, WebSocket соединение - это длительная сессия с состоянием. Переключение DNS или load balancer не перемещает существующие TCP-соединения с одного пода на другой.
В чём отличие soft migration от hard cutover при blue-green для WebSocket?
Итоги
- Graceful shutdown: сначала `server.close()` для новых соединений, потом close frame существующим (код 1001), потом ожидание + принудительное закрытие
- preStop sleep 15s в K8s компенсирует race condition между SIGTERM и обновлением балансировщика
- maxUnavailable: 1 вместо дефолтных 25% предотвращает reconnect-шторм при удалении группы подов
- Blue-green soft migration: новые соединения на green, blue продолжает работать до естественного drain
Связанные темы
Graceful deployment - пересечение нескольких областей DevOps
- Kubernetes Deployment strategies — Rolling update, maxUnavailable, terminationGracePeriodSeconds - всё это K8s примитивы
- Load Balancer connection draining — AWS ALB, GCP Cloud Load Balancing, nginx upstream - каждый имеет свой drain timeout
- WebSocket reconnection logic — Graceful shutdown работает только если клиент умеет reconnect'иться с exponential backoff
- Feature Flags — Canary deployment через feature flags - альтернатива blue-green без удвоения ресурсов
Вопросы для размышления
- Как изменится стратегия деплоя если в системе есть in-flight сообщения в очереди, которые нельзя потерять?
- При drain 120 секунд и 1000 активных пользователей на поде - сколько времени займёт деплой 20 подов с maxUnavailable: 1?
- Как организовать blue-green если blue и green используют разные схемы БД, несовместимые между собой?