How to configure Kubernetes readiness and liveness probes

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.

Steps to configure Kubernetes readiness and liveness probes:

  1. 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.

  2. 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

  3. Apply the manifest to the namespace.
    $ kubectl apply -f probe-web.yaml --namespace probe-demo
    deployment.apps/probe-web created
    service/probe-web created
  4. Wait for the Deployment rollout to finish.
    $ kubectl rollout status deployment/probe-web --namespace probe-demo --timeout=180s
    deployment "probe-web" successfully rolled out
  5. 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 #####
  6. 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.

  7. Force the readiness endpoint to fail.
    $ kubectl exec deployment/probe-web --namespace probe-demo -- rm /tmp/ready
  8. 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.

  9. 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.

  10. Restore the readiness endpoint.
    $ kubectl exec deployment/probe-web --namespace probe-demo -- touch /tmp/ready
  11. 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
  12. 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.

  13. 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
  14. 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 started
  15. 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.