Короткий ответ
Нужно разделить проверки на readiness (готов принимать новый трафик, используется балансировщиком/оркестратором для маршрутизации) и liveness (процесс жив, используется для решения о перезапуске), обрабатывать сигнал остановки (SIGTERM) так, чтобы сначала перестать принимать новые запросы (readiness становится false), затем дать время завершить уже начатые запросы (graceful period), и только потом завершить процесс.
Как это работает подробнее
При роллинг-деплое оркестратор (например, Kubernetes) сначала помечает старый под как «не ready» и перестаёт направлять на него новый трафик, одновременно отправляя ему SIGTERM; у приложения есть ограниченное время (terminationGracePeriodSeconds) на завершение уже начатых запросов и закрытие соединений (БД, очереди), после чего процесс принудительно убивается SIGKILL. Uvicorn/Gunicorn поддерживают graceful shutdown, ожидая завершения активных запросов при получении сигнала остановки, но только в пределах настроенного таймаута.
Длинный graceful period снижает риск обрыва долгих запросов, но замедляет деплой и увеличивает время, когда на кластере одновременно работают старая и новая версия (что требует совместимости схем/контрактов между версиями на этот период). Короткий period ускоряет деплой, но рискует обрывать легитимные долгие запросы. Решение зависит от профиля длительности запросов сервиса и от того, насколько приемлема временная работа двух версий одновременно.
Пример кода
# Пример фрагмента конфигурации Kubernetes (концептуально)
readinessProbe:
httpGet: {path: /health/ready, port: 8000}
periodSeconds: 5
livenessProbe:
httpGet: {path: /health/live, port: 8000}
periodSeconds: 10
terminationGracePeriodSeconds: 30 # должен покрывать самые долгие легитимные запросы
# /health/ready - проверяет реальную готовность (соединение с БД и т.п.)
# /health/live - проверяет только, что процесс отвечает, без проверки внешних зависимостейЧто использовать на практике
- Практика. Readiness-проверка должна отражать реальную готовность (например, успешное соединение с БД/кэшем при старте), а не просто «процесс запущен» — иначе трафик пойдёт на инстанс, который ещё не готов обслуживать запросы. Время graceful period нужно подбирать с учётом самых долгих легитимных запросов приложения, иначе часть запросов всё равно будет прервана принудительно.
Подводные камни
- Риск. Используют одну и ту же проверку для readiness и liveness, из-за чего временная перегрузка (которая должна временно убрать инстанс из балансировки через readiness) вместо этого приводит к перезапуску здорового процесса через liveness; устанавливают слишком короткий graceful period, не учитывая реальное распределение длительности запросов, из-за чего долгие запросы обрываются при каждом деплое.