Health checks: liveness и readiness пробы

Рейтинг: 61% · 6 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Health checks: liveness и readiness пробы

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы (вы здесь)
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Представь дежурного по ночам. У тебя сервис на 30 подах, и один из них завис: процесс жив, порт открыт, но внутри дедлок - запросы висят, отвечает таймаутами. Без проб Kubernetes об этом не узнает: контейнер не упал, exit code не было, значит для планировщика все в порядке. Трафик продолжает литься в дохлый под, пользователи ловят пятисотки, а ты в три ночи руками делаешь kubectl delete pod. Health checks существуют ровно для того, чтобы эту работу делал кластер, а не ты.

В этом уроке разберем по косточкам три типа проб - liveness, readiness и startup. Это базовый kubernetes health check, и при этом тема, где ломают себе прод чаще всего: достаточно поставить агрессивный kubernetes liveness, и кластер начнет крушить совершенно здоровые поды под нагрузкой. Цель урока - чтобы ты понимал не только синтаксис, но и механику: кто проверяет, когда, что происходит при провале, и как пробы связаны с rolling update и graceful shutdown.

Кто и как проверяет: механика kubelet

Первое, что надо выбить из головы: пробы выполняет не control plane, не api-server, а kubelet - агент на той самой ноде, где живет под. Это важно. Kubelet локально, без обращения по сети к мастерам, дергает твой контейнер по расписанию и принимает решение. Поэтому проба - это не мониторинг снаружи, а внутренний healthcheck с точки зрения ноды.

Каждая проба - это периодический вызов. Kubelet раз в periodSeconds выполняет проверку, ждет ответа до timeoutSeconds, и считает результат успехом или провалом. Дальше включается счетчик: чтобы признать пробу проваленной, нужно failureThreshold подряд неудач; чтобы признать восстановившейся - successThreshold подряд успехов (для liveness и startup successThreshold всегда 1, его менять нельзя).

Есть четыре способа, как именно kubelet щупает контейнер:
  • httpGet - GET-запрос на путь и порт. Успех - это код ответа от 200 до 399 включительно. Все, что 400+, или таймаут, или отказ соединения - провал. Самый частый и правильный выбор для веб-сервисов.
  • tcpSocket - просто пытается открыть TCP-соединение на порт. Открылось - успех. Подходит для всего, что не HTTP: базы, брокеры, gRPC без health-сервиса.
  • exec - запускает команду внутри контейнера. Exit code 0 - успех, любой другой - провал. Гибко, но дорого: каждый вызов это форк процесса внутри контейнера, на высокой частоте exec-пробы заметно грузят ноду.
  • grpc - нативная gRPC-проба (стабильна с давних версий, по умолчанию включена с 1.24). Дергает стандартный gRPC Health Checking Protocol на указанном порту. До нее приходилось тащить в образ бинарь grpc_health_probe и звать его через exec - теперь это родное поле.
Изображение

Liveness: когда нужен перезапуск, а когда это вредительство

Liveness-проба отвечает на один вопрос: жив ли контейнер или его надо перезапустить. Если liveness проваливается failureThreshold раз подряд, kubelet убивает контейнер (не под целиком, а именно этот контейнер) и рестартит его согласно restartPolicy. Счетчик рестартов ты видишь в колонке RESTARTS у kubectl get pods.

Смысл liveness - вытащить процесс из состояния, из которого он сам не выберется: дедлок, утечка дескрипторов, зависший event loop. Перезапуск как удар по системнику, когда комп завис.

И вот тут - главная граната всего урока. Агрессивный liveness рестартит здоровый под. Сценарий из жизни: ты поставил liveness на тяжелый эндпоинт /health, который внутри ходит в базу и в Redis. Прилетела нагрузка, база подтормаживает, /health начинает отвечать за 3 секунды, а у тебя timeoutSeconds: 1. Проба падает, под рестартится, нагрузка перераспределяется на оставшиеся поды, у них /health тоже начинает тупить, они тоже рестартятся - каскад. Ты своими руками устроил отказ из-за health check там, где сервис бы просто продышался.

Правила безопасного liveness:
  • Liveness-эндпоинт должен быть дешевым и локальным. Он проверяет "жив ли мой процесс", а не "здорова ли вся система". Никаких походов в БД, кэш, соседние сервисы. В идеале это легкий /healthz, который просто отвечает 200, если event loop крутится.
  • Ставь щадящие пороги. failureThreshold 3 и periodSeconds 10 дают 30 секунд на провал перед рестартом - этого хватает, чтобы не реагировать на случайный всплеск.
  • Если контейнеру в принципе нечем зависнуть так, чтобы помог только рестарт - liveness можно вообще не ставить. Отсутствие liveness - это нормально, а не пробел.
