04 · Health Checks (Liveness & Readiness Probes)¶
Not run against a live cluster
Manifests and output below follow documented kubelet probe behavior; not executed against a live cluster in this session.
Why "container is running" isn't "container is healthy"¶
kubelet's default health signal is just "is the process still alive" — a container can be running and yet deadlocked, stuck waiting on a dependency forever, or still warming up a cache and unable to serve traffic. Probes let kubelet ask the application itself whether it's actually working.
Liveness probes: restart if stuck¶
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: app
image: myapp:1.4
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 2
If /healthz fails 3 consecutive times (failureThreshold), kubelet kills
the container and the container runtime restarts it, subject to the Pod's
restartPolicy. This is for unrecoverable-without-a-restart states —
deadlocks, poisoned in-memory state — not for transient upstream issues,
which would just cause a restart loop without fixing anything.
Readiness probes: pull out of Service traffic without restarting¶
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
A failing readiness probe does not restart the container — it removes the Pod's IP from the Service's EndpointSlice so traffic stops being routed to it, resuming automatically once the probe succeeds again. This is the correct tool for "temporarily can't serve" (e.g. a downstream database connection dropped) where restarting would be pointless or disruptive.
Startup probes: protect slow-starting apps from premature liveness kills¶
startupProbe:
httpGet:
path: /healthz
port: 8080
failureThreshold: 30
periodSeconds: 10 # up to 300s to start before liveness kicks in
livenessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 10
failureThreshold: 3
While a startupProbe is defined and hasn't yet succeeded, kubelet disables
the liveness and readiness probes entirely. This solves the classic
"JVM app takes 90s to boot but my liveness probe's initialDelaySeconds is
only 30s, so it gets killed before it ever finishes starting" problem
without having to loosen the liveness probe's steady-state timing.
Probe mechanisms¶
livenessProbe:
exec:
command: ["cat", "/tmp/healthy"]
---
livenessProbe:
tcpSocket:
port: 5432
---
livenessProbe:
grpc:
port: 9090
| Mechanism | Success condition |
|---|---|
httpGet |
Response status 200–399 |
tcpSocket |
TCP connection to the port succeeds |
exec |
Command exits 0 |
grpc |
gRPC health-checking protocol responds SERVING |
Worked example: readiness failure removes a Pod without restarting it¶
kubectl apply -f deployment.yaml # 3 replicas, readiness on /ready
kubectl get endpoints web
# 3 IPs
kubectl exec deploy/web -- rm /tmp/ready-flag # app now fails /ready
kubectl get pods -l app=web
# NAME READY STATUS RESTARTS AGE
# web-abc123 0/1 Running 0 3m <- Running, NOT restarted
kubectl get endpoints web
# only 2 IPs now -- unready Pod pulled from rotation, traffic keeps flowing to the other 2
How It Actually Works¶
- Probes run entirely from kubelet on the container's own node, on a
fixed timer — not event-driven. Each probe (liveness/readiness/startup)
is executed independently by kubelet's
probeManageratperiodSecondsintervals;httpGet/tcpSocketprobes go over the network stack to the Pod's IP (from the node, not from inside the container's netns forexec, which does run inside via the container runtime's exec API) — the API server is not in the loop for the probe itself, only for reporting the resulting status. - A failing readiness probe changes cluster state via the API server, not
locally. kubelet doesn't touch traffic routing directly; it PATCHes the
Pod's
status.conditions[Ready]toFalsevia the API server. The EndpointSlice controller, watching Pod readiness, then removes that Pod's IP from the EndpointSlice, and every node's kube-proxy (watching EndpointSlices) reprograms its iptables/IPVS rules to drop it — three separate reconciling components, chained by watches, produce the "traffic stops" effect with real (if small) end-to-end propagation delay across the cluster. - A failing liveness probe is enforced locally and immediately by kubelet, no API-server round trip needed. kubelet directly calls the container runtime (containerd/CRI-O via the CRI gRPC API) to stop and restart the specific container — this is why liveness failures act faster and more deterministically than readiness-driven traffic changes, and why a liveness restart doesn't reschedule the Pod (same node, same Pod object, just a fresh container).
failureThreshold/successThresholdadd hysteresis so a single blip doesn't flap state. kubelet tracks consecutive failures/successes per probe type in memory; a probe must fail (or, after failing, succeed)thresholdtimes in a row before kubelet acts — a single dropped TCP packet or one slow response undertimeoutSecondsdoesn't, by itself, restart anything or pull a Pod from load balancing.
Exercise¶
Add a livenessProbe and a separate readinessProbe to a Deployment,
pointed at two different endpoints on a small app you control (or httpbin,
using /status/200 and a path you can flip to /status/500). Force the
readiness endpoint to fail and confirm via kubectl get endpoints that the
Pod is pulled from the Service without a restart; then force the liveness
endpoint to fail and confirm via kubectl get pod that RESTARTS
increments instead.