Короткий ответ
Graceful shutdown требует сначала убрать экземпляр из балансировки и дождаться завершения активных запросов, затем отправить SIGTERM и оставить запас до SIGKILL. Полностью исключить потери нельзя: нужны идемпотентность, корректные тайм-ауты и повторные запросы.
Как это работает подробнее
Корректный drain подразумевает: пометку инстанса как 'не готов принимать новый трафик' (например, readiness probe начинает возвращать failure, или инстанс явно удаляется из пула балансировщика), ожидание, пока все уже принятые запросы и долгоживущие соединения (включая держащиеся keep-alive/websocket) корректно завершатся, и только после этого отправку сигнала завершения процессу; между моментом исключения из пула балансировщика и моментом, когда сам балансировщик перестанет присылать новый трафик, всегда существует небольшое окно гонки, за время которого ещё могут прийти несколько новых запросов на уже 'выключающийся' инстанс.
Пример кода
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"] # дать время на propagation из балансировщика
terminationGracePeriodSeconds: 45 # с запасом над макс. временем запросаЧто использовать на практике
- Практика. для компенсации этого окна гонки вводят небольшую задержку (preStop hook в Kubernetes, sleep перед реальной остановкой) между исключением из балансировки и остановкой процесса, чтобы дать время на распространение изменений состояния по всем узлам балансировщика; также необходимо устанавливать terminationGracePeriodSeconds/TimeoutStopSec с запасом над максимальным реальным временем обработки самого долгого запроса, а не произвольным 'круглым' числом, и отдельно обрабатывать долгоживущие соединения (WebSocket, gRPC streaming), которым недостаточно просто 'доработать один HTTP-запрос'.
- Компромисс. Во время контролируемого скейлинга/релиза нужно замерить долю ошибок (5xx/connection reset) у клиентов именно в момент остановки подов; значение, близкое к нулю, подтверждает, что preStop-задержка и grace period подобраны достаточно, а не просто 'выглядят разумно'.
Подводные камни
- Риск. сразу останавливать процесс после изменения readiness без задержки на propagation delay, из-за чего часть новых запросов всё равно попадает на уже остановленный инстанс; устанавливать слишком короткий grace period, из-за чего долгие запросы обрываются принудительным SIGKILL; не учитывать долгоживущие соединения отдельно от обычных коротких HTTP-запросов.