rgoussu@goussu: ~/library/platform/observability-and-sre
~/library/platform/observability-and-sre cat health-checks-and-probes.md

Health checks: liveness, readiness, startup

# Designing probe endpoints from the probed side — liveness without dependency checks, readiness as the traffic gate, startup probes for slow boots — and the endpoint catalogue across Actuator, SmallRye, Micronaut, and hand-rolled stacks.

Conceptsaved 2026-08-11 #observability#health-checks#liveness#readiness#kubernetes#probes#actuator#microprofile

Overview

Health endpoints are the contract between a service and the platform that runs it — and the design question is best asked from the probed side: what should the orchestrator do when this check fails? If the answer is "restart me", that's liveness; if it's "stop sending me traffic", that's readiness; if it's "give me more time to boot", that's a startup probe. Most production probe incidents come from conflating the first two — putting dependency checks into liveness turns any database blip into a fleet-wide restart storm.

Key points

  • Liveness = "the process is alive and not deadlocked; restart me if this fails." It must be cheap, in-process, and must NOT check dependencies: a dead database would fail liveness on every pod at once and Kubernetes would restart-storm the whole deployment without fixing anything — restarts don't resurrect databases. A bare 200 from the running event loop / a trivial in-process check is correct.
  • Readiness = "route traffic to me." It does include critical downstream checks (the database this service cannot serve without, an unfinished cache warmup, a not-yet-completed boot) — failing readiness removes the pod from Service endpoints (load balancing) without restarting it, and it re-enters rotation when the check recovers. Readiness is also how graceful shutdown drains: fail readiness first, keep serving in-flight requests, then exit.
  • Startup probe covers slow boots (JVM warmup, migrations, large caches): until it succeeds, liveness/readiness are not evaluated, so liveness can stay tight (failureThreshold × periodSeconds on the startup probe is the boot budget). Without it, teams inflate initialDelaySeconds on liveness and lose fast deadlock detection.
  • Readiness on shared dependencies cascades: if every service in a chain checks a shared broker in readiness, one broker incident marks the whole estate unready and takes down even the endpoints that could still serve. Check only dependencies that are hard prerequisites for this service's own traffic; degrade instead of failing where partial service is possible.
  • Check design: per-dependency checkers with an overall AND for readiness; strict timeouts on each check (well under timeoutSeconds, or a slow dependency makes the probe itself time out — indistinguishable from a dead process); cache results a few seconds so aggressive periodSeconds doesn't hammer dependencies.
  • Exposure: health endpoints are unauthenticated by design — serve them on a separate management port (Actuator management.server.port, Micronaut's management port, a second listener in stdlib stacks) and never route them through the public ingress; the detail payload (dependency names, versions) is reconnaissance material.
  • The wiring lives at the assembly point: readiness state is flipped after the object graph is built and adapters are connected — the composition root owns "ready", per the hexagonal placement.

Details

The three probes at a glance

Probe Question On failure Checks dependencies?
Liveness Alive and not deadlocked? Container restarted Never
Readiness Should traffic arrive? Removed from endpoints; no restart Critical ones only
Startup Finished booting? Keeps waiting (until threshold, then restart) Boot completion only

Endpoint catalogue per stack

Stack Liveness Readiness Custom check API
Quarkus (SmallRye Health / MicroProfile Health) /q/health/live /q/health/ready @Liveness / @Readiness CDI beans implementing HealthCheck (/q/health/started for @Startup)
Spring Boot Actuator /actuator/health/liveness /actuator/health/readiness availability states (LivenessState, ReadinessState), health groups; probes auto-enabled on k8s or via management.endpoint.health.probes.enabled=true; custom HealthIndicator added to the readiness group (management.endpoint.health.group.readiness.include=readinessState,db)
Micronaut Management /health/liveness /health/readiness HealthIndicator beans; micronaut-management module
Go net/http, Rust axum, Node http (hand-rolled) /health/live → static 200 /health/ready → atomic ready flag flipped after wiring, AND over per-dependency checkers your own Checker interface/trait/function per dependency

Convention for hand-rolled stacks: return 200 with a small JSON body ({"status":"UP","checks":[…]} mirrors MicroProfile's schema) and 503 when down — Kubernetes only reads the status code, humans read the body.

Kubernetes wiring

livenessProbe:
  httpGet: { path: /health/live, port: 8081 }
  periodSeconds: 10
  timeoutSeconds: 1
  failureThreshold: 3
readinessProbe:
  httpGet: { path: /health/ready, port: 8081 }
  periodSeconds: 5
  failureThreshold: 3
startupProbe:
  httpGet: { path: /health/live, port: 8081 }
  periodSeconds: 5
  failureThreshold: 60   # 5 min boot budget

httpGet treats any 2xx–3xx as success. Probe handlers must not allocate per-request state worth tracing — exclude them from access logs, correlation middleware, and trace sampling, or they dominate your telemetry volume.

Pitfalls checklist

  • Dependency check in liveness → restart storm on dependency outage.
  • Shared-dependency check in every readiness → cascading estate-wide unreadiness.
  • Probe timeoutSeconds shorter than the slowest included check → false deaths under load.
  • No startup probe + slow boot → inflated initialDelaySeconds, slow deadlock detection.
  • Health on the public port → information disclosure and probe traffic through the ingress.
  • Ready flag flipped before adapters actually connected → 503s/errors during rollout.

Related

Citations

[1] Kubernetes — Liveness, Readiness and Startup Probes [2] Spring Boot — Kubernetes probes (availability & health groups) [3] SmallRye Health / MicroProfile Health