Зачем нужны проверки здоровья подов в Kubernetes
Kubernetes запускает контейнер и дальше не знает, что происходит внутри процесса. Приложение может уйти в deadlock, зациклиться на ретраях к недоступной базе или перестать отвечать на запросы, а под останется в статусе Running: для kubelet процесс жив. Трафик через Service продолжит идти на этот под, и пользователи получат таймауты или 502.
Пробы здоровья закрывают этот разрыв. Проба это периодическая проверка, которую выполняет kubelet на той же ноде, где работает под. Результат проверки задаёт одно из трёх действий: перезапустить контейнер, исключить под из балансировки трафика или ничего не делать и подождать. Механизм проверки выбирается из трёх вариантов: HTTP-запрос (httpGet), установка TCP-соединения (tcpSocket) или запуск команды внутри контейнера (exec).
| Проба | Что проверяет | Действие при провале | Типичный сценарий |
|---|---|---|---|
| livenessProbe | Процесс жив и отвечает | kubelet перезапускает контейнер | Deadlock, зависание, утечка памяти |
| readinessProbe | Готовность принимать трафик | Под исключается из Endpoints сервиса | Прогрев кэша, соединения с БД, миграции |
| startupProbe | Инициализация завершена | Перезапуск контейнера; liveness и readiness отключены до успеха | JVM, .NET, тяжёлые миграции |
Пробы описываются на уровне контейнера, а не пода: если контейнеров несколько, у каждого свой набор проверок. Под считается Ready только тогда, когда все его контейнеры проходят readiness. Место проб в общем контуре восстановления описали в материале о том, как оркестратор обеспечивает отказоустойчивость приложений.
Цена ошибки в конфигурации высокая. Один и тот же failureThreshold, выставленный слишком низко, превращает просадку ответа на пару секунд в шторм перезапусков. Проверка внешней зависимости внутри liveness превращает падение базы в каскадный отказ всего деплоя.
Liveness probe: перезапуск зависшего контейнера
livenessProbe отвечает на вопрос, нужно ли перезапускать контейнер. kubelet считает проверку провалившейся после failureThreshold неудач подряд и убивает контейнер. Дальше работает restartPolicy пода (по умолчанию Always): контейнер стартует заново, а в событиях появляется запись Back-off restarting failed container с растущей задержкой между попытками.
Ставьте liveness для сценариев, где процесс жив, а приложение мертво: взаимная блокировка потоков, бесконечный цикл, зависшая очередь задач. Если сервис сам завершает процесс при фатальной ошибке, restartPolicy справится без liveness, а лишняя проверка добавит риск ложных перезапусков.
Провал liveness стоит дорого: контейнер убивают, незавершённые запросы теряются, приложение прогревает кэш заново. Поэтому liveness проверяет только сам процесс и не обращается к БД, кэшу или внешним API.
Пример liveness probe через httpGet
Основной вариант для веб-приложений и API. Эндпоинт отвечает быстро и не требует аутентификации.
apiVersion: v1
kind: Pod
metadata:
name: web-api
spec:
containers:
- name: app
image: registry.example.com/web-api:1.4.2
ports:
- name: http
containerPort: 8080
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
successThreshold: 1
Успехом считается ответ с кодом в диапазоне 200-399, остальные коды kubelet трактует как провал. Порт указывается числом или именем из ports контейнера, в примере это имя http. По умолчанию httpGet отправляет заголовок Host с IP пода; если приложение маршрутизирует по домену, добавьте httpHeaders с нужным значением Host.
initialDelaySeconds 15 даёт приложению время поднять слушателя, periodSeconds 10 задаёт интервал, failureThreshold 3 означает перезапуск примерно после 30 секунд непрерывных сбоев. Эндпоинт /healthz держите максимально простым: запрос к БД внутри него превращает падение базы в перезапуск всего деплоя. Готовые комбинации проверок для разных типов нагрузки собраны в руководстве по настройке Readiness и Liveness Probes в Kubernetes.
Пример liveness probe через exec
Подходит для приложений без HTTP-сервера: воркеров очередей, демонов, legacy-сервисов. Команда выполняется внутри контейнера, успех это код возврата 0.
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
successThreshold: 1
Логика шаблона: приложение само поддерживает файл-маркер и удаляет его при зависании рабочего потока. Команда должна быть короткой и завершаться быстро. Каждый запуск exec-пробы тратит CPU и память контейнера, поэтому тяжёлые скрипты с сетевыми вызовами внутрь проверки не ставим.
Пример liveness probe через tcpSocket
tcpSocket устанавливает TCP-соединение с портом. Проверка подтверждает, что сокет открыт, но о логике приложения не говорит: процесс может принимать соединения и не обрабатывать запросы.
livenessProbe:
tcpSocket:
port: 3306
initialDelaySeconds: 15
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
Для баз данных и брокеров это рабочий вариант: порт 3306 у MySQL или 5672 у RabbitMQ отвечает, даже когда сервис перегружен. Если у приложения есть HTTP-эндпоинт, выбирайте httpGet, он проверяет больше.
Readiness probe: управление трафиком без перезапуска
readinessProbe определяет, попадёт ли под в список бэкендов сервиса. При провале kubelet помечает под как NotReady и убирает его из Endpoints: новые запросы на этот под не идут, контейнер продолжает работать, состояние и открытые соединения сохраняются. Как только проверка снова проходит, под возвращается в балансировку.
Сценарии для readiness: прогрев кэша после старта, установка соединений с БД и брокером, ожидание миграций, временная перегрузка пула потоков. Это ещё и механизм плавного rolling update: Deployment поднимает новый под, ждёт Ready и только затем гасит старый. Если readiness не проходит, обновление зависает и упирается в progressDeadlineSeconds (по умолчанию 600 секунд). Связку readiness с репликами и стратегиями обновления разбираем в статье про отказоустойчивость Kubernetes: probes, реплики и стратегии обновления.
Пример readiness probe через httpGet
В отличие от liveness, здесь проверка зависимостей уместна: соединение с БД, доступность Redis, применение миграций.
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
successThreshold: 1
timeoutSeconds 2 при periodSeconds 5 оставляет запас на медленный ответ базы. Если эндпоинт опрашивает внешние сервисы, кэшируйте результат на 1-2 секунды, иначе каждая проверка порождает лишний запрос к зависимости.
Пример readiness probe через exec
readinessProbe:
exec:
command:
- test
- -f
- /tmp/ready
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
Шаблон test -f /tmp/ready подходит воркерам и приложениям, которые сами знают момент готовности: процесс создаёт файл после инициализации и удаляет его при потере соединения с брокером.
Не делайте readiness строже, чем требует трафик. Если при сбое БД все поды уходят из балансировки, сервис отдаёт 503 при живых контейнерах: добавьте failureThreshold 3 и разделите проверки на критичные и желательные.
Startup probe: защита медленно стартующих приложений
startupProbe запускается первой. Пока она не пройдёт успешно, kubelet не выполняет liveness и readiness, а контейнер не получает трафик. Если проверка не уложилась в отведённый бюджет, контейнер перезапускается. После первого успеха startup probe передаёт управление liveness probe, которая дальше отвечает за обнаружение зависаний.
Кандидаты на startup probe: Java-сервисы на Spring Boot, приложения .NET, сервисы, которые на старте прогоняют миграции схемы или строят индекс. Без неё приходится завышать initialDelaySeconds у liveness, и тогда зависание в рантайме обнаруживается с той же задержкой. Со startup probe liveness настраивается агрессивно: она включается после готовности приложения.
Пример startup probe через httpGet
startupProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 30
Бюджет на старт считается как initialDelaySeconds + periodSeconds × failureThreshold: 10 + 10 × 30 = 310 секунд. Значение покрывает инициализацию JVM с прогревом и короткие миграции. После успеха проверка /healthz переходит к liveness probe, и та реагирует на зависания с интервалом 10 секунд.
Пример startup probe через exec
startupProbe:
exec:
command:
- test
- -f
- /tmp/started
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 60
Бюджет здесь 5 + 5 × 60 = 305 секунд. Приложение создаёт /tmp/started после завершения инициализации. Для воркеров без HTTP это основной способ отличить долгий старт от зависания.
Дайте запас 30-50% к ожидаемому времени старта, но не больше. failureThreshold 300 при periodSeconds 10 даёт почти час ожидания, за который сломанная миграция или опечатка в конфиге останутся незамеченными.
Как выбрать параметры проб: тайминги и пороги
Пять параметров управляют поведением любой пробы. Значения по умолчанию подходят для простых случаев: periodSeconds 10 и timeoutSeconds 1 оставляют мало времени на ответ под нагрузкой.
| Параметр | По умолчанию | Смысл | Практическое значение |
|---|---|---|---|
| initialDelaySeconds | 0 | Пауза перед первой проверкой | 0 при наличии startup probe; 5-15 для liveness без неё |
| periodSeconds | 10 | Интервал между проверками | 10 для liveness, 5 для readiness |
| timeoutSeconds | 1 | Сколько ждать ответ | 2-3 для HTTP, всегда меньше periodSeconds |
| successThreshold | 1 | Успехов подряд для перехода в Ready | 1 для liveness и startup, 1-2 для readiness |
| failureThreshold | 3 | Неудач подряд до действия | 3 для liveness и readiness, расчёт по бюджету для startup |
Бюджет старта: initialDelaySeconds + periodSeconds × failureThreshold. Приложению, которое поднимается две минуты, подойдут periodSeconds 10, failureThreshold 18 и initialDelaySeconds 10, итого 190 секунд. Для liveness с periodSeconds 10 и failureThreshold 3 контейнер перезапускается после 30 секунд непрерывных сбоев.
timeoutSeconds держите меньше periodSeconds. С timeoutSeconds 15 при periodSeconds 10 проверки накладываются друг на друга, и kubelet не успевает обрабатывать результаты. successThreshold больше 1 у liveness и startup API отклонит: для этих проб допустимо только значение 1.
Особенности для веб-приложений, баз данных и очередей
- Веб-приложение и API. httpGet на /healthz для liveness и /ready для readiness, отдельный лёгкий обработчик без авторизации на том же порту, что и рабочий трафик.
- База данных. tcpSocket на порт 3306 или 5432 для liveness, либо exec с admin-командой ping; readiness проверяет, что инстанс вышел из режима восстановления и принимает запросы.
- Очередь и воркеры. HTTP-эндпоинта нет, поэтому liveness это exec-скрипт с проверкой heartbeat-файла, а подключение к брокеру уходит в readiness.
- Медленный старт (JVM, .NET, тяжёлые миграции). startup probe с запасом по бюджету, liveness без initialDelaySeconds и периодом 10 секунд.
Типичные ошибки конфигурации проб и как их избежать
Инциденты с пробами связаны не столько с механизмом проверки, сколько с параметрами и выбором того, что проверяется. Разберём шесть конфигураций, которые ломают продакшен.
Ошибка: liveness probe зависит от базы данных
livenessProbe:
httpGet:
path: /healthz?db=1
port: 8080
periodSeconds: 5
failureThreshold: 2
База недоступна 30 секунд, эндпоинт отдаёт 500, и kubelet перезапускает все поды деплоя одновременно. После рестарта приложение снова открывает соединения к перегруженной базе и добивает её. Исправление: liveness проверяет только процесс по /healthz, а соединение с БД уходит в readiness probe с кэшированием результата.
Ошибка: initialDelaySeconds меньше реального времени старта
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
Java-приложение стартует 90 секунд, контейнер убивают примерно на 60-й секунде, до того как поднимется HTTP-сервер. Исправление: startup probe с periodSeconds 10 и failureThreshold 12, а liveness включается после успешного старта.
Ошибка: слишком строгая readiness probe
Проверка с failureThreshold 1 и timeoutSeconds 1, которая опрашивает три зависимости, выводит под из балансировки при первом замедлении базы. Все реплики уходят из Endpoints, и сервис отвечает 503 при живых контейнерах. Исправление: timeoutSeconds 3, failureThreshold 3, кэш результата на несколько секунд.
Ошибка: exec-проба, которая может зависнуть
livenessProbe:
exec:
command:
- sh
- -c
- curl -s localhost:8080/healthz | grep ok
timeoutSeconds: 1
Такой конфиг ломается по двум причинам: в образе может не быть curl или grep, а пайплайн способен ждать сетевого ответа дольше timeoutSeconds. Проба падает постоянно, контейнер уходит в перезапуск. Исправление: httpGet для HTTP-сервиса или прямой вызов одного бинарника в exec-форме без shell.
Ошибка: неверный порт пробы
Проба обращается к 8080, а приложение слушает 8000. kubelet получает connection refused и перезапускает здоровый контейнер. Исправление: сверьте containerPort с портом из логов старта, а при использовании имени порта убедитесь, что оно объявлено в ports контейнера.
Ошибка: бесконечный бюджет на старт
startup probe с periodSeconds 10 и failureThreshold 300 ждёт 50 минут. За это время сломанная миграция или опечатка в конфиге остаются незамеченными, а деплой висит в ожидании. Исправление: запас 30-50% от ожидаемого времени старта, остальное ловят метрики и алерты.
Как проверить и отладить пробы в кластере
Диагностика начинается с событий пода: там указано, какая проба упала и почему.
kubectl describe pod web-api-7d9c8b6f5c-2xk4p kubectl get events --field-selector involvedObject.name=web-api-7d9c8b6f5c-2xk4p kubectl logs web-api-7d9c8b6f5c-2xk4p --previous kubectl get endpoints web-api
В kubectl describe pod смотрите блок Events и колонку Restart Count. Типичные сообщения:
- Liveness probe failed: HTTP probe failed with statuscode: 500 - эндпоинт отвечает, но проверка внутри него падает: ошибка зависимости или логики.
- Readiness probe failed: dial tcp 10.244.1.7:8080: connect: connection refused - порт закрыт, приложение не слушает или указан неверный порт.
- Liveness probe failed: context deadline exceeded - ответ не уложился в timeoutSeconds, проверьте таймаут и нагрузку.
- Back-off restarting failed container - kubelet уже убил контейнер и увеличивает задержку перед следующим запуском.
Логи предыдущего контейнера (kubectl logs ... --previous) показывают, что происходило перед перезапуском: панику, OOM или обрыв соединения с БД.
Проверить httpGet вручную можно изнутри пода теми же параметрами, что в манифесте: kubectl exec -it pod -- curl -v localhost:8080/healthz. Для exec-проб выполните ту же команду в контейнере и сверьте код возврата. Для readiness полезно посмотреть, попал ли под в Endpoints сервиса: kubectl get endpoints web-api.
При отладке можно временно поднять failureThreshold или initialDelaySeconds, чтобы контейнер выжил и отдал логи. После правки верните рассчитанные значения. События проб стоит вывести в мониторинг: как собирать метрики и алерты по health-проверкам, разбираем в материале про health-check и раннюю диагностику отказов сервисов.
Чек-лист перед деплоем в продакшен
- liveness не обращается к внешним сервисам. Проверка касается только процесса и его способности отвечать; БД, кэш и брокер в liveness дают каскадные перезапуски.
- readiness проверяет готовность принимать трафик. Эндпоинт учитывает зависимости, но с разумным таймаутом и кэшированием результата.
- Для старта дольше 30 секунд есть startup probe. Иначе долгая инициализация лечится завышенным initialDelaySeconds, который скрывает зависания в рантайме.
- timeoutSeconds меньше periodSeconds. Проверки не накладываются друг на друга.
- Бюджет старта посчитан. initialDelaySeconds + periodSeconds × failureThreshold покрывает худшее время инициализации с запасом 30-50%.
- successThreshold для liveness и startup равен 1. Другие значения API не примет.
- Пробы не создают лишней нагрузки. Лёгкий обработчик, короткие команды, никаких тяжёлых SQL в каждой проверке.
- Конфигурация проверена в staging. Остановите зависимость и посмотрите, как ведут себя liveness, readiness и общее время восстановления.
- События проб попадают в мониторинг. Алерт на частые перезапуски и на поды в NotReady ловит проблему до жалоб пользователей.
- Есть связка с graceful shutdown. terminationGracePeriodSeconds и preStop hook дают приложению завершить запросы, пока readiness уже сняла трафик.
Начните с одного сервиса: вынесите проверку зависимостей из liveness в readiness, добавьте startup probe, если старт занимает больше 30 секунд, и прогоните конфигурацию по чек-листу. Шаблоны из этой статьи копируются в манифест как есть, меняются только порт и путь эндпоинта. Сценарии с репликами, PodDisruptionBudget и обновлением без простоя собраны в руководстве по отказоустойчивому кластеру Kubernetes.
Источники
- Configure Liveness, Readiness and Startup Probes — Kubernetes
- Liveness, Readiness, and Startup Probes — Kubernetes
- Deployments — Kubernetes
- Update a Deployment Without Downtime — Kubernetes
- A Guide to Understanding Kubernetes Liveness Probes — Fairwinds
- Invalid successThreshold value in livenessProbe — kubeval issue
- Понимаем пробы Kubernetes: типы, настройка и лучшие практики — Хабр
- Kubernetes Health Checks — DEV Community
- Kubernetes Health Probes Done Right — DEV Community