Есть паттерн, который я вижу почти в каждом кластере, куда прихожу: liveness-проба, скопированная из туториала, которая годами тихо делает продакшен хуже. Не «не помогает» — именно делает хуже: превращает локальные замедления в каскадные рестарты и красивые инциденты на ровном месте.
Тезис статьи простой: liveness-проба — это не «проверка здоровья». Это заявление «если этот запрос не ответил — процесс нужно убить». И в большинстве конфигураций, которые я встречал, это заявление ложно.
Что на самом деле обещают пробы
Быстро зафиксируем семантику, потому что путаница начинается уже здесь.
readiness отвечает на вопрос «можно ли слать в под трафик прямо сейчас». Провалилась — под выкидывается из endpoints, трафик уходит на соседей. Процесс жив, никто никого не трогает.
liveness отвечает на вопрос «имеет ли смысл дальше держать этот процесс живым». Провалилась failureThreshold раз подряд — kubelet посылает процессу SIGTERM, потом SIGKILL. Рестарт контейнера, обнуление всего локального состояния: прогретых кешей, JIT-компиляции, пулов соединений.
startup — костыль для медленно стартующих приложений: пока она не прошла, liveness и readiness не проверяются.
Ключевое различие — цена ошибки. Ложно упавшая readiness стоит вам немного трафика на соседние поды. Ложно упавшая liveness стоит вам рестарта — то есть под нагрузкой она стоит вам ещё больше нагрузки на оставшиеся поды.
Анатомия каскада
Теперь механика инцидента, который я наблюдал в разных вариациях не один раз. Исходные условия: HTTP-сервис, liveness и readiness смотрят на один и тот же эндпоинт /health, тот ходит в базу «для честности». Конфиг из туториала:
livenessProbe: httpGet: path: /health port: 8080 periodSeconds: 10 timeoutSeconds: 1 failureThreshold: 3
Дальше по шагам:
База начинает отвечать медленнее — вечерний пик, тяжёлая миграция, сосед по СУБД, неважно. /health, который ходит в базу, начинает укладываться не в 200 мс, а в полторы секунды.
timeoutSeconds: 1 — проба фейлится. Три раза подряд — это всего 30 секунд деградации базы.
kubelet убивает контейнер. Причём убивает здоровый процесс: приложение работало, просто медленнее обычного. Вместе с процессом умирают прогретый пул соединений и кеши.
Под рестартует и на старте делает то, что делают все приложения на старте: открывает соединения к базе, греет кеши. То есть добавляет нагрузку на и без того деградировавшую базу.
Пока он стартует, его долю трафика несут соседи. Их /health тоже ходит в базу. Их пробы тоже начинают фейлиться.
GOTO 3, но уже для нескольких подов одновременно.
Через пять минут у вас половина деплоймента в CrashLoopBackOff, база лежит от штормов реконнектов, а на графиках это выглядит как «внезапно всё упало». Хотя началось всё с полутора секунд латентности, которые сервис спокойно пережил бы, если бы его не начали расстреливать.
Отдельная вишенка: в kubectl describe вы увидите Liveness probe failed: ... context deadline exceeded, и дежурный сделает вывод «под завис». Под не завис. Под убили.
Главная ошибка: liveness с зависимостями
Правило, которое я бы вешал в рамочку: liveness-проба не должна проверять ничего, кроме самого процесса. Ни базу, ни соседний сервис, ни S3, ни очередь.
Почему — из семантики. Рестарт процесса лечит ровно один класс проблем: невосстановимое состояние самого процесса. Дедлок, наглухо застрявший event loop, утёкшая память, сломанный внутренний стейт. Если ваша база лежит, рестарт вашего пода её не поднимет — он только добавит реконнектов. Проверять в liveness зависимость — значит прописать в конфиге «когда база деградирует, начинайте убивать приложение». Сформулированное так, это звучит абсурдно; тем не менее ровно это написано в половине продовых манифестов.
Зависимости — это территория readiness, и то с оговорками: выкидывать под из балансировки из-за мигнувшей базы тоже стоит не всегда (если это делают все поды одновременно, вы получаете нулевой endpoints и 503 вместо деградации).
Честный liveness-эндпоинт — унизительно тупой:
http.HandleFunc("/livez", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
Смысл в том, что сам факт ответа доказывает главное: процесс жив, слушает сокет, event loop крутится. Всё. Если вам хочется проверять в /livez что-то ещё — сначала ответьте на вопрос «починит ли ЭТО рестарт процесса». Если нет — этому не место в liveness.
Вторая ошибка: таймауты, не переживающие реальность
timeoutSeconds: 1 — дефолт, и он агрессивен до неприличия. В реальном мире секундная пауза случается по десятку причин, не имеющих отношения к здоровью процесса: GC-пауза в JVM, CPU throttling из-за лимитов (отдельная больная тема), ретрансмиссия TCP, шумный сосед на ноде.
Мой базовый набор для liveness, от которого я отступаю осознанно, а не наоборот:
livenessProbe: httpGet: path: /livez port: 8080 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 6
Это даёт процессу минуту деградации до расстрела. «Минута?! А если он реально завис?» — если он реально завис, минута ничего не меняет: readiness выкинула его из трафика через секунды, пользователи его уже не видят. Liveness никогда не была механизмом быстрого failover — для этого есть readiness. Liveness — это медленный сборщик действительно мёртвых процессов, и спешка ей противопоказана.
И да, разные эндпоинты для liveness и readiness — не перфекционизм, а необходимость: у них разная семантика, разная цена ошибки и, как следствие, разное содержимое.
Третья ошибка: лечить пробами то, что пробы не лечат
Регулярно встречаю liveness как средство от утечек памяти («ну он раз в сутки перезапустится и норм») или от подвисаний, причину которых никто не искал. Это работает — в том смысле, в котором работает будильник, кидающий телефон об стену. Проблема в том, что рестарты по liveness выглядят в метриках безобидно, размазаны по времени и не алертятся — и настоящая бага живёт в проде годами, потому что симптом автоматически заметается под ковёр.
Если у вас есть restart count, растущий без OOMKilled в причинах — это не «пробы работают», это неисследованный инцидент на повторе.
Чек-лист
Вместо выводов — вопросы к каждому liveness-конфигу в вашем репозитории:
Ходит ли эндпоинт liveness в базу, кеш, соседний сервис? Если да — вы написали «убивать приложение при деградации зависимости»
Починит ли рестарт то, что проверяет проба? Если нет — проверке не место в liveness.
Переживёт ли проба GC-паузу, троттлинг, секунду сетевой икоты? timeoutSeconds: 1 не переживёт.
Совпадают ли эндпоинты liveness и readiness? Если да — у вас нет ни того, ни другого, у вас одна проба с неопределённой семантикой.
Смотрит ли кто-нибудь на restart count как на сигнал, а не как на фон?
А нужна ли liveness вообще — вопрос, который тоже стоит задавать. Для многих сервисов честный ответ: readiness обязательна, startup желательна, liveness — только если вы знаете конкретный сценарий зависания, который она ловит. «На всякий случай» — худшая причина дать кубелету право убивать ваши процессы.
Похожие разборы того, как Kubernetes и Linux ведут себя под капотом и почему привычные конфиги ломаются именно так, я пишу в телеграм-канале: t.me/rootcause_devops
А в комментариях интересно вот что: были ли у вас инциденты, где пробы сделали хуже — или наоборот, случай, когда агрессивная liveness реально спасла? Подозреваю, вторых будет сильно меньше, но хочу ошибаться.
Комментарии (3)

tigreavecdesailes
21.07.2026 06:56А ещё предусмотреть, что на дев-площадке разработчик может дебаггером зайти. Это вообще минут на 5-10 суммарно нужно timeout+threshold настраивать.
ch1971
Не очень в темя я честно говоря но както дико убивать приложение по глухому зависанию или по зависанию ввода/вывода и делать вид что так и надо просто вести статистику делать рестарты и более ничего. Наверное так можно делать по таймаутам клиентской стороны или БД но во втором случае всё равно желательно понять что это за запрос вызвал таймаут. А зависание это инцидент это надо както решать ладно ещё единичные случаи а если массово то это криво написанное приложение то есть там где то явная ошибка и эта кривизна однажды может вылезти таким боком что мало никому не покажется...
ProFfeSsoRr
Ну так а что делать, пока это решается? В этом и смысл Liveness, чтобы жить хоть как-то, пока инженеры разбираются в проблеме.