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
Steps to configure Kubernetes readiness and liveness probes:
- Create a namespace for the probe test.
$ 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
- Save a Deployment and Service manifest with separate readiness and liveness paths.
- probe-web.yaml
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 - Apply the manifest to the namespace.
$ kubectl apply -f probe-web.yaml --namespace probe-demo deployment.apps/probe-web created service/probe-web created
- Wait for the Deployment rollout to finish.
$ kubectl rollout status deployment/probe-web --namespace probe-demo --timeout=180s deployment "probe-web" successfully rolled out
- Inspect the saved probe configuration.
$ 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 ##### - Check that the Service EndpointSlice is ready.
$ 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.
- Force the readiness endpoint to fail.
$ kubectl exec deployment/probe-web --namespace probe-demo -- rm /tmp/ready
- Check the Pod readiness state.
$ 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.
- Check the EndpointSlice condition again.
$ 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.
- Restore the readiness endpoint.
$ kubectl exec deployment/probe-web --namespace probe-demo -- touch /tmp/ready
- Wait for the Pod to become ready again.
$ kubectl wait --for=condition=Ready pod --namespace probe-demo -l app=probe-web --timeout=60s pod/probe-web-884c845cd-5rxk8 condition met
- Force the liveness endpoint to fail.
$ 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.
- Check that the container restarted.
$ 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
- Inspect the liveness event.
$ 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 startedRelated: How to check Kubernetes events
- Delete the lab namespace when the probe workload was only a test.
$ 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.