.NET Tutorial
Middle5 мин чтенияВопрос 14 из 19

Health checks, Liveness vs Readiness probes?

Три вида проб

нет, лимит попыток
исчерпан

ждём

да

провал N раз

ok

провал

ok

Контейнер стартовал

Startup probe
прошла?

Перезапуск контейнера

Периодически:

Liveness

Readiness

Убрать из Service
(нет трафика)

В балансировке

нет, лимит попыток
исчерпан

ждём

да

провал N раз

ok

провал

ok

Контейнер стартовал

Startup probe
прошла?

Перезапуск контейнера

Периодически:

Liveness

Readiness

Убрать из Service
(нет трафика)

В балансировке

Как Kubernetes реагирует на пробы
Проба Вопрос Реакция на провал Что проверять
Startup Приложение закончило запуск? Перезапуск после failureThreshold Прогрев, миграции, загрузка кэша
Liveness Процесс не завис? Перезапуск контейнера Только сам процесс: отвечает ли, нет ли deadlock
Readiness Можно слать трафик? Исключение из балансировки Зависимости, без которых запросы точно упадут; перегрузка; shutdown

Настройка в ASP.NET Core

C#
builder.Services.AddHealthChecks()
    // liveness: только «я жив»
    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
    // readiness: зависимости (пакеты AspNetCore.HealthChecks.*)
    .AddNpgSql(builder.Configuration.GetConnectionString("Default")!, name: "postgres", tags: ["ready"], timeout: TimeSpan.FromSeconds(3))
    .AddRedis(builder.Configuration["Redis:Connection"]!, name: "redis", tags: ["ready"], failureStatus: HealthStatus.Degraded)
    .AddCheck<CacheWarmupHealthCheck>("warmup", tags: ["ready", "startup"]);

var app = builder.Build();

app.MapHealthChecks("/health/live",  new HealthCheckOptions { Predicate = c => c.Tags.Contains("live") });
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
    Predicate = c => c.Tags.Contains("ready"),
    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse     // подробный JSON (пакет HealthChecks.UI.Client)
});
app.MapHealthChecks("/health/startup", new HealthCheckOptions { Predicate = c => c.Tags.Contains("startup") });
Собственная проверка
public sealed class CacheWarmupHealthCheck(CatalogCache cache) : IHealthCheck
{
    public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken ct = default) =>
        Task.FromResult(cache.IsWarmedUp
            ? HealthCheckResult.Healthy("Кэш каталога загружен")
            : HealthCheckResult.Unhealthy("Кэш каталога ещё загружается"));
}

Статусы: Healthy → HTTP 200, Degraded → 200 (по умолчанию), Unhealthy → 503. failureStatus: Degraded для некритичной зависимости (Redis-кэш): сервис работает медленнее, но работает.

Конфигурация Kubernetes

deployment.yaml (фрагмент)
containers:
  - name: orders-api
    image: registry.example.com/orders-api:1.4.2
    ports: [{ containerPort: 8080 }]
    startupProbe:
      httpGet: { path: /health/startup, port: 8080 }
      periodSeconds: 5
      failureThreshold: 30          # до 150 секунд на старт
    livenessProbe:
      httpGet: { path: /health/live, port: 8080 }
      periodSeconds: 10
      timeoutSeconds: 2
      failureThreshold: 3           # перезапуск после ~30 секунд зависания
    readinessProbe:
      httpGet: { path: /health/ready, port: 8080 }
      periodSeconds: 5
      timeoutSeconds: 3
      failureThreshold: 2

Почему нельзя проверять БД в liveness

Pod 3Pod 2Pod 1KubernetesPostgreSQLкратковременныйсбой 20 секундБД восстановиласьвсе поды одновременно стартуют,прогреваются, открывают соединения —шторм нагрузки на БД, сервис недоступен дольшеliveness: проверка БД —провалliveness: провалliveness: провалперезапускперезапускперезапуск
Pod 3Pod 2Pod 1KubernetesPostgreSQLкратковременныйсбой 20 секундБД восстановиласьвсе поды одновременно стартуют,прогреваются, открывают соединения —шторм нагрузки на БД, сервис недоступен дольшеliveness: проверка БД —провалliveness: провалliveness: провалперезапускперезапускперезапуск
Каскадный перезапуск из-за неправильной liveness-пробы

Перезапуск контейнера не лечит недоступную базу. Правильно: БД — в readiness (или вообще не в пробах, если сервис умеет частично работать без неё), liveness — только признаки зависания самого процесса.

Защита и производительность

  • Health-эндпоинты не должны раскрывать внутренности наружу: детальный ответ — только для внутренней сети (RequireHost("*:8081"), отдельный порт) или с авторизацией.
  • Проверки должны быть быстрыми и с таймаутом — проба с таймаутом 2 с, а проверка БД висит 30 с → ложные провалы.
  • Дорогие проверки можно кэшировать (HealthCheckPublisherOptions, собственный кэш результата) — пробы вызываются часто и со всех узлов.
  • Health Check Publisher (IHealthCheckPublisher) периодически публикует результаты в мониторинг (Prometheus, Application Insights).

Вопросы на засыпку

Чем readiness отличается от startup probe?

Startup выполняется только до первого успеха и защищает медленно стартующее приложение от преждевременных liveness-перезапусков. Readiness выполняется всё время жизни и может многократно переключаться между «готов» и «не готов».

Что вернуть в readiness, если одна из трёх зависимостей недоступна?

Зависит от того, может ли сервис обслуживать запросы. Если без неё не работает ничего — Unhealthy. Если страдает только часть функций — Degraded/Healthy, а отказ обрабатывается в коде (Circuit Breaker, fallback). Readiness = «слать ли сюда трафик вообще».

Нужны ли health checks вне Kubernetes?

Да: балансировщики (Nginx, HAProxy, облачные LB), Docker (HEALTHCHECK), мониторинг (uptime-проверки), Azure App Service — все используют их для маршрутизации и алертов.

Как проверить, что процесс «завис», если он отвечает на HTTP?

Например, проверять, что фоновый обработчик очереди делал итерацию недавно (heartbeat-метка времени), или что пул потоков не голодает. Ответ на HTTP — минимальная liveness; зависшие фоновые компоненты требуют собственных проверок.

Итог

Liveness отвечает «жив ли процесс» и должна быть максимально простой, readiness — «можно ли слать трафик» и проверяет критичные зависимости, startup — защищает медленный старт. Разделяйте их через теги и отдельные эндпоинты и никогда не завязывайте liveness на внешние системы.