Вопрос 19 · Раздел 14

Зачем нужны Health Checks в Kubernetes?

Health checks в K8s -- это совокупность liveness, readiness и startup probes. Каждый решает свою задачу: liveness = жив ли, readiness = готов ли, startup = стартовал ли.

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Простое определение

Health Checks (Проверки здоровья) — это механизмы Kubernetes, которые позволяют оркестратору понять, в каком состоянии находится приложение: запущено ли оно, готово ли принимать трафик, и не застряло ли. На основе этих проверок Kubernetes автоматически принимает решения: перезапустить контейнер, убрать его из балансировщика или дать больше времени на запуск.

Health checks в K8s – это совокупность liveness, readiness и startup probes. Каждый решает свою задачу: liveness = жив ли, readiness = готов ли, startup = стартовал ли.

Аналогия

Представьте, что вы управляете сетью кофеен. Health Checks — это ежедневные отчёты каждой кофейни:

  • Liveness: “Мы открыты, свет горит” (контейнер жив)
  • Readiness: “Кассы работают, бариста на месте, можем принимать клиентов” (готов к трафику)
  • Startup: “Мы ещё делаем ремонт, скоро откроемся” (дайте время на запуск)

Пример YAML (все три пробы)

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
    - name: app
      image: my-app:1.0
      startupProbe:
        httpGet:
          path: /health/startup
          port: 8080
        failureThreshold: 30
        periodSeconds: 10
      livenessProbe:
        httpGet:
          path: /health/liveness
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 10
      readinessProbe:
        httpGet:
          path: /health/readiness
          port: 8080
        periodSeconds: 5
        failureThreshold: 3

Пример kubectl

# Посмотреть статус Pod'а
kubectl get pods
# READY: 1/1 — все пробы прошли, 0/1 — readiness провалена

# Детальная информация о пробах
kubectl describe pod my-app

# Проверить события (перезапуски из-за liveness)
kubectl get events --sort-by='.lastTimestamp'

Когда использовать

  • Всегда в production — без health checks Kubernetes не сможет автоматически восстанавливать приложение
  • Для любых микросервисов, API, веб-приложений
  • Для Java-приложений с долгим стартом (JVM, Spring Boot) — обязательно startupProbe

Когда НЕ использовать liveness probe

НЕ используйте liveness probe для stateful-приложений (базы данных) – перезапуск может усугубить проблему. Вместо этого используйте readiness probe + мониторинг.


🟡 Middle Level

Как это работает

Kubernetes kubelet выполняет три типа проверок независимо друг от друга:

  1. Liveness Probe — kubelet опрашивает эндпоинт. При провале (failureThreshold раз подряд) kubelet убивает контейнер и контейнерная среда его перезапускает. Restart count увеличивается.

  2. Readiness Probe — kubelet опрашивает эндпоинт. При провале Pod удаляется из Endpoints всех связанных Services. Контейнер продолжает работать.

  3. Startup Probe — kubelet опрашивает до первого успеха. Пока startupProbe не прошла, liveness и readiness отключены. После первого успеха startupProbe больше не выполняется.

Каждая проба выполняется в отдельной goroutine kubelet’а с таймаутом. Если проба не ответила в течение timeoutSeconds, она считается проваленной.

Практические сценарии

Сценарий 1: Java-приложение с долгим стартом (3 минуты)

startupProbe:
  httpGet:
    path: /actuator/health
    port: 8080
  failureThreshold: 30     # 30 попыток
  periodSeconds: 10        # каждые 10 секунд = 5 минут максимум
# Только после успеха startupProbe:
livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 10        # проверяем каждые 10 секунд

Без startupProbe пришлось бы ставить initialDelaySeconds: 180 для liveness, что означало бы 3 минуты без проверки зависаний после запуска.

Сценарий 2: Временная перегрузка Приложение получает слишком много запросов, очередь переполнена. Readiness Probe возвращает 503, Pod снимается с балансировки, обрабатывает накопленные задачи и возвращается в строй.

Сценарий 3: Deadlock detection Liveness Probe проверяет не только HTTP, но и внутреннее состояние. При обнаружении deadlock — проба проваливается, контейнер перезапускается.

Таблица распространённых ошибок