Readiness: liveness и readiness - это разные вопросы

Связка liveness readiness - то, что путают чаще всего, поэтому разведем жестко. Liveness - "перезапустить?", readiness - "пускать ли трафик?". Readiness probe не рестартит ничего. Если она проваливается, kubelet помечает под как not ready, и эндпоинт-контроллер убирает его из EndpointSlice соответствующего Service. Под живой, крутится, но Service перестает слать на него запросы. Как только readiness снова зеленая - под возвращается в балансировку.

Зачем это нужно:
  • Прогрев на старте. Приложение поднялось, но еще грузит конфиги, прогревает JIT, наполняет кэш, открывает пул коннектов к БД. Пока не готово - readiness красная, трафик не идет, пользователь не ловит ошибки "из-за того что под только проснулся".
  • Временная недоступность зависимости. Отвалилась база - readiness может уйти в красное, под выпадет из балансировки, но не рестартнется. Восстановилась база - под сам вернулся. Тут readiness как раз может и должна щупать зависимости, в отличие от liveness.
  • Защита при перегрузке. Под на пределе по нагрузке - временно говорит "я не ready", балансировщик его щадит.
Отсюда антипаттерн номер два: readiness без liveness на сервисе, который умеет намертво виснуть. Если процесс ушел в дедлок, readiness честно покажет not ready, под вынут из трафика - и он будет вечно висеть мертвым грузом, никто его не перезапустит. Нужна пара: readiness уберет из балансировки на время, liveness добьет, если совсем завис.

И зеркальный антипаттерн: liveness, проверяющий внешнюю зависимость. Отвалился Redis - и если на нем висит liveness, у тебя разом рестартятся все поды сервиса. Внешние зависимости - это про readiness, никогда про liveness.

Startup: защита медленного старта от liveness

Третья проба родилась из конкретной боли. Допустим, у тебя легаси на Java или монолит, который стартует 90 секунд. Чтобы liveness не убил его при нормальном старте, наивно увеличивают initialDelaySeconds до 120. Но это палка о двух концах: ты выставил огромную задержку для нормального liveness уже работающего пода, и если он зависнет через час, kubelet будет полминуты-минуту тупить, прежде чем заметить.

Startup-проба решает это чисто. Пока она не прошла хотя бы раз, liveness и readiness вообще не запускаются - они заморожены. Это дает медленному приложению столько времени, сколько нужно, не размазывая задержки на боевые пробы.

Окно стартапа считается просто: failureThreshold умножить на periodSeconds. Поставил failureThreshold: 30 и periodSeconds: 10 - получил до 300 секунд на запуск. Не уложился - kubelet убивает контейнер как не стартовавший. А как только startup отдала первый успех - она больше не вызывается, и в дело вступают liveness с readiness уже с боевыми, агрессивными порогами.

Параметры: разбираем поля по одному

Код: Выделить всё

apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: orders-api
  template:
    metadata:
      labels:
        app: orders-api
    spec:
      terminationGracePeriodSeconds: 30
      containers:
        - name: app
          image: registry.example.ru/orders-api:1.8.2
          ports:
            - containerPort: 8080
          startupProbe:
            httpGet:
              path: /healthz
              port: 8080
            failureThreshold: 30
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2
            successThreshold: 1
Разбор полей:
  • initialDelaySeconds - сколько ждать после старта контейнера до первой проверки. Если есть startupProbe, для liveness/readiness это поле почти не нужно - стартап и так их прикроет.
  • periodSeconds - интервал между проверками. По умолчанию 10.
  • timeoutSeconds - сколько ждать ответа. По умолчанию 1 - и это коварно мало для реальных сервисов под нагрузкой. Часто корень "почему под рестартится без причины" именно тут.
  • failureThreshold - сколько провалов подряд до вердикта. Для liveness 3 - разумный минимум.
  • successThreshold - сколько успехов подряд для восстановления. Для liveness и startup только 1, менять можно лишь у readiness.
Обрати внимание: у разных эндпоинтов разные пути. /healthz - дешевый "я жив" для liveness и startup. /ready - "я готов работать" для readiness, может проверять пул коннектов к БД. Разделять их - хорошая практика, а не педантизм.

Связь с rolling update и graceful shutdown

Пробы - не изолированная игрушка, они вшиты в жизненный цикл деплоя.

Rolling update. При обновлении Deployment поднимает новый под и ждет, пока его readiness станет зеленой, прежде чем гасить старый (это регулируется maxUnavailable и maxSurge). Если readiness настроена честно - пользователь не заметит выкатку: новый под начнет получать трафик строго после прогрева. Если readiness нет или она фиктивная (просто возвращает 200 сразу) - Deployment сочтет под готовым мгновенно, пошлет на него трафик до прогрева, и ты получишь всплеск ошибок на каждом релизе. Readiness - это то, что делает rolling update бесшовным.

