Как livenessProbe может убить ваш Pod

от автора

Привет, Хабр!

Если вы хоть раз дебажили под, который вроде работает, но Kubernetes его всё равно убивает — добро пожаловать. Сегодня разложим по косточкам, как livenessProbe может угробить ваш сервис в самый беззащитный момент — и как не дать этому случиться.

Сценарий проблемы

Рассмотрим типичный кейс. Есть микросервис, например на Spring Boot или.NET, который при запуске выполняет стандартный набор операций:

  • применяет миграции схемы базы данных (Flyway, Liquibase);

  • загружает конфигурации из внешнего Vault или Consul;

  • устанавливает соединения с Redis, Kafka, S3;

  • прогревает кеш, создаёт фоновые воркеры;

  • и только после этого начинает слушать HTTP‑порт и отдавать /healthz.

Инициализация может занимать от 30 до 60 секунд — особенно на загруженном CI/CD‑кластере, с прогретыми томами и сетевой задержкой к БД.

Теперь посмотрим на фрагмент манифеста:

livenessProbe:   httpGet:     path: /healthz     port: 8080   initialDelaySeconds: 10   periodSeconds: 5   failureThreshold: 2

Что делает kubelet:

  1. Через 10 секунд после старта контейнера запускается первая проверка на /healthz.

  2. Приложение ещё не завершило старт: порт 8080 может не слушаться, эндпоинт /healthz ещё не отвечает.

  3. Первая проверка возвращает connection refused или timeout — это считается ошибкой.

  4. Через 5 секунд — вторая попытка. Ситуация та же: приложение не готово.

  5. Второй фейл подряд. Достигнут failureThreshold = 2 → kubelet считает контейнер «неживым».

  6. Контейнер убивается и перезапускается с нуля.

  7. Цикл повторяется бесконечно.

Суть проблемы: livenessProbe срабатывает до того, как приложение технически готово пройти проверку, и Kubernetes ошибочно считает, что контейнер «завис». Но пофакту контейнер не завис, он просто ещё не успел инициализироваться.

livenessProbe не ждёт, пока приложение будет готово. Она начинает проверки строго по initialDelaySeconds. И если приложение в этот момент ещё не подняло HTTP‑сервер — оно будет считаться «мертвым».

Что ещё усугубляет ситуацию:

  • Эндпоинт /healthz зависит от внешней инфраструктуры: если при старте сервис не может достучаться до БД или Redis, он будет возвращать 500 или 503, даже если сам процесс жив и находится в стабильной инициализации.

  • Параметры пробы подобраны без учёта real‑world‑таймингов: значения вроде initialDelaySeconds: 10 и failureThreshold: 2 подходят для лёгких сервисов, но не для сложных backend‑приложений с цепочкой инициализаций.

  • В CI/CD pipeline нет возможности протестировать пробу под высокой нагрузкой — на слабых узлах с подогретым volume startup может быть заметно медленнее.

Как правильно настраивать пробы

readinessProbe — контроль трафика, а не здоровья

Это первая проба, которую стоит включить в продакшен. Её задача — сообщить Kubernetes, что контейнер ещё не готов обрабатывать запросы, даже если он запущен.

readinessProbe:   httpGet:     path: /ready     port: 8080   initialDelaySeconds: 15   periodSeconds: 10   failureThreshold: 5

Что происходит при отказе: контейнер не будет включён в список endpoints, и сервис не станет направлять на него трафик. Но контейнер останется жив — он не будет перезапущен. Это нужно, если:

  • приложение стартует, но ещё инициализирует зависимости (DB, кеш, внешние API);

  • сервис может пережить временные ошибки, не прерывая работу (например, перегрузка, пауза GC);

  • вы хотите, чтобы временно недоступные поды просто «выпали из балансировки».

use‑case: сервис стартует за 20–30 секунд, но обрабатывать запросы может начать только после 40-й. readinessProbe не даст нагружать его раньше времени.

startupProbe — защита от преждевременного рестарта

Если приложение имеет тяжёлую инициализацию — обязательно добавляйте startupProbe.

startupProbe:   httpGet:     path: /healthz     port: 8080   periodSeconds: 10   failureThreshold: 30

Эта конфигурация позволяет приложению стартовать в течение до 5 минут (30 попыток по 10 секунд), прежде чем Kubernetes начнёт применять livenessProbe и readinessProbe.

Пока startupProbe не пройдена, Kubernetes не выполняет другие пробы.

livenessProbe — только после стабилизации поведения