Ошибка Последствие Решение
Все три пробы на одном эндпоинте Невозможно различить “завис” и “не готов” Разделить /health/liveness, /health/readiness, /health/startup
Liveness проверяет внешнюю БД При падении БД все Pod’ы перезапускаются бесконечно Liveness проверяет только внутреннее состояние, без внешних зависимостей
Слишком агрессивный failureThreshold Лишние перезапуски при кратковременных проблемах Ставить 3-5 для liveness, 15-30 для startup
Отсутствие health checks Зависшие Pod’ы получают трафик, пользователи видят 502 Всегда добавлять все три пробы в production
timeoutSeconds слишком маленький (1 сек) Провалы при GC pause или slow requests Ставить 3-10 секунд, учитывая worst-case latency приложения
Использование :latest тега Kubernetes не видит изменений и не запускает Rolling Update Всегда использовать конкретные теги или SHA256 digest

Сравнительная таблица всех трёх проб

Характеристика Startup Probe Liveness Probe Readiness Probe
Вопрос “Запустился ли я?” “Жив ли я?” “Готов ли к трафику?”
Действие при сбое Повторная попытка (до failureThreshold) Перезапуск контейнера Удаление из Service Endpoints
Когда отключена После первого успеха — навсегда Работает всегда (если нет startupProbe) Работает всегда (если нет startupProbe)
Взаимодействие Блокирует liveness и readiness Перезапускает Снимает с балансировки
Типичный periodSeconds 5-10 10-30 3-10
Типичный failureThreshold 15-30 3-5 3
Проверяет зависимости? Нет Нет Да

Когда НЕ использовать

  • Короткоживущие Jobs/CronJobs — если Pod запускается, выполняет задачу и завершается, health checks не нужны (или нужны только для отладки)
  • Sidecar-контейнеры без сетевого интерфейса — если контейнер только пишет логи или собирает метрики, liveness может быть достаточно, readiness — нет
  • Init-контейнеры — они запускаются до основных и завершаются, health checks к ним не применяются

🔴 Senior Level

Глубинная механика: kubelet probe manager

Health Checks управляются компонентом kubelet, а точнее — подсистемой prober в исходном коде kubelet’а (pkg/kubelet/prober/).

Архитектура:

  1. worker — goroutine на каждую пробу, выполняет HTTP/TCP/exec проверки
  2. manager — координирует workers, хранит результаты в results.Manager
  3. statusManager — передаёт результаты в PodStatus, который отправляется в API Server

kubelet выполняет пробы асинхронно. Каждый worker имеет свою очередь (workqueue) и timeout. При timeout проба считается проваленной, даже если нет ответа.

Startup Probe + Liveness/Readiness взаимодействие:

Pod создан → startupProbe активна, liveness/readiness отключены
→ kubelet выполняет startupProbe каждые periodSeconds
→ При success: startupProbe отключается навсегда, liveness/readiness активируются
→ При failure (failureThreshold подряд): контейнер убивается (только с Kubernetes 1.20+)

До K8s 1.20 failure в startupProbe не приводил к убийству контейнера — это было исправлено в PR #95190.

Trade-offs

Аспект Trade-off
Startup vs initialDelaySeconds startupProbe гибче: даёт много времени на старт, но потом быстрые liveness. initialDelaySeconds — либо слишком долго, либо слишком агрессивно
Частота liveness Частые проверки = быстрее обнаружение deadlocks, но выше CPU overhead и ложные срабатывания при GC pause
HTTP vs exec HTTP быстрее и даёт status code, но exec может проверить глубже (файлы, процессы). exec медленнее и создаёт дополнительные процессы
Зависимости в Readiness Проверка зависимостей = честный статус, но риск каскадного отказа. Без проверки = Pod получает трафик, но не может его обработать
Single vs Multi endpoint Один эндпоинт проще, но менее информативен. Раздельные эндпоинты точнее, но требуют больше кода и обслуживания

Edge Cases (6+)

Edge Case 1: GC Pause в JVM и Liveness Probe JVM может остановить все потоки на 5-30 секунд при Full GC (особенно без ZGC/Shenandoah). Если timeoutSeconds: 3 и failureThreshold: 3, три проваленные пробы убьют контейнер. Решение: ZGC/G1GC с MaxGCPauseMillis, timeoutSeconds: 10, или отдельный native sidecar для health checks вне JVM.

Edge Case 2: Cascading Failure через Readiness Все микросервисы проверяют одну БД в readinessProbe. БД замедляется → все Pod’ы одновременно отключаются от трафика → полный outage. Решение: Readiness проверяет только внутреннее состояние, а доступность БД обрабатывается через Circuit Breaker на уровне бизнес-логики.

