Задача
Сервис ходит в базу данных. Настроен liveness probe:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3Эндпоинт /health проверяет соединение с БД: если запрос к базе не проходит, возвращается 503.
База начинает тормозить под нагрузкой. Через минуту все реплики сервиса уходят в цикл перезапусков, и ситуация становится хуже, чем была.
Вопрос: какая ошибка допущена в конфигурации?
Что кажется очевидным
«Порог failureThreshold слишком низкий» или «надо увеличить таймауты». Такие правки немного оттягивают проблему, но не устраняют её.
Как это работает на самом деле
Ошибка концептуальная: liveness probe проверяет не то, что нужно.
Три probe отвечают на три разных вопроса:
| Probe | Вопрос | Провал → что происходит |
|---|---|---|
| liveness | «Процесс жив или завис?» | Контейнер перезапускается |
| readiness | «Готов ли принимать трафик?» | Под убирают из endpoints (трафик прекращается, перезапуска нет) |
| startup | «Приложение закончило запуск?» | Контейнер перезапускается; пока идёт — остальные probe не выполняются |
Ключевое различие — в цене ошибки. Провал readiness обратим и дёшев: перестал идти трафик, приложение восстановилось — трафик вернулся. Провал liveness означает убийство процесса.
Почему возникла лавина
Разберём цепочку:
- База тормозит →
/healthвозвращает503на всех репликах одновременно. - Liveness probe трижды подряд получает ошибку → kubelet перезапускает контейнеры.
- Перезапущенные приложения открывают новые соединения к уже перегруженной базе, заново прогревают пулы и кэши.
- Нагрузка на базу растёт →
/healthпродолжает падать → новый круг перезапусков.
Получился контур с положительной обратной связью: механизм «самоисцеления» усиливает аварию. Причём перезапуск здесь не может помочь в принципе — проблема не в процессе, а во внешней зависимости.
Более того: пока под перезапускается, он не обслуживает и те запросы, которые не ходят в базу и могли бы работать.
Ответ
Ошибка в том, что liveness probe проверяет внешнюю зависимость. Перезапуск лечит зависший процесс, но не лечит недоступную базу — он лишь добавляет нагрузку и превращает частичную деградацию в полный отказ.
Проверка зависимостей — это работа readiness probe.
Что с этим делать
Разделите эндпоинты
# Жив ли процесс: максимально просто, без внешних вызовов
livenessProbe:
httpGet:
path: /healthz # отвечает 200, если event loop жив
port: 8080
periodSeconds: 10
failureThreshold: 3
# Готов ли обслуживать: здесь проверяем зависимости
readinessProbe:
httpGet:
path: /ready # проверяет БД, кэш и т.п.
port: 8080
periodSeconds: 5
failureThreshold: 2
# Медленный старт: даём время, не мешая liveness
startupProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5
failureThreshold: 60 # до 5 минут на запускПравила, которые стоит принять как догму:
- Liveness — только самопроверка процесса. Никаких обращений к БД, кэшам, соседним сервисам. Идеальный
/healthzвозвращает200всегда, когда процесс способен обработать HTTP-запрос. - Readiness — зависимости. База недоступна → под уходит из балансировки, но продолжает жить и восстановится сам, когда база вернётся.
- Startup — для медленно стартующих приложений. Он приостанавливает liveness и readiness до успешного завершения, что снимает необходимость раздувать
initialDelaySeconds.
Осторожно с массовым провалом readiness
Есть обратная опасность: если все реплики одновременно провалят readiness, сервис останется без endpoints и перестанет отвечать полностью. Для зависимостей, отказ которых не полностью ломает сервис, разумнее оставлять под в строю и отдавать деградированный ответ, чем выводить из балансировки весь пул.
Настройка параметров
periodSeconds×failureThreshold= время до реакции. Для liveness лучше запас (30+ секунд), чтобы GC-пауза или пик нагрузки не вызвали перезапуск.timeoutSecondsпо умолчанию 1 секунда — часто слишком мало; медленный ответ засчитывается как провал.- Probe не должен быть дорогим: он выполняется постоянно на каждом поде.
Диагностика
# Причина последнего перезапуска
kubectl describe pod my-app | grep -A5 "Last State"
# События probe
kubectl get events --field-selector reason=Unhealthy --sort-by=.lastTimestamp
# Счётчик перезапусков — растущий RESTARTS почти всегда означает
# проблемный liveness
kubectl get pods -wСвязанное: почему Running не значит Ready — задача 1.
Дальше: Задача 8. Держим сервис живым во время обслуживания нод.