Probes: liveness, readiness, startup
Probes — это механизм, через который kubelet периодически проверяет состояние контейнера. В Kubernetes есть три типа probes, и они отвечают на разные вопросы. Перепутать их — главная причина restart loops в production и провальных задач на CKAD. Все три probes задаются в spec.containers[].livenessProbe / readinessProbe / startupProbe и выполняются kubelet-ом на узле, где запущен Pod — не из control plane, не из Service.
HEALTHCHECK в Dockerfile: проверка живости контейнера
Три probes — три разных вопроса
- livenessProbe отвечает на вопрос «жив ли процесс, или его надо рестартануть». При failure kubelet убивает контейнер и применяет
restartPolicy(по умолчанию Always для Deployment-ов). Use case — детект deadlock, hung process, in-memory corruption. Это последняя инстанция, а не первая линия защиты. - readinessProbe отвечает на вопрос «можно ли направлять трафик в этот Pod». При failure kubelet убирает Pod IP из Endpoints соответствующих Services. Контейнер при этом продолжает работать. Use case — temporary unavailability: соединение с БД упало, кэш прогревается, idle-после-deploy.
- startupProbe (GA с v1.20) отвечает на вопрос «закончился ли начальный warm-up». Пока startup не PASS — kubelet не запускает liveness и readiness probes. После первого success — startup отключается навсегда (для этого экземпляра контейнера), и начинают работать остальные. Use case — slow-starting apps (Java с JIT warmup, миграции БД на старте).
Discovery vs health: ключевая ментальная модель
readiness — это про маршрутизацию трафика. liveness — это про восстановление контейнера. Это разные слои:
- Service routing смотрит на Endpoints. Endpoints контролируется readinessProbe. Контейнер может быть жив (liveness PASS), но временно не готов принимать трафик (readiness FAIL) — Service вырезает его, остальные replicas принимают нагрузку. Когда зависимость восстановится — readiness PASS, Pod возвращается в Endpoints. Никаких рестартов.
- Lifecycle контейнера смотрит на liveness. Контейнер может быть в Endpoints (readiness PASS), но если процесс залип (event loop deadlock, например) — liveness FAIL, kubelet убивает его. Новый контейнер стартует — при наличии startupProbe он сначала пройдёт warm-up, readiness probe вернёт его в Endpoints.
Если Pod должен быть рестартован, когда зависимость недоступна — это почти всегда ошибка дизайна. Зависимость может быть down минуты или часы, и рестарт никак не помогает. Helps только readinessProbe — Pod снимается из трафика, остальные replicas (если они есть) обслуживают запросы.
startupProbe: зачем он нужен отдельно
До v1.20 единственным способом дать app медленно стартовать был initialDelaySeconds на livenessProbe. Это плохо работало: если приложение обычно стартует за 30 секунд, но иногда — за 90 (cold JVM, медленный диск), initialDelaySeconds: 30 не помогал — после 30 секунд начиналась проба, она fail-ила, contаiner убивался, новый стартовал — restart loop.
Решение в startupProbe:
- На startup даётся большой запас (например, `failureThreshold: 30 + periodSeconds: 10 = 5 минут максимум**).
- После первого success — startup отключается и начинает работать агрессивная liveness (`failureThreshold: 3 + periodSeconds: 10 = 30 секунд** до рестарта при настоящем зависании).
Без startupProbe приходится делать ужасный компромисс: либо liveness рестартует медленный, но здоровый процесс на старте, либо liveness настолько ленивая, что зависший контейнер живёт минуты.
Анти-паттерны на старте
1. Liveness too aggressive на slow-starting app. Java apps с 60+ секунд warmup, livenessProbe с initialDelaySeconds: 30, periodSeconds: 10, failureThreshold: 3 — через минуту контейнер убит. Лечится startupProbe.
2. readiness == liveness. Один и тот же endpoint /health используется в обоих probes — они ничего не различают. Когда /health fail-ит, Pod и убирается из Endpoints, и рестартуется. Цель readiness была — оставить контейнер живым, чтобы дать ему время восстановиться. Эффект противоположный.
3. Heavy probe. probe делает SELECT 1 к БД, или дергает downstream HTTP service, или вычисляет что-то тяжёлое. Каждые periodSeconds секунд — нагрузка на app и его dependencies. При большом числе replicas — самый натуральный self-DDoS.
Probes — это не «healthcheck endpoints для дашборда». Это горячая обратная связь kubelet-у. Каждое решение здесь — стоит компромисса (рестарт, исключение из Endpoints). Не дергайте downstream-ы из probes без острой необходимости.