Edge Case 3: kubelet restart сбрасывает probe state При рестарте kubelet теряет счётчик consecutive failures для startupProbe. Pod, который был близок к failureThreshold, получает “чистый лист”. Это может привести к тому, что никогда не запускающееся приложение будет бесконечно пытаться стартовать.

Edge Case 4: Containerd/CRI-O timeout Если kubelet не может выполнить exec пробу из-за проблем с CRI (containerd/CRI-O завис), проба считается проваленной. Это не проблема приложения, но контейнер всё равно будет перезапущен. Мониторьте kubelet_pod_worker_duration_seconds.

Edge Case 5: Readiness + HPA race condition HPA проверяет CPU/RPS. Readiness отключает Pod от трафика. HPA видит падение RPS на Pod и создаёт новые реплики. Но новые тоже могут провалить Readiness. Результат: explosion Pod’ов без реальной пользы. Решение: behavior.stabilizationWindowSeconds в HPA + proper Readiness без внешних зависимостей.

Edge Case 6: Liveness Probe + Graceful Shutdown conflict Liveness Probe проваливается, kubelet убивает контейнер. Одновременно приложение получает SIGTERM и начинает graceful shutdown (завершает запросы 30 сек). Но kubelet уже запустил новый контейнер. Два экземпляра одного приложения работают параллельно, возможен conflict на ресурсы (файлы, порты, БД коннекты).

Edge Case 7: Multi-container Pod с разными пробами В Pod’е 2 контейнера: app и sidecar (envoy). Liveness у app проходит, у sidecar — нет. kubelet перезапускает sidecar, но app продолжает работать. Если sidecar критичен (например, service mesh proxy), app работает без прокси. Решение: использовать ShareProcessNamespace и проверять оба контейнера.

Performance Numbers

Метрика Значение
kubelet probe overhead (HTTP) ~1-5ms CPU, ~10-50KB RAM на одну пробу
kubelet probe overhead (exec) ~10-50ms CPU, создание нового процесса
Probe timeout влияние При timeout probe считается failed, но goroutine ждёт timeout перед очисткой
API Server PodStatus update latency 10-100ms
EndpointSlice propagation delay 500ms-2s
kubelet restart probe state loss Полная потеря счётчика consecutive failures
JVM Full GC pause (G1GC) 100ms-2s, (ZGC) 1-10ms
Максимум проб на Pod Не ограничен, но каждая проба — goroutine, ~2KB stack

Security

  • Health эндпоинты не должны быть публичными — они могут раскрыть внутреннюю структуру приложения
  • exec пробы запускаются от имени контейнера — если контейнер работает как root, exec probe тоже root. Это не эскалация, но стоит учитывать
  • Liveness Probe не должен иметь side-effects — злоумышленник с доступом к Pod может вызвать liveness эндпоинт и спровоцировать перезапуск (DoS)
  • mTLS для health эндпоинтов — если sidecar proxy (Istio) перехватывает весь трафик, health эндпоинты должны быть исключены из mTLS или использовать отдельный порт
  • NetworkPolicy — ограничьте доступ к health эндпоинтам только от kubelet (через kube-system namespace)

Production War Story

Ситуация: Финтех-компания, кластер из 500 нод, 5000 Pod’ов. Все сервисы имели один health эндпоинт /health, который проверял БД, Redis, и Kafka. Во время инцидента Redis начал тормозить (99th percentile latency выросла с 5ms до 500ms). Liveness Probe начал таймаутить, так как проверка Redis занимала 10+ секунд при timeoutSeconds: 5.

Эффект домино:

  1. Liveness Probe провалился → kubelet убил 2000 Pod’ов за 2 минуты
  2. Новые Pod’ы стартовали, но startupProbe проверяла Redis → тоже таймаутили
  3. HPA увидел падение реплик → создал ещё 3000 Pod’ов
  4. Кластер достиг лимита Pod’ов на нодах → новые Pod’ы в Pending
  5. Полный outage на 45 минут

Post-mortem и fix:

  1. Разделены эндпоинты: /health/live (без внешних зависимостей), /health/ready (с Circuit Breaker)
  2. startupProbe убрана проверка Redis — только JVM startup
  3. Добавлен Redis Circuit Breaker с fallback на degraded mode
  4. HPA ограничен maxReplicas и stabilizationWindowSeconds: 300
  5. Внедрён Chaos Engineering — регулярное тестирование сценариев отказа

Мониторинг после fix:

# Alert: Liveness перезапуски растут
rate(kube_pod_container_status_restarts_total[5m]) > 0.1