Graceful shutdown. Когда под удаляют, происходит вот что, и порядок тут важен. Kubelet почти одновременно: убирает под из EndpointSlice (новый трафик перестает приходить) и шлет процессу SIGTERM. Дальше дается terminationGracePeriodSeconds (по умолчанию 30) на корректное завершение - дослать ответы текущим клиентам, закрыть коннекты. Не уложился - прилетает SIGKILL.

Тонкость, на которой спотыкаются: удаление из EndpointSlice и доставка SIGTERM - асинхронны, между ними есть гонка. Поэтому грамотные приложения на SIGTERM не падают сразу, а сначала переводят readiness в красное и держат сервер открытым еще пару секунд - чтобы добрать запросы, которые балансировщик успел отправить до того, как узнал об удалении пода. Связка readiness + preStop-хук со sleep тут спасает от пятисоток на каждом масштабировании вниз.

Кстати, начиная с версии 1.25 у каждой пробы можно задать свой terminationGracePeriodSeconds - например, дать liveness-провалу убивать контейнер быстро, не дожидаясь общего льготного периода пода. Если задан и на поде, и на пробе - kubelet берет значение с пробы.

Практика: смотрим, как пробы валят и поднимают под

Накатим деплой и посмотрим события. Самое полезное при отладке проб - kubectl describe, секция Events:

Код: Выделить всё

$ kubectl describe pod orders-api-7d9f8c6b54-xk2lp
...
Events:
  Type     Reason     Age                From     Message
  ----     ------     ----               ----     -------
  Normal   Scheduled  62s                default-scheduler  Successfully assigned default/orders-api-7d9f8c6b54-xk2lp to node-2
  Normal   Pulled     60s                kubelet  Container image "orders-api:1.8.2" already present on machine
  Normal   Created    60s                kubelet  Created container app
  Normal   Started    60s                kubelet  Started container app
  Warning  Unhealthy  8s (x3 over 28s)   kubelet  Liveness probe failed: Get "http://10.244.1.7:8080/healthz": context deadline exceeded
  Normal   Killing    8s                 kubelet  Container app failed liveness probe, will be restarted
Читаем: Unhealthy с x3 over 28s - проба упала три раза за 28 секунд, это и есть наш failureThreshold: 3. context deadline exceeded - сработал timeoutSeconds, эндпоинт не ответил вовремя. Killing - kubelet решил рестартить. Если видишь такое на здоровом по логам поде - почти наверняка timeout слишком мал или эндпоинт слишком тяжелый.

Текущую готовность видно сразу в get pods - колонка READY показывает "готовых контейнеров из всех":

Код: Выделить всё

$ kubectl get pods
NAME                          READY   STATUS    RESTARTS      AGE
orders-api-7d9f8c6b54-xk2lp   0/1     Running   2 (12s ago)   3m
orders-api-7d9f8c6b54-jh4mn   1/1     Running   0             3m
Первый под: 0/1 - контейнер запущен (Running), но readiness красная, в трафик не идет; RESTARTS 2 - liveness уже дважды его перезапускала. Это классическая картина "что-то не так с пробами". Второй под здоров - 1/1, ноль рестартов.

Проверить, реально ли под в балансировке, можно через EndpointSlice - туда попадают только ready-поды:

Код: Выделить всё

$ kubectl get endpointslices -l kubernetes.io/service-name=orders-api
NAME               ADDRESSTYPE   PORTS   ENDPOINTS      AGE
orders-api-abcde   IPv4          8080    10.244.2.4     5m
В ENDPOINTS только один адрес, хотя подов два - значит второй (тот, что 0/1) выкинут из трафика. Ровно то, чего мы и ждем от readiness.

Грабли и антипаттерны - сводка
  • timeoutSeconds: 1 по умолчанию. Самая частая причина ложных рестартов. Под нагрузкой даже легкий эндпоинт может не уложиться. Ставь 2-3 осознанно.
  • Тяжелый liveness-эндпоинт. Liveness, который ходит в БД, превращает тормоза зависимости в каскад рестартов. Liveness - только локальная проверка процесса.
  • Liveness на внешнюю зависимость. Отвалился Redis - рестартнулся весь сервис. Зависимости - в readiness.
  • Readiness без liveness на виснущем сервисе. Завис - выпал из трафика навсегда, никто не перезапустит. Нужна пара.
  • Раздувание initialDelaySeconds вместо startupProbe. Для медленного старта используй startup, а не калечь задержку боевого liveness.
  • Фиктивная readiness (мгновенный 200). Ломает бесшовность rolling update - трафик идет до прогрева.
