Health & Readiness
Health endpoints tell orchestrators whether a Python process is alive (liveness) and whether it can accept traffic (readiness). Split them so dependency blips drain traffic without restarting pods.
Recipe
from fastapi import FastAPI, Response, status
app = FastAPI()
@app.get("/health/live")
def live():
return {"status": "ok"}
@app.get("/health/ready")
def ready():
if not db_ping():
return Response(content='{"status":"down"}', status_code=503, media_type="application/json")
return {"status": "ok"}When to reach for this:
- Kubernetes/ECS load balancer health checks
- Rolling deploys gate traffic to ready pods only
- Dependency failures remove instance from rotation temporarily
- Synthetic monitoring external uptime checks (usually public
/health/liveonly)
Working Example
FastAPI readiness with timeout-bound DB check and startup probe friendly lazy init.
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI, Response, status
class HealthState:
def __init__(self) -> None:
self.db_ready = False
async def check_db(self) -> bool:
try:
await asyncio.wait_for(fake_db_ping(), timeout=0.5)
self.db_ready = True
return True
except Exception:
self.db_ready = False
return False
async def fake_db_ping() -> None:
await asyncio.sleep(0.01)
state = HealthState()
@asynccontextmanager
async def lifespan(app: FastAPI):
await state.check_db()
yield
app = FastAPI(lifespan=lifespan)
@app.get("/health/live")
async def live():
return {"status": "ok"}
@app.get("/health/ready")
async def ready():
ok = await state.check_db()
body = {"status": "ok" if ok else "degraded", "db": ok}
if not ok:
return Response(content=str(body), status_code=status.HTTP_503_SERVICE_UNAVAILABLE)
return bodyWhat this demonstrates:
- Liveness always 200 if event loop runs
- Readiness returns 503 when DB check fails - LB stops routing
- Short timeout on dependency check avoids hung probes
Deep Dive
Probe Mapping
| Endpoint | K8s probe | Fails when |
|---|---|---|
/health/live | liveness | deadlock (rare) |
/health/ready | readiness | deps unavailable |
/health/startup | startup | slow init |
Check Scope
- Readiness: DB pool, cache, required feature flags loaded
- Liveness: avoid external calls - kubelet restart is expensive
Python Notes
startupProbe:
httpGet:
path: /health/ready
port: 8000
failureThreshold: 30
periodSeconds: 10Gotchas
- DB check on liveness - DB outage kills all pods. Fix: readiness only for deps.
- Slow readiness always - deploy never finishes. Fix: startupProbe for slow init, fast readiness after.
- Readiness flapping - thundering herd on DB recovery. Fix: hysteresis or short cached check result (1-2s TTL).
- Public readiness exposes internals -
{"db": false}info leak. Fix: generic body externally, details in logs/metrics. - Health on wrong port - probe hits metrics port. Fix: document 8000 app port in Deployment.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| TCP socket probe | Non-HTTP service | FastAPI/Django HTTP apps |
| Exec probe | Custom script check | Simple HTTP sufficient |
| Mesh health | Istio/Linkerd | Plain K8s Service |
FAQs
One endpoint or two?
Minimum two paths for liveness vs readiness semantics - operators and K8s docs assume split.
Should readiness check S3?
Only if every request requires S3 - otherwise optional dependency belongs in degraded feature flag not 503.
Django health?
django-health-check app or lightweight view querying default DB connection.
What HTTP code?
200 ready, 503 not ready - some LBs accept only 200 as healthy - verify platform.
Authentication on health?
Usually unauthenticated on internal network; protect admin metrics separately.
How do migrations affect readiness?
During migrate job, old pods stay ready; new pods wait until migrations complete and pool connects.
Worker processes?
Celery workers expose separate health via inspect ping or custom HTTP sidecar.
Load balancer grace period?
Align with preStop and readiness failure threshold so traffic drains cleanly.
External synthetics?
Monitor /health/live from outside; alert on regional failures independent of kubelet.
Metrics on probe failures?
Counter readiness_check_failures_total helps debug flaky deps without reading probe logs.
Related
- Deployment Basics - health intro
- Kubernetes for Python Apps - probe YAML
- Zero-Downtime Deploys - drain behavior
- Metrics - probe failure counters
- Observability Best Practices - probe checklist
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.