# Alert: Readiness failures
sum(kube_pod_status_ready{condition="false"}) by (namespace) > 10

# Alert: HPA scaling слишком агрессивный
kube_horizontalpodautoscaler_status_desired_replicas - kube_horizontalpodautoscaler_spec_min_replicas > 5

Monitoring (Prometheus/Grafana)

Ключевые метрики:

# Перезапуски контейнеров (liveness failures)
rate(kube_pod_container_status_restarts_total[5m])

# Pod'ы не в Ready состоянии
sum(kube_pod_status_ready{condition="false"}) by (namespace)

# kubelet probe latency (должна быть < periodSeconds)
histogram_quantile(0.99, kubelet_pod_worker_duration_seconds_bucket)

# Probe timeout events (через kubelet logs)
kubelet_pod_probe_failed_total

# HPA scaling events
kube_horizontalpodautoscaler_status_current_replicas

# EndpointSlice availability
sum(kube_endpoint_address_available) by (service)

Grafana Dashboard панели:

  1. Pod Restarts Rate (по namespace) — красная линия при > 0.1/sec
  2. Liveness vs Readiness failure rate — корреляция
  3. Probe latency p50/p99 — должна быть стабильной
  4. HPA current vs desired replicas
  5. Correlate с infrastructure metrics: node CPU, memory, disk I/O

Highload Best Practices

  1. Всегда разделяйте liveness и readiness эндпоинты — liveness = “процесс жив”, readiness = “могу обслуживать”
  2. Используйте startupProbe для JVM-приложений — это спасение для Spring Boot с 2-3 минутным стартом
  3. Liveness без внешних зависимостей — проверяйте только deadlock, thread starvation, критическую нехватку памяти
  4. Readiness с Circuit Breaker — если зависимость недоступна, возвращайте Ready в degraded mode, а не отключайте Pod
  5. timeoutSeconds >= p99 latency вашего эндпоинта — учитывайте worst-case с GC pause
  6. Не используйте :latest — Kubernetes не обнаружит изменение и не запустит Rolling Update
  7. Graceful Shutdown + preStop hook — дайте время на завершение запросов:
    lifecycle:
      preStop:
        exec:
          command: ["sh", "-c", "sleep 10"]
    terminationGracePeriodSeconds: 60
    
  8. Monitor restart rate — более 1 перезапуска в минуту на Pod = проблема с liveness конфигурацией или приложением
  9. Используйте Prometheus Operator + kube-state-metrics — для автоматического сбора метрик о health checks
  10. Chaos Engineering — регулярно тестируйте сценарии: падение БД, network partition, node drain — и проверяйте, как health checks справляются

🎯 Шпаргалка для интервью

Обязательно знать:

  • 3 типа проб: Liveness (перезапуск), Readiness (убрать из трафика), Startup (защита при старте)
  • Startup Probe блокирует liveness и readiness до первого успеха — спасение для JVM
  • Liveness БЕЗ внешних зависимостей, Readiness МОЖЕТ проверять зависимости (с Circuit Breaker)
  • Death Spiral — агрессивные liveness настройки → массовый kill Pod’ов → outage
  • Для Java: timeout > max GC pause, startupProbe для JIT warmup (failureThreshold: 30)
  • Разделяйте эндпоинты: /health/live, /health/ready, /health/startup
  • Без health checks K8s не сможет автоматически восстанавливать приложение

Частые уточняющие вопросы:

  • «Зачем все 3 пробы?» — Startup даёт время на старт, Liveness ловит зависания, Readiness — готовность к трафику
  • «Liveness для stateful-приложений?» — Нет, перезапуск может усугубить проблему; используйте readiness + мониторинг
  • «Kubelet restart сбрасывает probe state?» — Да, теряет счётчик consecutive failures для startupProbe
  • «Exec vs HTTP probe?» — HTTP быстрее, exec проверяет глубже; exec создаёт доп. процессы

Красные флаги (НЕ говорить):

  • «Все пробы на одном эндпоинте» (невозможно различить «завис» и «не готов»)
  • «Liveness проверяет БД, кэш, внешние API» (внешние проблемы → массовый kill)
  • «Health checks не нужны в dev» (баги конфигурации всплывут только в prod)
  • «Startup Probe = Liveness с большим delay» (нет, startup блокирует liveness/readiness)

Связанные темы:

  • [[Что такое liveness probe]] — детально о liveness
  • [[Что такое readiness probe]] — детально о readiness
  • [[Как организовать rolling update в Kubernetes]] — health checks при деплое