Опирается на правила: R-SHUT-K8S-1R-SHUT-K8S-3 и R-SHUT-K8S-X1R-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:

  1. Spring publishes REFUSING_TRAFFIC.
  2. Readiness endpoint → 503.
  3. K8s через periodSeconds: 5 × failureThreshold: 2 = 10s видит fail.
  4. 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 — обязательно.

Что запрещено — таблица

АнтипаттернПравилоЧто взамен
Нет preStopR-SHUT-K8S-X1sleep 10 обязателен
terminationGracePeriodSeconds: 30 (default)R-SHUT-K8S-X260
Один /actuator/health endpoint для обеих probesR-SHUT-K8S-2отдельные /liveness и /readiness
Liveness зависит от DBR-SHUT-K8S-2только readiness
maxUnavailable > 0 без причиныR-SHUT-K8S-30 для zero-downtime
maxSurge: 0 (no extra pod)R-SHUT-K8S-3maxSurge: 1 минимум
initialDelaySeconds: 0 на livenessR-SHUT-K8S-230-60s для прогрева JVM
preStop через httpGet (flaky)R-SHUT-K8S-X1exec 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 как параллель.