Health Probes & Self-Healing
Without Probes — The Silent Failure
Availability Is Part of the CIA Triad
CIA stands for Confidentiality, Integrity, Availability. Most DevSecOps content focuses on C and I. Availability is often forgotten — until an incident makes it obvious.
Without health probes, Kubernetes has no way to know whether your application is actually working. It only knows whether the container process is running. A container can be running while:
- Still initialising (not yet ready to serve traffic)
- In a crash loop (dying and restarting repeatedly)
- Deadlocked (process running, but not responding)
- Out of memory (responding slowly, dropping requests)
In all four cases, Kubernetes will happily route traffic to the broken pod.
The Three Probes
| Probe | Purpose | What happens on failure |
|---|---|---|
| Startup | "Is the app still starting?" | Container is not killed during slow startup |
| Liveness | "Is the app still healthy?" | Container is restarted |
| Readiness | "Is the app ready for traffic?" | Pod removed from Service endpoints |
Create the Demo Namespace
kubectl create namespace probes-demo
Deploy Without Probes
cat > no-probe-app.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: no-probe-app
namespace: probes-demo
spec:
replicas: 2
selector:
matchLabels:
app: no-probe-app
template:
metadata:
labels:
app: no-probe-app
spec:
containers:
- name: app
image: public.ecr.aws/nginx/nginx:1.25-alpine
resources:
requests: {cpu: "100m", memory: "64Mi"}
limits: {cpu: "200m", memory: "128Mi"}
ports:
- containerPort: 80
EOF
kubectl apply -f no-probe-app.yaml
kubectl rollout status deployment/no-probe-app -n probes-demo --timeout=60s
Demonstrate the Problem
POD=$(kubectl get pods -n probes-demo -l app=no-probe-app \
-o jsonpath='{.items[0].metadata.name}')
# Kill the nginx process inside the container
kubectl exec -n probes-demo "$POD" -- nginx -s stop
# Watch: Kubernetes sees the process die and restarts the container
kubectl get pod "$POD" -n probes-demo -w
Expected: RESTARTS column increments. Without a liveness probe, Kubernetes only restarts the container when the process exits — not when it is hanging or deadlocked.
The broader problem is readiness: for the duration between the process dying and the restart completing (several seconds), traffic is still routed to this pod, causing errors for users.
Adding Probes — Self-Healing with Precision
The Probed Deployment
cat > probe-app.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: probe-app
namespace: probes-demo
spec:
replicas: 2
selector:
matchLabels:
app: probe-app
template:
metadata:
labels:
app: probe-app
spec:
containers:
- name: app
image: public.ecr.aws/nginx/nginx:1.25-alpine
resources:
requests: {cpu: "100m", memory: "64Mi"}
limits: {cpu: "200m", memory: "128Mi"}
ports:
- containerPort: 80
startupProbe:
httpGet:
path: /
port: 80
failureThreshold: 30
periodSeconds: 3
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
failureThreshold: 2
timeoutSeconds: 3
EOF
kubectl apply -f probe-app.yaml
kubectl rollout status deployment/probe-app -n probes-demo --timeout=90s
Understanding Each Probe Setting
startupProbe:
failureThreshold: 30 # Give the app 30 × 3s = 90s to start
periodSeconds: 3 # Check every 3 seconds
livenessProbe:
initialDelaySeconds: 5 # Wait 5s before first check (startup complete)
periodSeconds: 10 # Check every 10s
failureThreshold: 3 # Restart after 3 consecutive failures = 30s
readinessProbe:
periodSeconds: 5 # Check every 5s
failureThreshold: 2 # Remove from Service after 2 failures = 10s
The startup probe prevents liveness from killing a slow-starting container (a common footgun: liveness kills the app before it finishes initialising, causing a restart loop).
Watch Self-Healing in Action
cat > probe-svc.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: probe-app
namespace: probes-demo
spec:
selector:
app: probe-app
ports:
- port: 80
targetPort: 80
EOF
kubectl apply -f probe-svc.yaml
POD=$(kubectl get pods -n probes-demo -l app=probe-app \
-o jsonpath='{.items[0].metadata.name}')
# Watch pods in background
kubectl get pods -n probes-demo -w &
# Watch endpoints in background
kubectl get endpoints probe-app -n probes-demo -w &
# Kill the nginx process to trigger probes
kubectl exec -n probes-demo "$POD" -- nginx -s stop
What you will see:
- Pod's
READYdrops from1/1to0/1(readiness probe fails) - Pod's IP disappears from the
Endpointslist — traffic stops going to it - After 3 liveness failures, pod restarts
- Pod passes readiness checks and re-joins the Endpoints
- Traffic resumes to all pods
Compare this to the no-probe-app: without readiness, the Service would have continued routing traffic to the broken pod throughout the restart cycle.
Security Implications
| Probe | Security Benefit |
|---|---|
| Readiness | Prevents traffic to pods under load (a pod being exploited may respond slowly — readiness gates it out) |
| Liveness | Limits attacker dwell time: a crashed process gets restarted quickly, limiting how long an attacker maintains a foothold |
| Startup | Prevents restart loops that create a window where a half-initialised app with partial security controls is reachable |
Probe Best Practices
DO: Use a dedicated /healthz endpoint that checks internal state
DO: Set timeoutSeconds to prevent probe pileup under load
DO: Use startupProbe for slow-starting apps
DO NOT: Use liveness on anything that fails under load (causes cascading restarts)
DO NOT: Make liveness probe call external dependencies (DB down ≠ restart the app)
Cleanup
kubectl delete namespace probes-demo
- Previous lesson
- Rolling Updates — Zero-Downtime CVE Patching
- Next lesson
- ConfigMaps & Secrets — The Right and Wrong Way