Навіщо потрібні Health Checks в Kubernetes?
Health checks в K8s -- це сукупність liveness, readiness і startup probes. Кожен вирішує своє завдання: liveness = чи живий, readiness = чи готовий, startup = чи стартував.
🟢 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 виконує три типи перевірок незалежно одна від одної:
-
Liveness Probe — kubelet опитує endpoint. При провалі (failureThreshold разів поспіль) kubelet вбиває контейнер і контейнерне середовище його перезапускає. Restart count збільшується.
-
Readiness Probe — kubelet опитує endpoint. При провалі Pod видаляється з Endpoints усіх пов’язаних Services. Контейнер продовжує працювати.
-
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/).
Архітектура:
- worker — goroutine на кожну пробу, виконує HTTP/TCP/exec перевірки
- manager — координує workers, зберігає результати в
results.Manager - 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 проба вважається 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-systemnamespace)
Production War Story
Ситуація: Фінтех-компанія, кластер з 500 нод, 5000 Pod’ів. Усі сервіси мали один health ендпоинт /health, який перевіряв БД, Redis, та Kafka. Під час інциденту Redis почав сповільнювати (99th percentile latency зросла з 5ms до 500ms). Liveness Probe почав таймаутити, оскільки перевірка Redis займала 10+ секунд при timeoutSeconds: 5.
Ефект доміно:
- Liveness Probe провалився → kubelet вбив 2000 Pod’ів за 2 хвилини
- Нові Pod’и стартували, але startupProbe перевіряла Redis → теж таймаутили
- HSA побачив падіння реплік → створив ще 3000 Pod’ів
- Кластер досяг ліміту Pod’ів на нодах → нові Pod’и в Pending
- Повний outage на 45 хвилин
Post-mortem та fix:
- Розділені ендпоинти:
/health/live(без зовнішніх залежностей),/health/ready(з Circuit Breaker) - startupProbe прибрана перевірка Redis — тільки JVM startup
- Додано Redis Circuit Breaker з fallback на degraded mode
- HPA обмежено
maxReplicasтаstabilizationWindowSeconds: 300 - Впроваджено 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 панелі:
- Pod Restarts Rate (по namespace) — червона лінія при > 0.1/sec
- Liveness vs Readiness failure rate — кореляція
- Probe latency p50/p99 — має бути стабільною
- HPA current vs desired replicas
- Correlate з infrastructure metrics: node CPU, memory, disk I/O
Highload Best Practices
- Завжди розділяйте liveness і readiness ендпоинти — liveness = “процес живий”, readiness = “можу обслуговувати”
- Використовуйте startupProbe для JVM-додатків — це порятунок для Spring Boot з 2-3 хвилинним стартом
- Liveness без зовнішніх залежностей — перевіряйте тільки deadlock, thread starvation, критичну нестачу пам’яті
- Readiness з Circuit Breaker — якщо залежність недоступна, повертайте Ready в degraded mode, а не відключайте Pod
- timeoutSeconds >= p99 latency вашого ендпоинту — враховуйте worst-case з GC pause
- Не використовуйте
:latest— Kubernetes не виявить зміну і не запустить Rolling Update - Graceful Shutdown + preStop hook — дайте час на завершення запитів:
lifecycle: preStop: exec: command: ["sh", "-c", "sleep 10"] terminationGracePeriodSeconds: 60 - Monitor restart rate — більше 1 перезапуску в хвилину на Pod = проблема з liveness конфігурацією або додатком
- Використовуйте Prometheus Operator + kube-state-metrics — для автоматичного збору метрик про health checks
- Chaos Engineering — регулярно тестуйте сценарії: падіння БД, network partition, node drain — і перевіряйте, як health checks справляються
🎯 Шпаргалка для інтерв’ю
Обов’язково знати:
- 3 типи проб: Liveness (перезапуск), Readiness (прибрати з трафіку), Startup (захист при старті)
- Startup Probe блокує liveness і readiness до першого успіху — порятунок для JVM
- Liveness БЕЗ зовнішніх залежностей, Readiness МОЖЕ перевіряти залежності (з Circuit Breaker)
- Death Spiral — агресивні liveness налаштування → масове вбивство 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» (зовнішні проблеми → масове вбивство)
- «Health checks не потрібні в dev» (баги конфігурації спливуть тільки в prod)
- «Startup Probe = Liveness з великим delay» (ні, startup блокує liveness/readiness)
Пов’язані теми:
- [[Що таке liveness probe]] — детально про liveness
- [[Що таке readiness probe]] — детально про readiness
- [[Як організувати rolling update в Kubernetes]] — health checks при деплої