🩺 Three Probes, Three Questions
Kubernetes uses three independent probes to decide how to handle a running container. Each answers a different question:
🔴 Liveness
"Is this container still alive?" — Failure triggers a container restart. Use for deadlock detection.
🟡 Readiness
"Is this container ready to serve traffic?" — Failure removes the pod from Service endpoints. Use for warm-up and dependency checks.
🟢 Startup
"Has this container finished starting up?" — Disables liveness/readiness until it passes. Use for slow-starting apps.
🔌 Four Probe Mechanisms
Each of the three probe types can use any of these four mechanisms to check health:
| Mechanism | How it works | Best for |
|---|---|---|
| httpGet | kubelet makes an HTTP GET to the container's IP; 2xx–3xx = success, anything else = failure | HTTP/HTTPS services — most common |
| tcpSocket | kubelet opens a TCP connection to the specified port; connection opens = success | Non-HTTP services (databases, message queues) |
| exec | kubelet runs a command inside the container; exit code 0 = success | Custom health logic, file existence checks |
| grpc | kubelet calls the gRPC Health Checking Protocol; SERVING = success (K8s 1.24+ GA) | gRPC services — avoids an extra HTTP sidecar |
All Four in YAML
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: X-Health-Check
value: "true"
readinessProbe:
tcpSocket:
port: 5432 # e.g. postgres readiness
livenessProbe:
exec:
command:
- sh
- -c
- "redis-cli ping | grep PONG"
livenessProbe:
grpc:
port: 50051
service: "" # empty string = overall server health
🟡 Readiness Probe — Traffic Gate
The readiness probe controls whether a pod receives traffic from a Service. When it fails, the pod's IP is removed from the Service's Endpoints/EndpointSlice — no new connections are routed to it. The pod is NOT restarted; it just stops receiving traffic until the probe passes again.
Key use cases
🔥 Warm-up
JVM apps, ML models, or caches that need time to load before serving. Keep pod out of rotation until warm.
🔗 Dependency check
App can't serve if the database is unreachable. Readiness (not liveness!) should check this — no restart, just traffic hold.
⚖️ Graceful overload
Pod signals it's overloaded by failing readiness. Load balancer stops sending new requests while in-flight ones finish.
🚀 Rolling deployment
New pods must pass readiness before old pods are removed. Prevents a broken deployment from taking down all replicas.
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
successThreshold: 1 # 1 success to re-enter rotation
failureThreshold: 3 # 3 failures to leave rotation
🎛️ Tuning Probe Parameters
Misconfigured probe thresholds are one of the most common causes of production incidents. Here is a field-by-field guide:
| Field | Default | Meaning | Tuning guidance |
|---|---|---|---|
initialDelaySeconds |
0 | Wait before first probe fires | Use startup probe instead. If not using startup probe, set to your p95 startup time. |
periodSeconds |
10 | How often to probe | 10–15s for liveness. 5–10s for readiness (faster traffic recovery). |
timeoutSeconds |
1 | Max wait for a probe response | Increase to 3–5s. Default 1s is too tight under load — causes false failures. |
successThreshold |
1 | Consecutive successes to become healthy | Must be 1 for liveness/startup. Can be 2–3 for readiness to prevent flapping. |
failureThreshold |
3 | Consecutive failures before action | 3 is usually right for liveness. For readiness, consider 1 for fast egress from rotation. |
Probe Anti-Patterns to Avoid
| Anti-pattern | Problem | Fix |
|---|---|---|
| Liveness checks external deps (DB, Redis) | Restarts app when dependency is down — cascading failure | Liveness = internal only; readiness = dependencies |
| No startup probe on slow JVM/Python apps | Liveness kills pod before it finishes starting → CrashLoopBackOff | Add startup probe with generous failure budget |
timeoutSeconds: 1 under load |
Slow responses under CPU pressure cause false liveness failures and restarts | Set timeoutSeconds to 3–5s |
| Same endpoint for liveness and readiness | Dependency failure triggers restart instead of traffic removal | Separate /healthz (liveness) from /readyz (readiness) |
| exec probe running expensive script | Probe consumes CPU on a tight periodSeconds — starves the app | Use httpGet or reduce periodSeconds; keep exec probes lightweight |
Observing Probe State
# See probe configuration and recent events
kubectl describe pod my-app-xyz
# Events section shows probe failures:
# Warning Unhealthy Liveness probe failed: Get "http://10.0.0.5:8080/healthz": context deadline exceeded
# Warning Killing Container my-app failed liveness probe, will be restarted
# Check restart count — tells you if liveness has been firing
kubectl get pod my-app-xyz -o jsonpath='{.status.containerStatuses[0].restartCount}'
# Watch readiness — pod is Ready only when readiness passes
kubectl get pod my-app-xyz -w
# NAME READY STATUS RESTARTS
# my-app-xyz 0/1 Running 0 ← readiness failing
# my-app-xyz 1/1 Running 0 ← readiness passed
🔴 Liveness Probe — Restart on Deadlock
The liveness probe detects containers that are running but stuck — a goroutine leak causing deadlock, a thread pool exhausted, or a process that stopped responding but hasn't exited. Without it, the container sits in Running state forever while serving errors.
When liveness fails
- kubelet increments the failure counter.
- Once
failureThresholdconsecutive failures are reached, kubelet kills the container. - The container restarts according to the pod's
restartPolicy(almost alwaysAlwaysfor long-running services). - The pod's
restartCountincrements — visible inkubectl describe pod.
Complete liveness example with all fields
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10 # wait before first probe
periodSeconds: 15 # probe every 15s
timeoutSeconds: 3 # fail if no response within 3s
successThreshold: 1 # 1 success to go healthy (must be 1 for liveness)
failureThreshold: 3 # 3 consecutive failures → restart
timeoutSeconds: 1 will cause cascading restarts. Liveness should only check the application's own internal health (e.g. can it handle requests at all).
What a good /healthz endpoint looks like
// Go example — liveness checks ONLY internal state
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
if isDeadlocked() || threadPoolExhausted() {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
// Readiness checks dependencies too
http.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
if err := db.Ping(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
})
🟢 Startup Probe — Protect Slow Starters
Before startup probes existed (added in 1.18, GA in 1.20), the only way to handle slow-starting apps was setting a large initialDelaySeconds on liveness — which meant waiting that long for every restart, slowing recovery. The startup probe solves this elegantly.
How startup probe works
- While the startup probe is pending or failing, liveness and readiness probes are disabled.
- Once the startup probe succeeds once, it hands off to liveness and readiness.
- If the startup probe fails
failureThreshold × periodSecondstotal time, the container is killed (same as liveness).
# Pattern: give a slow app up to 5 minutes to start
# then switch to a tight 15s liveness cycle
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 30 × 10s = 300s max startup window
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 15
failureThreshold: 3 # tight once running
CrashLoopBackOff on a slow-starting app, the liveness probe is likely killing it before it finishes starting. Add a startup probe with a generous failureThreshold × periodSeconds budget matching your worst-case startup time.
🧠 Knowledge Check
Q1. A pod shows READY 0/1 but STATUS Running. What is happening?
Q2. Why should a liveness probe NOT check external dependencies like a database connection?
Q3. A Java app takes up to 3 minutes to start. What is the correct probe configuration?
initialDelaySeconds: 180 on the liveness probefailureThreshold: 18 and periodSeconds: 10 (180s budget)restartPolicy: Never to prevent premature restarts