← Паттерны приложений
10 мин · просто · Урок 2 из 5

Health и readiness эндпоинты

Чем liveness отличается от readiness, зачем приложению отдельный /healthz и как это связано с балансировкой и оркестратором.

Откуда оркестратор знает, что приложение живо

Балансировщик и оркестратор не умеют читать мысли приложения. Им нужен способ спросить «ты в порядке?» по сети. Поэтому приложение отдаёт специальный HTTP-эндпоинт, который быстро отвечает о своём состоянии. По соглашению его называют /healthz или /health.

Проверок три, и каждая отвечает на свой вопрос: запустилось ли приложение, живо ли оно и готово ли принимать трафик.

Liveness: приложение живо

Liveness отвечает на вопрос «приложение не зависло?». Если liveness перестаёт отвечать или возвращает ошибку, оркестратор считает копию мёртвой и перезапускает её. Это лечит зависания, из которых процесс сам не выберется (дедлок, утечка, бесконечный цикл).

Liveness делают максимально простым: вернуть 200 OK, если процесс жив. Он не должен ходить в базу или внешние сервисы, иначе временный сбой базы вызовет лавину ненужных перезапусков.

$ curl -s -o /dev/null -w "%{http_code}" http://app:8080/healthz
200

Readiness: приложение готово к трафику

Readiness отвечает на другой вопрос: «можно ли уже слать сюда запросы?». Приложение запустилось, но ещё прогревает кеш, устанавливает соединение с базой, читает конфиг. В этот момент оно живо, но не готово. Readiness возвращает не 200, пока приложение не готово, и балансировщик не шлёт на него трафик.

В отличие от liveness, readiness как раз проверяет зависимости: есть ли соединение с базой, доступна ли очередь. Нет базы, значит не готов обслуживать, и трафик идёт на здоровые копии.

Startup: приложение ещё запускается

Третья проверка решает проблему медленного старта. Пусть приложению нужно две минуты на прогрев: поднять JVM, прочитать большой конфиг, накатить миграции. Liveness с обычным таймаутом за это время решит, что процесс завис, и перезапустит его. Приложение начнёт стартовать заново, опять не успеет — и так по кругу. Классический CrashLoopBackOff на ровном месте.

Раньше это лечили костылём: ставили liveness огромный initialDelaySeconds или десятки разрешённых провалов. Побочный эффект неприятный — после прогрева проверка становится вялой, и реальное зависание ловится не за пять секунд, а за пять минут.

Startup probe разводит эти два режима. Она отвечает на вопрос «стартовало ли приложение» и работает только в начале жизни копии. В Kubernetes она появилась в 1.16 и стала стабильной в 1.20.

Порядок такой. Пока startup не вернула успех, liveness и readiness вообще не запускаются — оркестратор просто ждёт, столько, сколько разрешает бюджет startup-проверки. Как только startup прошла, она больше не выполняется никогда, и включаются liveness с readiness со своими нормальными, короткими таймаутами.

старт копии
   │
   ├── startup probe: опрашивается, пока не ответит 200
   │       (liveness и readiness ждут)
   │
   └── startup прошла → выключается навсегда
           │
           ├── liveness   — весь остаток жизни
           └── readiness  — весь остаток жизни

Бюджет startup считают как «период опроса × число попыток». Тридцать попыток раз в 10 секунд дают пять минут на запуск. Не уложился — копию перезапускают.

Отдельный эндпоинт под startup обычно не заводят: чаще всего она смотрит на тот же /healthz, только с более щедрым лимитом попыток.

Зачем разделять

Разделение даёт правильное поведение в трёх случаях:

Если бы liveness ходил в базу, такой сбой устроил бы каскад перезапусков на ровном месте.

Связь с выкатом

Readiness замыкает прошлые уроки. Rolling и canary полагаются на него: новую копию начинают грузить трафиком, только когда её readiness стал 200. А graceful shutdown наоборот: при SIGTERM приложение переводит readiness в «не готов», балансировщик убирает его из ротации, и дренаж проходит без потерянных запросов.

Что важно запомнить

Приложение отдаёт health-эндпоинты, чтобы оркестратор и балансировщик знали его состояние по сети. Liveness отвечает «живо ли» (провал → перезапуск) и делается простым, без зависимостей.

Readiness отвечает «готово ли к трафику» (провал → убрать из ротации) и как раз проверяет базу и очереди. Разделение спасает при старте и временных сбоях зависимостей.

Startup отвечает «запустилось ли» и работает только в начале жизни копии: пока она не прошла, liveness и readiness не выполняются. Так медленный старт не превращается в бесконечный перезапуск, а liveness после прогрева остаётся быстрым.

Выкат грузит копию трафиком по readiness, а graceful shutdown переводит readiness в «не готов» перед дренажом.

В следующем уроке разберём 12-factor конфигурацию: как держать настройки в окружении.

Прошли материал до конца? Отметьте урок пройденным.