Опирается на правила:
R-SHUT-K8S-1…R-SHUT-K8S-3иR-SHUT-K8S-X1…R-SHUT-K8S-X2из Graceful Shutdown Style Guide → раздел 6. Kubernetes.
Важно знать
terminationGracePeriodSeconds: 60обязательно — не k8s default 30.preStop sleep 10— отдельный бюджет, не входит вterminationGracePeriodSeconds.readinessProbeна/actuator/health/readiness,livenessProbeна/actuator/health/liveness.- На shutdown нужен readiness=503 (убирает из endpoints), не liveness (перезапускает pod).
maxSurge: 1, maxUnavailable: 0на rolling deploy — zero downtime.- Без
preStop— гарантированные 502 в окне 5-15 секунд.terminationGracePeriodSeconds: 30+ Spring graceful 30s — pod SIGKILL посередине дрейна.
K8s — это контекст, в котором живёт graceful shutdown. Spring graceful без правильной k8s-конфигурации работает только частично: pod корректно завершает свои tasks, но клиенты ещё видят 502 — потому что kube-proxy не успел убрать pod из endpoints. UCP формулирует k8s-настройки, замыкающие цепочку graceful.
terminationGracePeriodSeconds: 60
R-SHUT-K8S-1: Total budget.
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
image: order-service:1.4.2
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"]
K8s shutdown sequence:
T=0 kubectl delete pod (или rolling deploy)
T=0 kubelet начинает shutdown sequence:
- Запускает preStop hook
- Параллельно убирает pod из Service endpoints
T=10s preStop sleep завершён → kubelet шлёт SIGTERM
T=10..70s Process получает 60 секунд для graceful (terminationGracePeriodSeconds)
T=70s Если процесс ещё жив → SIGKILL
terminationGracePeriodSeconds — только после preStop. Не дефолт-30 потому что:
- 30s =
preStop 10s+Spring graceful 30sуже не помещается (40s > 30s). - Pod уходит в SIGKILL посередине дрейна, активные requests прерываются.
- UCP стандарт — 60s, позволяет распределить бюджет (см. Бюджеты).
Probes — отдельные endpoints
R-SHUT-K8S-2: readiness ≠ liveness.
spec:
containers:
- name: app
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 2
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
initialDelaySeconds: 60
| Probe | Что проверяет | Действие k8s при fail |
|---|---|---|
| readinessProbe | /actuator/health/readiness | Убрать из endpoints (не рестартует) |
| livenessProbe | /actuator/health/liveness | Рестартует pod |
На shutdown работает readiness:
- Spring publishes
REFUSING_TRAFFIC. - Readiness endpoint → 503.
- K8s через
periodSeconds: 5×failureThreshold: 2= 10s видит fail. - Pod removed from endpoints, traffic stops.
Liveness не должна падать на shutdown:
- Если падает — k8s рестартует pod вместо корректного завершения.
- Restart loop — старый pod не успевает graceful, новый ещё не готов.
- Это destroys graceful shutdown.
Подробнее различие — Observability → health checks.
initialDelaySeconds для liveness
initialDelaySeconds: 60 на liveness — даёт приложению 60s для старта без риска restart-цикла. Spring Boot стартует 20-30s + первые requests могут долго отвечать (cold caches) → 60s baseline.
Readiness может начать срабатывать сразу — она проверяет «готов ли принимать трафик», и если не готов (initializing) — 503 нормально, traffic просто не идёт.
maxSurge / maxUnavailable
R-SHUT-K8S-3: zero-downtime rolling deploy.
spec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
Что значит:
maxSurge: 1— k8s может создать на 1 pod больше во время deploy (4 вместо 3).maxUnavailable: 0— ни один pod не должен быть unavailable одновременно.
Sequence rolling deploy:
Состояние: 3 old pods (v1)
T=0 Create новый pod (v2). Теперь 4 pods (3 v1 + 1 v2)
T=N Новый pod v2 проходит readiness probe → joins endpoints
T=N Старый pod v1 #1 начинает shutdown:
- preStop sleep 10
- SIGTERM
- readiness 503 → removed from endpoints
- graceful drain
- exit
Теперь: 2 v1 + 1 v2 = 3 active
T=N Create v2 #2. Теперь 4 pods (2 v1 + 2 v2)
...
Без maxUnavailable: 0:
- K8s может убить v1 до создания v2.
- В момент N pods active < 3 → меньше capacity → возможны 503 на пиковом traffic.
maxSurge: 1 достаточно для small/medium replicas. Для high-traffic (50+ pods) — maxSurge: 25%.
Что запрещено
Отсутствие preStop
R-SHUT-K8S-X1: критическая ошибка.
# КАТАСТРОФА
spec:
containers:
- name: app
image: order-service:1.4.2
# нет preStop
Сценарий:
T=0 SIGTERM отправлен сразу (нет preStop)
T=0+ Spring graceful → readiness=503
T=0+ K8s readiness probe ещё не успела опросить (period 5s)
T=0..15s Pod ещё в endpoints на других нодах (kube-proxy не обновился)
T=0..15s Traffic продолжает идти на наш pod
T=0+ Spring уже не принимает → 502 / connection refused для клиентов
В окне 5-15 секунд — гарантированные 502. На большом deploy (rolling нескольких pods) — каждый pod даёт 5-15s 502 → tens of thousands errors.
preStop sleep 10 — обязательно (см. HTTP drain).
terminationGracePeriodSeconds: 30
R-SHUT-K8S-X2: дефолт k8s не подходит.
# ПЛОХО
spec:
terminationGracePeriodSeconds: 30 # default
containers:
- lifecycle:
preStop:
exec: { command: ["sh", "-c", "sleep 10"] }
С preStop 10s — Spring graceful получает только 20s. Если timeout-per-shutdown-phase: 30s — 10s не хватает, SIGKILL.
terminationGracePeriodSeconds: 60 — обязательно.
Что запрещено — таблица
| Антипаттерн | Правило | Что взамен |
|---|---|---|
Нет preStop | R-SHUT-K8S-X1 | sleep 10 обязателен |
terminationGracePeriodSeconds: 30 (default) | R-SHUT-K8S-X2 | 60 |
Один /actuator/health endpoint для обеих probes | R-SHUT-K8S-2 | отдельные /liveness и /readiness |
| Liveness зависит от DB | R-SHUT-K8S-2 | только readiness |
maxUnavailable > 0 без причины | R-SHUT-K8S-3 | 0 для zero-downtime |
maxSurge: 0 (no extra pod) | R-SHUT-K8S-3 | maxSurge: 1 минимум |
initialDelaySeconds: 0 на liveness | R-SHUT-K8S-2 | 30-60s для прогрева JVM |
preStop через httpGet (flaky) | R-SHUT-K8S-X1 | exec sleep |
Куда дальше
- Graceful Shutdown → раздел 6. Kubernetes — нормативные формулировки.
- HTTP drain — preStop sleep детали.
- JVM/Spring конфигурация — Spring graceful timeout.
- Бюджеты и observability — раскладка 60s.
- Observability → health checks — liveness vs readiness.
- Container/image-уязвимости — HEALTHCHECK как параллель.