The kubelet needs a workload-owned health signal before it can decide where traffic belongs or when a container has stopped recovering. Readiness and liveness probes put those decisions into the Pod template instead of leaving Kubernetes to treat a started process as automatically usable.
A readiness probe controls whether a Pod is marked Ready and whether Service routing should treat that Pod as a usable endpoint. A liveness probe is stricter: repeated liveness failure tells the kubelet to restart the container because the process is still running but no longer passing the health check.
Use different endpoints when the application has separate meanings for traffic readiness and process recovery. A slow application may also need a startupProbe so liveness checks wait until initialization finishes, but readiness and liveness should still test the conditions that matter after startup.
Related: How to create a Kubernetes Deployment
Related: How to create a Kubernetes Service
Related: How to check Kubernetes events
$ kubectl create namespace probe-demo namespace/probe-demo created
Use the target application namespace instead of probe-demo when adding probes to a real workload.
Related: How to create a Kubernetes namespace
apiVersion: apps/v1 kind: Deployment metadata: name: probe-web labels: app: probe-web spec: replicas: 1 selector: matchLabels: app: probe-web template: metadata: labels: app: probe-web spec: containers: - name: web image: python:3.12-alpine ports: - name: http containerPort: 8080 command: - /bin/sh - -c - | touch /tmp/ready /tmp/live python - <<'PY' from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path class Handler(BaseHTTPRequestHandler): def do_GET(self): checks = {"/ready": "/tmp/ready", "/live": "/tmp/live"} if self.path == "/": self.send_response(200) self.end_headers() self.wfile.write(b"app\n") return path = checks.get(self.path) if path and Path(path).exists(): self.send_response(200) self.end_headers() self.wfile.write(b"ok\n") return self.send_response(500 if path else 404) self.end_headers() self.wfile.write(b"fail\n") def log_message(self, format, *args): pass HTTPServer(("0.0.0.0", 8080), Handler).serve_forever() PY readinessProbe: httpGet: path: /ready port: http periodSeconds: 3 failureThreshold: 1 livenessProbe: httpGet: path: /live port: http initialDelaySeconds: 6 periodSeconds: 3 failureThreshold: 1 --- apiVersion: v1 kind: Service metadata: name: probe-web spec: selector: app: probe-web ports: - name: http port: 80 targetPort: http
The sample app returns HTTP 200 while /tmp/ready or /tmp/live exists and returns HTTP 500 when the matching marker is removed. Short probe intervals make the lab feedback fast; use less aggressive values for real workloads after measuring startup, dependency, and recovery behavior.
Tool: Kubernetes Deployment Generator
$ kubectl apply -f probe-web.yaml --namespace probe-demo deployment.apps/probe-web created service/probe-web created
$ kubectl rollout status deployment/probe-web --namespace probe-demo --timeout=180s deployment "probe-web" successfully rolled out
$ kubectl describe deployment probe-web --namespace probe-demo
Name: probe-web
Namespace: probe-demo
Labels: app=probe-web
Selector: app=probe-web
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
##### snipped #####
Pod Template:
Labels: app=probe-web
Containers:
web:
Image: python:3.12-alpine
Port: 8080/TCP (http)
Liveness: http-get http://:http/live delay=6s timeout=1s period=3s #success=1 #failure=1
Readiness: http-get http://:http/ready delay=0s timeout=1s period=3s #success=1 #failure=1
##### snipped #####
$ kubectl describe endpointslice --namespace probe-demo -l kubernetes.io/service-name=probe-web
Name: probe-web-s7jnp
Namespace: probe-demo
Labels: endpointslice.kubernetes.io/managed-by=endpointslice-controller.k8s.io
kubernetes.io/service-name=probe-web
AddressType: IPv4
Ports:
Name Port Protocol
---- ---- --------
http 8080 TCP
Endpoints:
- Addresses: 10.244.0.5
Conditions:
Ready: true
Hostname: <unset>
TargetRef: Pod/probe-web-884c845cd-5rxk8
NodeName: worker-1
Zone: <unset>
Events: <none>
Ready: true means the endpoint is eligible for Service routing.
$ kubectl exec deployment/probe-web --namespace probe-demo -- rm /tmp/ready
$ kubectl get pod --namespace probe-demo -l app=probe-web NAME READY STATUS RESTARTS AGE probe-web-884c845cd-5rxk8 0/1 Running 0 47s
The container is still running, but the Pod is no longer ready because the readiness probe returns HTTP 500.
$ kubectl describe endpointslice --namespace probe-demo -l kubernetes.io/service-name=probe-web
Name: probe-web-s7jnp
Namespace: probe-demo
Labels: endpointslice.kubernetes.io/managed-by=endpointslice-controller.k8s.io
kubernetes.io/service-name=probe-web
AddressType: IPv4
Ports:
Name Port Protocol
---- ---- --------
http 8080 TCP
Endpoints:
- Addresses: 10.244.0.5
Conditions:
Ready: false
Hostname: <unset>
TargetRef: Pod/probe-web-884c845cd-5rxk8
NodeName: worker-1
Zone: <unset>
Events: <none>
Ready: false removes the endpoint from normal Service traffic even though the Pod IP still exists in the EndpointSlice object.
$ kubectl exec deployment/probe-web --namespace probe-demo -- touch /tmp/ready
$ kubectl wait --for=condition=Ready pod --namespace probe-demo -l app=probe-web --timeout=60s pod/probe-web-884c845cd-5rxk8 condition met
$ kubectl exec deployment/probe-web --namespace probe-demo -- rm /tmp/live
The sample container recreates /tmp/live only when it starts, so liveness failure should restart the container instead of only changing readiness.
$ kubectl get pod --namespace probe-demo -l app=probe-web NAME READY STATUS RESTARTS AGE probe-web-884c845cd-5rxk8 1/1 Running 1 (22s ago) 2m22s
$ kubectl describe pod --namespace probe-demo -l app=probe-web
Name: probe-web-884c845cd-5rxk8
Namespace: probe-demo
Labels: app=probe-web
pod-template-hash=884c845cd
Status: Running
##### snipped #####
Containers:
web:
Image: python:3.12-alpine
Port: 8080/TCP (http)
State: Running
Last State: Terminated
Reason: Error
Exit Code: 137
Ready: True
Restart Count: 1
Liveness: http-get http://:http/live delay=6s timeout=1s period=3s #success=1 #failure=1
Readiness: http-get http://:http/ready delay=0s timeout=1s period=3s #success=1 #failure=1
##### snipped #####
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 52s kubelet spec.containers{web}: Liveness probe failed: HTTP probe failed with statuscode: 500
Normal Killing 52s kubelet spec.containers{web}: Container web failed liveness probe, will be restarted
Normal Created 22s kubelet spec.containers{web}: Container created
Normal Started 22s kubelet spec.containers{web}: Container started
Related: How to check Kubernetes events
$ kubectl delete namespace probe-demo namespace "probe-demo" deleted
Do not delete a real application namespace to clean up a probe change. Keep probe-web.yaml or the equivalent workload manifest in source control for production workloads.