Опирается на правила: R-OBS-HC-1R-OBS-HC-3 и R-OBS-HC-X1R-OBS-HC-X3 из Observability Style Guide → раздел 4. Health checks.

Важно знать

  • Liveness и readiness — разные probes, разные семантики, разные dependencies.
  • /actuator/health/liveness — UP пока процесс отвечает. Не должен зависеть от внешних систем.
  • /actuator/health/readiness — UP когда сервис готов принимать трафик: БД подключена, dependencies прогреты.
  • Custom HealthIndicator для критичных внешних систем — с TTL-кешем, чтобы probe не дудосила сам сервис.
  • /actuator/info содержит git.commit.id, build.version, build.time — для отладки версии в проде.
  • Health — техническое состояние, не бизнес. orderCount > N → DOWN — антипаттерн.
  • Liveness не зависит от DB: иначе K8s рестартует pod в loop при кратковременной недоступности.

Health checks — то, на что смотрит Kubernetes, ALB и load balancer, решая «дать ли трафик в этот pod». Неправильно настроенные probes — главный источник cascading failure: одна реплика DB лагает → все pods unhealthy → весь сервис недоступен.

Liveness vs Readiness

R-OBS-HC-1: Spring Boot Actuator поддерживает оба типа probe из коробки.

management:
  endpoint:
    health:
      probes:
        enabled: true
      show-details: always
  health:
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true

После этого доступны два endpoint-а:

EndpointЧто проверяетЧто делает K8s
/actuator/health/livenessПроцесс не deadlocked, JVM отвечаетUP → продолжать; DOWN → рестартует pod
/actuator/health/readinessСервис готов принимать трафик: БД, прогревUP → шлёт трафик; DOWN → снимает из Service endpoints

Семантическое различие критично:

  • Если БД недоступна 5 секунд — readiness должен стать DOWN (трафик уйдёт на другие реплики), liveness должен остаться UP (рестарт pod-а не поможет, БД всё та же).
  • Если deadlock в JVM — liveness DOWN, K8s убивает pod, новый стартует с чистым thread pool.

K8s манифест:

spec:
  containers:
    - name: order-service
      livenessProbe:
        httpGet:
          path: /actuator/health/liveness
          port: 8081
        initialDelaySeconds: 30
        periodSeconds: 10
        failureThreshold: 3
      readinessProbe:
        httpGet:
          path: /actuator/health/readiness
          port: 8081
        initialDelaySeconds: 5
        periodSeconds: 5
        failureThreshold: 2

Порт 8081 — отдельный management port (management.server.port), не business 8080. Это позволяет ограничить /actuator/* сетевой политикой.

Custom HealthIndicator с TTL-кешем

R-OBS-HC-2: для каждой критичной внешней системы — отдельный HealthIndicator (см. R-RES-HC-1). Чтобы probe не делала десятки запросов к provider'у каждые 5 секунд — TTL-кеш (см. R-RES-HC-2).

@Component
@RequiredArgsConstructor
public class PaymentProviderHealthIndicator implements HealthIndicator {

    private final PaymentProviderClient client;
    private final AtomicReference<CachedHealth> cache = new AtomicReference<>();
    private static final Duration TTL = Duration.ofSeconds(10);

    @Override
    public Health health() {
        var cached = cache.get();
        if (cached != null && cached.expiresAt().isAfter(Instant.now())) {
            return cached.health();
        }

        var health = checkProvider();
        cache.set(new CachedHealth(health, Instant.now().plus(TTL)));
        return health;
    }

    private Health checkProvider() {
        try {
            client.ping();
            return Health.up().withDetail("provider", "sber").build();
        } catch (Exception e) {
            return Health.down(e).withDetail("provider", "sber").build();
        }
    }

    private record CachedHealth(Health health, Instant expiresAt) {}
}

Без TTL-кеша: K8s проверяет readiness каждые 5 секунд × 10 реплик × HealthIndicator дёргает provider → 20 ping/s на provider только от health-check. На провайдере rate-limit срабатывает, реальные запросы фейлятся, readiness становится DOWN, начинается cascading.

Auto-индикаторы (DataSourceHealthContributor для DB, RedisHealthIndicator для Redis) работают из коробки и сами дёшевые.

/actuator/info с git и build info

R-OBS-HC-3: ставим в Gradle git-commit-id-plugin, дописываем в application.yml:

management:
  info:
    git:
      mode: full
    build:
      enabled: true
info:
  service:
    name: ${spring.application.name}

В результате /actuator/info отдаёт:

{
  "git": {
    "commit": {
      "id": "5380f21abc...",
      "time": "2026-05-25T22:24:00Z"
    },
    "branch": "main"
  },
  "build": {
    "version": "1.4.2",
    "time": "2026-05-25T22:25:30Z",
    "artifact": "order-service"
  },
  "service": {
    "name": "order-service"
  }
}

Без этого endpoint каждый инцидент начинается с вопроса «какая версия в проде сейчас» и поиска через CI/CD логи.

Что запрещено

Business-state в health check

R-OBS-HC-X1: «если pending заказов > 1000 → DOWN». Это бизнес-метрика, не техническое здоровье. Health-probe в DOWN → K8s снимает реплику из Service endpoints → меньше реплик обрабатывают накопившиеся заказы → ещё хуже.

Бизнес-метрики мониторятся отдельно через Prometheus + alerting (см. SLO). Health — только техническое: процесс жив, dependencies доступны.

Liveness зависит от внешних систем

R-OBS-HC-X2: классическая ловушка.

// КАТАСТРОФА — liveness вернёт DOWN при недоступности DB
@Component
public class CustomLivenessIndicator implements HealthIndicator {
    public Health health() {
        try {
            jdbcTemplate.execute("SELECT 1");
            return Health.up().build();
        } catch (Exception e) {
            return Health.down(e).build();
        }
    }
}

Сценарий: PG лагает 30 секунд из-за VACUUM FULL. Liveness DOWN → K8s убивает pod → новый стартует, та же DB лагает → DOWN → убивает → loop. Все реплики падают за минуту, сервис недоступен.

Liveness обязан зависеть только от самого процесса (JVM, thread pool, disk). Внешние dependencies — только в readiness.

Health-probe делает business-операцию

R-OBS-HC-X3: «давайте в probe попробуем создать тестовый заказ и удалить». Каждые 5 секунд × 10 реплик = ddos самих себя. См. R-RES-HC-X2.

Probe — light: SELECT 1, ping, cached value. Не business work.

Что запрещено — таблица

АнтипаттернПравилоЧто взамен
Business-state в health checkR-OBS-HC-X1техническое состояние; бизнес → SLO
Liveness зависит от DB/Redis/externalR-OBS-HC-X2только readiness зависит от внешних
Probe делает business-операциюR-OBS-HC-X3light probe (ping, cached)
HealthIndicator без TTL-кешаR-OBS-HC-2TTL 5-30 секунд
/actuator/health показывает details публичноR-OBS-HC-1show-details: when-authorized или internal-only
Нет /actuator/info с git+buildR-OBS-HC-3git-commit-id-plugin + management.info.*

Куда дальше

  • Observability → раздел 4. Health checks — нормативные формулировки.
  • Configuration — management port, exposed endpoints.
  • Resilience → health checks — HealthIndicator per-system, TTL.
  • Metrics — бизнес-метрики мониторятся через Prometheus, не health.
  • SLO и алерты — бизнес-цели отдельно от probes.
  • Graceful shutdown — readiness DOWN при SIGTERM, drain in-flight.