Мини-лаба: воспроизведи руками
  • Возьми любой образ с HTTP, например свой сервис или nginx. Создай Deployment с liveness httpGet на путь, которого нет (например /nope), с timeoutSeconds: 1 и failureThreshold: 3.
  • Накати: kubectl apply -f deploy.yaml. Подожди и смотри kubectl get pods -w - увидишь, как RESTARTS растет, под в CrashLoopBackOff.
  • Сделай kubectl describe pod на этот под, найди в Events строки Unhealthy и Killing. Прочитай Message - это твой диагноз.
  • Поправь путь liveness на реальный (/ для nginx), добавь readinessProbe и startupProbe. Передеплой. Убедись, что под стал 1/1 и RESTARTS замер на нуле.
  • Проверь kubectl get endpointslices - адрес здорового пода должен появиться в ENDPOINTS.
  • Бонус: временно сломай readiness (несуществующий путь) при рабочем liveness - под станет 0/1 без рестартов и исчезнет из EndpointSlice. Это наглядно показывает разницу между двумя пробами.
Контрольные вопросы
  • Что произойдет с подом, если провалится liveness-проба, и что - если провалится readiness? В чем принципиальная разница последствий?
  • Почему опасно вешать liveness на эндпоинт, который внутри ходит в базу данных? Опиши каскад.
  • Зачем нужна startup-проба, если можно просто увеличить initialDelaySeconds у liveness? Что именно она дает медленному приложению?
  • Как readiness-проба связана с бесшовностью rolling update и с graceful shutdown пода?
Итог

Три пробы - три разных вопроса к контейнеру. Liveness спрашивает "тебя перезапустить?" и должна быть дешевой и локальной. Readiness спрашивает "пускать ли трафик?", не рестартит, управляет попаданием в EndpointSlice и делает выкатки бесшовными. Startup прикрывает медленный старт, замораживая две первые пробы, пока приложение не поднялось. Главные грабли - агрессивный liveness и тяжелые эндпоинты: они превращают временный тормоз в самоподдерживающийся отказ. Настроишь пробы с холодной головой - и ночные дежурства станут заметно скучнее, чего тебе и желаю.
👍2 ❤️3 🔥3 😄 🤔1
✔ Лучший ответ сформирован автоматически — patton1941
anton_k8s писал(а):Никаких проверок базы или внешних API там быть не должно а как тогда поймать деградацию? у нас liveness как раз пингует постгрес, живем так года полтора и вроде норм. получается правильный путь это алерты из прометеуса, а кубер пусть смотрит только за процессом? страшновато убирать, вдруг зависшие коннекты к базе перестанем ловить
Перейти к ответу →
Аватара пользователя
patton1941
Сообщения: 1
Зарегистрирован: 24 май 2026, 01:12

Re: Health checks: liveness и readiness пробы

Сообщение patton1941 »

✔ Лучший ответ — сформирован автоматически
anton_k8s писал(а):Никаких проверок базы или внешних API там быть не должно
а как тогда поймать деградацию? у нас liveness как раз пингует постгрес, живем так года полтора и вроде норм. получается правильный путь это алерты из прометеуса, а кубер пусть смотрит только за процессом? страшновато убирать, вдруг зависшие коннекты к базе перестанем ловить
👍2 ❤️1 🔥 😄 🤔
Аватара пользователя
chimps
Сообщения: 1
Зарегистрирован: 14 май 2026, 05:31

Re: Health checks: liveness и readiness пробы

Сообщение chimps »

для спринговиков подсказка: в boot начиная с 2.3 есть готовые /actuator/health/liveness и /actuator/health/readiness, в кубере они включаются сами, локально через management.endpoint.health.probes.enabled=true. месяц назад выкинул самописный healthz в пользу этих, полет нормальный
👍 ❤️1 🔥 😄 🤔
Аватара пользователя
Martti
Сообщения: 1
Зарегистрирован: 21 май 2026, 13:56

Re: Health checks: liveness и readiness пробы

Сообщение Martti »

а если у меня gunicorn с парой sync воркеров? во время долгого запроса он на healthz не ответит, и кубер его прибьет получается. повышать failureThreshold или это лечится только переходом на gthread/async?
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
machismo
Сообщения: 2
Зарегистрирован: 22 май 2026, 19:35

Re: Health checks: liveness и readiness пробы

Сообщение machismo »

спасибо за трюк с heartbeat файлом. селери воркеры у нас висли молча по ночам, до утра никто не замечал. прикрутил такую пробу, за две недели два честных авторестарта вместо утреннего разбора полетов руками
👍 ❤️1 🔥2 😄 🤔1
Ответить
← Предыдущая глава
Namespaces, requests и limits
Следующая глава →
Отладка: почему под не стартует

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость