Опирается на правила:
R-RES-HC-1…R-RES-HC-4иR-RES-HC-X1…R-RES-HC-X2из Resilience Style Guide → раздел 10. Health checks.
Важно знать
- На каждую внешнюю систему —
HealthIndicatorбин:SberHealthIndicator implements HealthIndicator.- Probe cached с TTL 30s. Не каждый
/actuator/healthзапрос ходит в Sber.- Probe-метод — light: GET
/healthили OPTIONS/, не реальный бизнес-вызов (register,confirmPayment).- Health отражается в
/actuator/health/<system>. K8slivenessProbeсмотрит на/actuator/health/liveness(overall),readinessProbe— на/actuator/health/readiness(включая внешние).- Sync-probe без кеша = DDoS внешней системы силами k8s (probe каждые 5s × N pod-ов = десятки RPS только от health-check'ов).
- Business-операция в probe изменяет состояние, плодит test-данные, нагружает систему.
Health check внешней системы — это способ k8s узнать, что мы не в состоянии нормально работать, не «глобальный pinger Sber». Поэтому он должен быть дешёвым (для нашей системы и для внешней) и точным (отражать реальную готовность принимать трафик). Раскрытие раздела 10 гайда.
HealthIndicator на каждую внешнюю систему
R-RES-HC-1: реализуем HealthIndicator бин на каждую внешнюю систему.
@Component
@RequiredArgsConstructor
public class SberHealthIndicator implements HealthIndicator {
private final SberHealthApi sberHealthApi;
private final Clock clock;
private volatile Health lastResult = Health.unknown().build();
private volatile Instant lastProbe = Instant.EPOCH;
private static final Duration TTL = Duration.ofSeconds(30);
@Override
public Health health() {
if (Duration.between(lastProbe, clock.instant()).compareTo(TTL) < 0) {
return lastResult;
}
lastProbe = clock.instant();
try {
SberHealthResponse resp = sberHealthApi.health();
lastResult = "UP".equals(resp.getStatus())
? Health.up().withDetail("version", resp.getVersion()).build()
: Health.down().withDetail("reason", resp.getReason()).build();
} catch (Exception e) {
lastResult = Health.down().withDetail("error", e.getMessage()).build();
}
return lastResult;
}
}
Spring Boot Actuator автоматически подхватит этот бин под /actuator/health/sber.
Cached probe с TTL 30s
R-RES-HC-2: probe cached, не дёргается на каждый actuator-запрос.
Зачем кэш:
- K8s по умолчанию probes каждые 10s. С 5 pod'ами это 30 запросов в минуту к Sber только за health.
- При TTL 30s — реальный поход к Sber раз в 30 секунд независимо от частоты actuator-запросов.
- 30s — компромисс: достаточно свежо чтобы заметить деградацию за минуту, не сильно нагружает внешнюю систему.
Реализации:
@Cacheable("health.sber")сCacheбином, у которогоexpireAfterWrite: 30s. Просто, но требует cache-инфраструктуры.- Ручной
volatile Instant + Health(как в примере выше). Простота, нет внешних зависимостей. - Reactive
Mono.cache(Duration)— если кода базовый стек реактивный.
Для большинства случаев — вариант 2.
Light probe — GET /health, не business
R-RES-HC-3: probe-метод — лёгкий технический endpoint, не бизнес-операция.
// ХОРОШО — light probe
SberHealthResponse resp = sberHealthApi.health(); // GET /health
// ХОРОШО — если у системы нет /health, OPTIONS на root
sberApi.options(); // OPTIONS /
Если внешняя система не предоставляет /health:
- Использовать OPTIONS на корневом endpoint — обычно отвечает быстро без реальной обработки.
- Использовать самый дешёвый GET (например, list metadata) с
?limit=1— минимизировать нагрузку. - В крайнем случае — просто TCP-connect проверить (без HTTP).
Никаких бизнес-операций:
- Не
getOrderStatus(testOrderId)— нагружает систему, портит метрики. - Не
register(testAmount)— изменяет состояние, плодит test-данные.
Структура /actuator/health
R-RES-HC-4: правильная организация exposure'а в Actuator.
management:
endpoint.health:
probes.enabled: true
show-details: always # only-when-authorized в проде
show-components: always
group:
readiness:
include: readinessState, db, sber, odnakassa
liveness:
include: livenessState
endpoints.web.exposure:
include: health, info, metrics, prometheus
server.port: 9090 # actuator на отдельном порту
Что важно:
liveness— только вlivenessState(приложение запущено и не зависло). Не включаем внешние системы — k8s не должен убивать pod, если Sber лежит.readiness— включает БД и критичные внешние системы. Pod выходит из Service backend pool, когда не готов принимать трафик (нет БД — точно не готов; нет Sber — может, готов, может, нет).- Per-system endpoints доступны как
/actuator/health/sber,/actuator/health/odnakassa— SRE видит детали.
Какие external включать в readiness — решение бизнес-домена:
- Если без Sber сервис физически не может ничего делать (платёжный шлюз) → включаем.
- Если без Sber только некоторые операции откажут → не включаем (handler сам вернёт 503 на конкретный endpoint).
Что запрещено
Sync-probe без кеша
R-RES-HC-X1: probe ходит к Sber на каждый actuator-запрос.
// ПЛОХО — без кеша
@Override
public Health health() {
try {
SberHealthResponse resp = sberHealthApi.health(); // ← каждый раз
return Health.up().build();
} catch (Exception e) {
return Health.down().build();
}
}
Что произойдёт:
- K8s livenessProbe + readinessProbe — каждый 10s. 2 probes × 5 pod'ов = 60 запросов в минуту к Sber.
- Если Prometheus тоже скрэйпит metrics endpoint каждые 15s, добавляется ещё load.
- При деградации Sber probes начинают висеть до timeout, что парализует actuator — другие health-check'и тоже не отвечают.
Корректно: TTL 30s, как показано выше.
Probe с business-операцией
R-RES-HC-X2: health-probe вызывает реальный business-метод.
// ПЛОХО — probe делает реальный платёжный запрос
@Override
public Health health() {
try {
sberApi.register(testRegisterRequest); // ← реальный платёж с тестовыми данными
return Health.up().build();
} catch (Exception e) {
return Health.down().build();
}
}
Что не так:
- Изменяет состояние. Каждые 30 секунд в Sber приходит test-register. После года работы — 1 миллион test-записей в их БД.
- Нагружает. Реальные операции дороже OPTIONS.
- Портит метрики. Бизнес-аналитика видит «register volume» с учётом наших probes — искажает реальные показатели.
- Может выполниться нежелательно. Если probe идёт через retry — три тест-вызова за раз.
Корректно: light technical endpoint (GET /health, OPTIONS /). Если нет — самый дешёвый read.
Что запрещено — таблица
| Антипаттерн | Правило | Что взамен |
|---|---|---|
| Sync-probe без TTL-кеша | R-RES-HC-X1 | TTL 30s |
| Probe вызывает business-операцию | R-RES-HC-X2 | Light technical endpoint |
Внешние системы в livenessProbe | R-RES-HC-4 | Только в readinessProbe |
| HealthIndicator без try-catch (выкидывает exception наружу) | R-RES-HC-1 | catch + Health.down() |
Один общий HealthIndicator на несколько систем | R-RES-HC-1 | Per-system bean |
| Actuator endpoints на основном HTTP-порту | R-RES-HC-4 | management.server.port: 9090 |
Куда дальше
- Resilience → раздел 10. Health checks — нормативные
R-RES-HC-*. - Observability — Micrometer-метрики для CB / Bulkhead.
- Per-system isolation — bean-структура per-system.
- Observability Style Guide — Actuator endpoints, exposure, k8s probes.
- Caching Style Guide —
@Cacheableдля probe-результата.