Загрузка
Все статьи

Kubernetes health checks: как мы потеряли трафик из-за readiness probe

Микросервис работал нормально, но Kubernetes периодически убирал его из балансировщика. Пользователи видели 503. Оказалось, readiness probe проверял подключение к PostgreSQL, и при кратковременных просадках БД (backup, vacuum) сервис становился «не готовым». Рассказываю, как правильно настроить health checks в Kubernetes, чтобы не терять трафик из-за сторонних проблем.

В прошлый вторник мы словили странную проблему. Наш биллинговый микросервис, который работает уже полгода без нареканий, внезапно начал отдавать 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 зависимым от внешних систем.

Поделиться статьёй

Комментарии

0
0 / 2000

Пока нет комментариев

Будьте первым, кто оставит комментарий к этой статье

Читайте также

Docker Compose для локальной разработки Kotlin-микросервисов — от 10 минут до 30 секунд старта

Как мы оптимизировали локальное окружение из 10+ микросервисов с помощью Docker Compose, multi-stage Dockerfile и правильных health checks.

15 июня 2026 10 мин

Terraform vs Pulumi — Infrastructure as Code для Kotlin-команды

Сравниваем Terraform на HCL и Pulumi на Kotlin для управления Kubernetes-инфраструктурой — с реальными примерами кода, болью миграции и нашим итоговым выбором.

14 июня 2026 11 мин

Nginx + PHP-FPM: тюнинг под высокую нагрузку — от 500 RPS до 5000 RPS

Сайт на PHP + Nginx выдерживал 500 RPS, а при 1000 начинал отдавать 502. Проблема была не в PHP-коде (он был оптимизирован), а в конфигурации Nginx и PHP-FPM: неправильный worker_connections, отсутствие keepalive к upstream, слишком маленький pm.max_children. Рассказываю пошагово, как мы увеличили пропускную способность в 10 раз.

23 мая 2026 11 мин