Liveness vs readiness: one restarts, one just stops traffic
Readiness asks "should I send you work?". Liveness asks "should I put you out of your misery?". Wiring the second one to a slow dependency is how a brief blip becomes a full outage.
Both probes hit an endpoint on your pod, on a schedule, and both mark it failed after enough misses. What differs is entirely what Kubernetes does about it.
One pod, one slow database, two probe configurations
1 / 4readinessProbe
Probe passing. The pod is in the Service's endpoints.
livenessProbe
Identical starting point. Probe passing, traffic flowing.
A healthy pod. The probe passes, the Service has it in its endpoint list, and traffic flows.
Why this specific mistake is so common
The two probes take the same fields, so the natural thing is to copy the block and change one word:
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
readinessProbe:
httpGet: { path: /healthz, port: 8080 } # ← same endpointThat looks tidy and is quietly dangerous. If /healthz checks the database,
you have told Kubernetes "restart me whenever the database is slow", and
since every replica shares that database, every replica restarts together.
The endpoints should ask different questions:
/healthz(liveness): "is this process wedged?" Return 200 unless the process is genuinely unrecoverable: a deadlocked thread pool, a corrupted in-memory state. No dependency calls. If in doubt, return 200./ready(readiness): "can I serve a request right now?" Here you check the database, the cache, the migration state. Failing is cheap and reversible.
| readinessProbe | livenessProbe | |
|---|---|---|
| On failure | Pod removed from the Service's endpoints. | Container killed and restarted. |
| Reversible? | Yes, passes again, traffic returns instantly. | No. A restart is a restart. |
| Cost of a false positive | A little lost capacity. | Cold start, dropped requests, possible crash loop. |
| Should it check dependencies? | Yes, that is the point. | No. Only the process's own health. |
| Blast radius | One pod at a time. | Every replica at once, if they share the cause. |
| Safe default | Check what you need to serve. | Return 200 unless you are certain. |
The startup case
There is a third probe that exists purely because people used to solve slow
boots by loosening liveness. startupProbe runs first, and both other probes
are disabled until it passes:
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30
periodSeconds: 10 # up to 5 minutes to bootThat buys a slow-starting app five minutes without giving it a permanently lax liveness threshold for the rest of its life.
Remember this
- 1Readiness controls traffic. Liveness controls restarts. Every other difference follows from that one.
- 2Never check a shared dependency in a liveness probe: every replica shares it, so every replica restarts at once.
- 3Liveness is for a wedged process, not a slow one. If you are unsure whether to fail it, return 200.