livenessProbe — самая опасная из всех трёх. Её задача — обнаруживать зависшие процессы. Но если она настроена неправильно, она же может и убить живой под.

livenessProbe:   httpGet:     path: /healthz     port: 8080   initialDelaySeconds: 60   periodSeconds: 20   failureThreshold: 3

Параметры initialDelaySeconds: 60, periodSeconds: 20 и failureThreshold: 3 означают, что первая проверка начнётся через минуту после запуска, затем будут попытки каждые 20 секунд, и только три подряд неудачи приведут к перезапуску контейнера.

Если под уходит в рестарты, необходимо понять — какая именно проба срабатывает. Делается это с помощью kubectl describe.

kubectl describe pod <pod-name>

В выводе ищите секции вроде:

Liveness probe failed: HTTP probe failed with statuscode: 500

или

Readiness probe failed: Get http://10.0.1.5:8080/ready: connection refused

Также полезна команда:

kubectl get events --sort-by=.lastTimestamp

Она покажет хронологию всех событий по поду: когда сработала проба, когда был рестарт, какие статусы возвращались.

Пример комбинированной конфигурации

startupProbe:   httpGet:     path: /healthz     port: 8080   periodSeconds: 10   failureThreshold: 30  readinessProbe:   httpGet:     path: /ready     port: 8080   initialDelaySeconds: 5   periodSeconds: 10   failureThreshold: 3  livenessProbe:   httpGet:     path: /healthz     port: 8080   initialDelaySeconds: 60   periodSeconds: 20   failureThreshold: 3

Такой шаблон подходит для большинства веб‑приложений, которые:

  • стартуют от 30 до 90 секунд;

  • имеют временные зависимости;

  • должны переживать кратковременные сбои без рестарта.

Ошибки, которых стоит избегать

Неиспользование startupProbe при медленном старте

Если приложение запускается дольше 10–15 секунд — обязательно нужно использовать startupProbe. Без неё Kubernetes может начать выполнять livenessProbe слишком рано, когда сервис ещё инициализируется. В результате — ложные фейлы и перезапуски.

Что происходит:

  • livenessProbe срабатывает до завершения инициализации.

  • Приложение не отвечает вовремя на /healthz.

  • kubelet считает его умершим и перезапускает.

Для этого нужно добавить startupProbe, которая даст приложению больше времени на запуск. Например:

startupProbe:   httpGet:     path: /healthz     port: 8080   failureThreshold: 30   periodSeconds: 10

Это даёт до 5 минут на старт (30 × 10 секунд), без риска быть «убитым» раньше времени.

Зависимость /healthz от внешних сервисов

Бывает, что в /healthz вы проверяете всё: базу данных, Redis, очередь сообщений, и даже внешний API. Кажется логичным: пусть он скажет, что «всё работает».

Проблема в другом: эти сервисы могут временно недоступны, а это не означает, что сам ваш сервис «умер». Например, если Redis отвалился на 5 секунд — зачем перезапускать весь контейнер?

Поэтому в livenessProbe проверяйте, работает ли сам процесс: есть ли доступ к памяти, нет ли deadlock»ов, не «завис» ли event loop. А так же проверку внешних зависимостей лучше делать в readinessProbe — чтобы временно выключать под из балансировки, не убивая его.

Пример безопасного liveness‑эндпоинта:

func Healthz(w http.ResponseWriter, r *http.Request) {     w.WriteHeader(http.StatusOK) }

Возврат 200 OK в /healthz в любом случае

Самая распространённая ошибка — всегда возвращать 200, независимо от состояния сервиса. Да, это удобно на старте, чтобы pod не падал. Но по факту такое поведение лишает смысла все проверки.

Что происходит:

  • /healthz всегда отвечает 200, даже если сервис завис.

  • Kubernetes считает, что всё в порядке.

  • Балансировщик продолжает направлять трафик на нерабочий под.

/healthz должен возвращать 200 только в случае, если сервис действительно работоспособен. При любых сбоях — например, потере соединения с базой, блокировке потоков или внутренней ошибке — следует возвращать 503, чтобы Kubernetes мог адекватно отреагировать.


Если вы работаете с Kubernetes в проде, хорошо знаете его поведение в теории и на практике — возможно, вас заинтересуют открытые уроки, где разберём чуть глубже архитектурные принципы и современные инструменты экосистемы.

Ближайшие темы:

  • 8 апреля — Kubernetes: архитектура и абстракции. Подробнее

  • 17 апреля — Знакомство с Service mesh на примере Istio. Подробнее


ссылка на оригинал статьи https://habr.com/ru/articles/897550/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *