В прошлый вторник мы словили странную проблему. Наш биллинговый микросервис, который работает уже полгода без нареканий, внезапно начал отдавать 503 часть запросов. Причём не все — где-то 10–15% запросов падали с ошибкой, а остальные обрабатывались нормально. Графики выглядели как зубья пилы: каждые несколько минут трафик на поде обрывался и восстанавливался.
Что произошло
Начал разбираться. Зашёл на под через kubectl exec — приложение работает, логи чистые, запросы к API обрабатываются. Посмотрел события пода:
kubectl describe pod billing-service-7d4f8b-x2k9p
Events:
Warning Unhealthy 2m kubelet Readiness probe failed
Warning Unhealthy 1m kubelet Readiness probe failed: context deadline exceededАга. Readiness probe падала, и Kubernetes убирал под из Service. Трафик шёл на оставшиеся поды, но при трёх репликах и потере одной — нагрузка распределялась неравномерно, часть запросов отваливалась по таймауту.
Liveness vs Readiness vs Startup
Перед тем как описывать наше решение, давайте разберёмся с тремя типами проб в Kubernetes. Это важно, потому что многие их путают и ставят одну и ту же проверку на все три.
Liveness probe — «живой ли процесс?». Если падает — Kubernetes перезапускает контейнер. Здесь нельзя проверять внешние зависимости, иначе получите cascade failure: упал Redis — все поды перезапускаются одновременно — Redis и вовсе ложится под нагрузкой.
Readiness probe — «готов ли под принимать трафик?». Если падает — под исключается из Service, но не перезапускается. Именно здесь мы сделали ошибку.
Startup probe — «запустился ли контейнер?». Используется для медленно стартующих приложений. Пока startup probe успешна — liveness и readiness не проверяются.
Наша ошибка
Вот как выглядел наш манифест до фикса:
spec:
containers:
- name: billing-service
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3Эндпоинт /health делал следующее: проверял подключение к PostgreSQL, Redis и Kafka. Если хоть одно соединение не отвечало — возвращал 500. А в ту ночь DBA запустил pg_dump для бэкапа, PostgreSQL начал тормозить, health check отвалился по таймауту, и Kubernetes выкинул под из балансировщика.
Правильная архитектура health checks
Главный принцип: readiness probe не должна зависеть от внешних ресурсов. Она должна проверять только то, что контролирует сам под. Вот как мы переделали:
spec:
containers:
- name: billing-service
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 15
failureThreshold: 3
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 2
successThreshold: 1
startupProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 0
periodSeconds: 3
failureThreshold: 20/health/live
Проверяет только то, что HTTP-сервер поднят и отвечает. Ничего внешнего. Если этот эндпоинт не отвечает — контейнер действительно завис и его надо перезапустить. Внутри это буквально:
@GetMapping("/health/live")
fun liveness(): ResponseEntity = ResponseEntity.ok().build() /health/ready
Проверяет, что приложение инициализировано и внутренне готово. Мы убрали из него проверки PostgreSQL, Redis и Kafka. Теперь он проверяет только: загружен ли кэш конфигурации, инициализирован ли пул соединений (не то, что он подключён, а то, что он создан). Если PostgreSQL кратковременно недоступен — под остаётся в сервисе, а обработка запросов с ошибкой подключения к БД делегируется самому приложению.
Backoff-стратегии
Ещё одна важная деталь: periodSeconds и failureThreshold формируют окно терпимости Kubernetes. Наш readiness probe с periodSeconds: 5 и failureThreshold: 2 означает, что под будет выкинут из Service через 10 секунд после первой неудачной проверки. Для liveness probe мы поставили failureThreshold: 3 и periodSeconds: 15 — 45 секунд до перезапуска.
Мониторинг
После инцидента мы добавили в Grafana дашборд метрики kube-probe. Теперь видим, как часто readiness probe падает, и можем коррелировать с бэкапами БД. Если видим всплеск отказов readiness каждый день в 3:00 — это повод перенести бэкап или добавить buffer в health check.
Главный урок: health checks в Kubernetes — это не формальность. Неправильная конфигурация readiness probe может быть хуже, чем её отсутствие. Отделяйте «я жив» от «я готов» и никогда не делайте liveness зависимым от внешних систем.
Пока нет комментариев
Будьте первым, кто оставит комментарий к этой статье