Kubernetes · intermediate
Kubernetes readiness probe failed: find why traffic was withheld
Kubernetes readiness probes determine whether a running pod should receive Service traffic. When a probe fails, the pod stays Running with Ready=False and is removed from a Service's endpoint set, effectively withholding traffic without indicating a crash. This guide walks through identifying which probe type failed, why it failed, and how to restore service membership with evidence-based decisions.
The symptoms
- •kubectl get pods shows the pod in Running state with READY column showing 0/1 or 0/N, and the pod's Ready condition stays False.
- •kubectl describe pod reports Readiness probe failed or Readiness probe errored repeatedly in the Events list with HTTP status, connection refused, or timeout messages.
- •Application requests to the Service return 503, connection refused, or hang, while direct pod IP requests succeed when the probe target responds correctly.
- •kubectl get endpoints shows the pod IP missing from the Service's subset list, even though the pod is Running and its container has no restart count increase.
- •Rolling updates or scale-out events appear stuck because new pods never become Ready and the workload's readyReplicas stays below replicas.
Likely causes
- •The readiness probe path or port does not match the actual application listener, so the probe request gets connection refused or HTTP 404.
- •The probe initialDelaySeconds is shorter than the application's real warm-up time, causing the container to fail probes before it finishes initializing.
- •The probe periodSeconds or timeoutSeconds are too aggressive for a slow endpoint, producing intermittent failures under normal load.
- •Dependencies the readiness endpoint checks (database, cache, downstream API) are unavailable or slow, so the endpoint reports unhealthy even though the application process itself is fine.
- •A mismatched scheme (HTTP vs HTTPS) or a TLS handshake error between kubelet and the container causes every probe to fail before the application logic is even evaluated.
- •The pod is Ready=False because of a Pod-level readiness gate (such as a Pod Readiness Gate added by a service mesh or custom controller) rather than the container probe itself.
First ten minutes
- 01Confirm the failure mode is readiness, not liveness or startup: run kubectl describe pod <pod> and read the Conditions block; Ready should be False while ContainersReady or Initialized may also be reported.
- 02Capture the exact probe failure message from Events: grep the kubectl describe pod output for "Readiness probe failed" and note the HTTP status code, latency, or connection error string reported.
- 03Verify the pod is excluded from Service traffic: run kubectl get endpoints <service> -n <namespace> and compare its addresses list to the pod IPs returned by kubectl get pods -l <selector>.
- 04Read the last 100 lines of the container logs with kubectl logs <pod> --tail=100 to see whether the application started its listener before probes began; cross-check the probe's start time against log timestamps.
- 05Reproduce the probe locally from the cluster network by using kubectl port-forward <pod> <local_port>:<probe_port> and issuing the same HTTP request the kubelet would send, to isolate whether the application or the probe definition is wrong.
Evidence to collect
- •Full output of kubectl describe pod <pod> -n <namespace>, including the Conditions array and the recent Events list.
- •The readyReplicas field of the owning Deployment, StatefulSet, or ReplicaSet compared to desired replicas, to quantify how many pods are stuck.
- •The addresses list of the target Service endpoints and the notReadyAddresses list, to confirm whether traffic is actually being withheld.
- •Container logs from the moment of first probe attempt through the most recent failure, including any startup banner that proves the listener bound to the expected port.
- •The Pod's effective readinessProbe, livenessProbe, and startupProbe definitions, including scheme, path, port, initialDelaySeconds, periodSeconds, timeoutSeconds, successThreshold, and failureThreshold.
- •If a Pod Readiness Gate is configured, the status of the corresponding condition (for example, service.beta.kubernetes.io/...) from kubectl get pod <pod> -o jsonpath='{.status.conditions}'.
Where to look
- •The kubelet-probe boundary: kubelet executes readiness probes against the pod IP and the containerPort, not against the Service virtual IP, so probe behavior is independent of kube-proxy rules.
- •The Pod spec boundary in the API server: readinessProbe is read from spec.containers[*].readinessProbe, so merged defaults from a Pod template and any higher-level controller must be inspected together.
- •The EndpointSlice or Endpoints object boundary: kube-controller-manager builds subsets from pods whose Ready condition is True, so any mismatch between pod state and Service subset points to this control loop.
- •The application listener boundary inside the container: probes target a specific port and path; verify the process binds to the expected interface and responds on the expected route.
- •The dependency boundary: if the readiness endpoint performs a downstream check, trace which external system it queries and confirm that dependency's health from inside the cluster network.
Diagnostic steps
- 01Run kubectl get pod <pod> -n <namespace> -o jsonpath='{.status.conditions}' and confirm the Ready condition status, reason, and message; Ready=False with reason ContainersNotReady indicates container probes failed.
- 02Run kubectl get pod <pod> -n <namespace> -o jsonpath='{.spec.containers[*].readinessProbe}' to capture the effective probe definition and compare each field (httpGet.path, httpGet.port, tcpSocket.port, exec.command, initialDelaySeconds, periodSeconds, timeoutSeconds, failureThreshold).
- 03Run kubectl describe pod <pod> -n <namespace> and inspect the Events list for entries of type Warning with reason Unhealthy and message starting "Readiness probe failed:"; record the error string verbatim.
- 04Run kubectl get endpoints <service> -n <namespace> -o yaml and compare subsets[*].addresses to the pod IP; absence of the pod's IP confirms traffic is withheld because Ready is False.
- 05Run kubectl exec <pod> -- ss -ltnp (or netstat -ltnp) to confirm the application process is listening on the exact port the probe targets and on the interface expected by the pod network.
- 06Run kubectl exec <pod> -- wget -qO- http://<pod-ip>:<probe_port><probe_path> (or curl) to reproduce the probe request inside the container and capture the response code and latency.
- 07If exec probes are configured, run kubectl exec <pod> -- <exec_command> to reproduce the readiness command directly and inspect stdout, stderr, and exit code, then map failureThreshold successes or failures to the readiness state.
- 08If a Pod Readiness Gate is present, run kubectl get pod <pod> -o jsonpath='{.status.conditions}' and inspect any custom condition whose type is not Ready, ContainersReady, Initialized, or PodScheduled; its status explains why Ready is False.
Common mistakes
- •Assuming a Running pod is serving traffic: Running only means the container process is alive; Ready=False removes the pod from Service subsets regardless of Running state.
- •Restarting the pod without reading the probe Events: restarts driven by liveness probes do not fix readiness failures, and kubelet will continue issuing the same failing probe against the new container.
- •Pointing the probe at the wrong port or path: a 404 from the application counts as a failure, but the Events message may not make the wrong path obvious; always cross-check the spec against the actual listener.
- •Setting initialDelaySeconds too low: a warm-up period of several seconds for JVM, Python, or Node.js startup will cause every probe to fail until the listener is ready.
- •Embedding expensive downstream checks in the readiness endpoint: if the endpoint queries a database or third-party API, that dependency's blip will pull every pod out of rotation at once.
- •Confusing startupProbe failures with readinessProbe failures: during the startup window kubelet suspends readiness and liveness checks, so a failing readiness probe only after the startup window proves is a readiness issue, not a startup issue.
Safe fixes
- •If the probe path or port is wrong, correct spec.containers[*].readinessProbe.httpGet.path or httpGet.port in the Pod template (often via the workload's Pod spec), redeploy, and confirm a new pod reports Ready=True within periodSeconds.
- •If initialDelaySeconds is shorter than observed startup time, increase initialDelaySeconds to at least the time between container start and first successful listener log line; redeploy and watch for Ready=True.
- •If timeoutSeconds is shorter than observed probe latency under normal load, raise timeoutSeconds above p99 probe latency measured from the Events timestamps.
- •If the readiness endpoint checks a flaky downstream dependency, narrow the probe to a lightweight local check (such as a /healthz that returns 200 once the listener is up) and move deep dependency checks to a separate diagnostic path.
- •If scheme mismatch or TLS handshake causes failures, align readinessProbe.httpGet.scheme with the actual listener (HTTP vs HTTPS) and confirm any required client certificates are mounted and trusted.
- •If a Pod Readiness Gate is keeping Ready=False, address the upstream controller that owns the gate (for example, a service mesh sidecar readiness signal) rather than editing kubelet probe fields.
Prove the fix
- 01After the change rolls out, run kubectl get pods -l <selector> -n <namespace> and confirm the READY column shows 1/1 for every replica, and readyReplicas on the owning workload equals replicas.
- 02Run kubectl describe pod <pod> -n <namespace> and confirm the Ready condition status is True with reason "", and that no new Warning events of reason Unhealthy mention Readiness probe failed since the redeploy.
- 03Run kubectl get endpoints <service> -n <namespace> -o yaml and confirm subsets[*].addresses contains the IP of every replica and subsets[*].notReadyAddresses is empty.
- 04Send a synthetic request to the Service DNS name on the Service port (for example via a debug pod using getent and a request tool) and observe a 2xx response from any replica, repeated across several minutes to confirm steady membership.
- 05Trigger a rolling restart of the workload and confirm each new pod reaches Ready=True within the configured initialDelaySeconds plus a small buffer, and that readyReplicas tracks replicas throughout the rollout.
Prevention and next steps
- •Define readiness probes that probe only the local listener, not external dependencies, and keep separate liveness and diagnostic endpoints to avoid coupling rotation to downstream health.
- •Use a startupProbe when cold start times are variable, and set its failureThreshold generously so readiness and liveness probes only activate after the application is truly ready.
- •Capture baseline probe latency in non-production environments and set timeoutSeconds and periodSeconds above observed p99, so normal request variance does not produce false failures.
- •Alert on the difference between desired replicas and readyReplicas, and on the count of pods with Ready=False for more than a configurable threshold, so readiness withholding is detected before users are affected.
Safe commands and checks
kubectl describe pod <pod> -n <namespace>
kubectl get pod <pod> -n <namespace> -o jsonpath='{.status.conditions}'
kubectl get pod <pod> -n <namespace> -o jsonpath='{.spec.containers[*].readinessProbe}'
kubectl get endpoints <service> -n <namespace> -o yaml
kubectl get pods -l <selector> -n <namespace> -o wide
kubectl logs <pod> -n <namespace> --tail=100 --timestamps=true
kubectl exec <pod> -n <namespace> -- ss -ltnp
kubectl exec <pod> -n <namespace> -- wget -qO- http://<pod-ip>:<probe_port><probe_path>