Graceful shutdown сервлет-контейнера в Kubernetes

02.06.2026 · Spring Boot 3, Kubernetes 1.30+

Симптом знаком многим: при каждой выкатке в логах балансировщика появляется горстка ошибок 502 или сброшенных соединений. При этом в приложении включён server.shutdown=graceful, и по логам видно, что все начатые запросы честно завершились.

Что происходит при остановке пода

Когда под удаляется, Kubernetes делает две вещи параллельно: отправляет контейнеру SIGTERM и начинает убирать адрес пода из Endpoints. Пока это изменение дойдёт до kube-proxy на всех узлах и до ingress-контроллера, проходит от долей секунды до нескольких секунд.

Spring Boot на SIGTERM сразу перестаёт принимать новые соединения и ждёт активные запросы. Но балансировщики в этот момент ещё считают под живым и продолжают слать на него трафик — эти запросы и превращаются в ошибки.

Пауза в preStop

Решение — дать сети время забыть про под, прежде чем приложение начнёт останавливаться. Для этого служит хук preStop: он выполняется до отправки SIGTERM.

lifecycle:
  preStop:
    sleep:
      seconds: 10
terminationGracePeriodSeconds: 45

Встроенное действие sleep появилось в Kubernetes 1.29 и включено по умолчанию с 1.30. В старых кластерах то же самое делают через exec с sleep 10, но для этого в образе должна быть оболочка — в distroless-образах её нет.

Как сложить таймауты

Время из terminationGracePeriodSeconds (по умолчанию 30 секунд) отсчитывается с момента начала остановки и включает в себя preStop. Поэтому бюджет складывается так:

Если сумма больше grace period, Kubernetes просто пришлёт SIGKILL, и вся аккуратность пропадёт. В примере выше 10 + 30 секунд укладываются в 45 с запасом.

Проверка

Лучший тест — нагрузка во время выкатки: даже простой цикл с curl на сотню запросов в секунду сразу показывает, теряются ли запросы. Без такой проверки graceful shutdown легко считать работающим только потому, что его включили в настройках.