Scenario-Based Production Incident Reviews
Realistic production post-mortems and triage walkthroughs covering multi-layered Kubernetes failure modes.
Scenario 1: Deployment Has 3 Replicas but 0 Available Pods
Symptom & Alert:
Prometheus triggers KubeDeploymentReplicasMismatch:
Deployment "checkout-service" has 3 desired replicas, 3 updated replicas, but 0 available replicas.
Step-by-Step Triage:
-
Inspect Deployment status:
kubectl get deployment checkout-serviceOutput:
READY 0/3, UP-TO-DATE 3, AVAILABLE 0 -
Inspect individual Pod status:
kubectl get pods -l app=checkout-serviceOutput:
NAME READY STATUS RESTARTS AGE checkout-service-78f898c6d4-5m2ks 0/1 Running 0 4m checkout-service-78f898c6d4-8d4np 0/1 Running 0 4m checkout-service-78f898c6d4-p9l2s 0/1 Running 0 4mNotice that the Pods are
Running, but0/1containers areREADY. -
Check granular conditions on one Pod:
kubectl describe pod checkout-service-78f898c6d4-5m2ks | grep -A 6 "Conditions:"Output:
Type Status Initialized True Ready False <-- NOT READY ContainersReady False PodScheduled True -
Inspect events for Readiness Probe failures:
kubectl describe pod checkout-service-78f898c6d4-5m2ks | grep -A 5 "Events:"Output:
Warning Unhealthy 12s (x8 over 3m) kubelet Readiness probe failed: HTTP probe failed with statuscode: 503 -
Read application logs:
kubectl logs checkout-service-78f898c6d4-5m2ksOutput:
Database connection pool initialization timed out: postgres-master.db.svc:5432 unreachable.
Root Cause:
The application containers booted successfully, but failed their readiness probes because their downstream database dependency was unreachable. Kubelet correctly withheld the Ready condition, preventing traffic from reaching unready containers.
Remediation:
- Fix the downstream database connectivity.
- In the deployment manifest, configure
startupProbewith an appropriatefailureThresholdto give cold-start initialization ample time before readiness probes begin failing.
Scenario 2: Service Resolves in DNS but TCP Connections Time Out
Symptom & Alert:
Client microservices report connection timed out after 30 seconds when calling http://inventory.production.svc.cluster.local:8080.
Step-by-Step Triage:
-
Verify DNS resolution from a debug pod:
kubectl run test-dns --image=busybox:1.36 --rm -it --restart=Never -- nslookup inventory.production.svc.cluster.localResult: Resolves successfully to
10.96.45.120. CoreDNS is healthy. -
Verify Service Endpoints:
kubectl get endpointslices -l kubernetes.io/service-name=inventoryResult:
NAME ADDRESSTYPE PORTS ENDPOINTS AGE inventory-xk89d IPv4 8080 <unset> 12mThe Service has ZERO endpoints!
-
Compare Service Selector with Pod Labels:
# Check service selector kubectl get svc inventory -o jsonpath='{.spec.selector}' # Output: {"app":"inventory","tier":"backend"} # Check actual pod labels kubectl get pods -l app=inventory --show-labels # Output: app=inventory,tier=api
Root Cause:
A label mismatch (tier: backend vs tier: api). The EndpointSlice controller found 0 pods matching all selector labels. When clients sent SYN packets to the Service VIP, no DNAT rules existed in the node’s nftables tables, causing packets to be silently dropped.
Remediation:
Patch the Service’s selector to match the actual Pod labels:
kubectl patch svc inventory -p '{"spec":{"selector":{"tier":"api"}}}'Endpoints appear immediately, and TCP connections succeed.
Scenario 3: HPA Demands 20 Replicas, but Pods Remain Stuck in Pending
Symptom & Alert:
During a flash-sale event, HPA scales up the order-processing deployment from 4 to 20 replicas. However, order processing throughput does not increase, and 16 new pods remain stuck in Pending.
Step-by-Step Triage:
-
Inspect the pending pods:
kubectl get pods -l app=order-processor | grep Pending -
Inspect scheduler events on one pending pod:
kubectl describe pod order-processor-65f8d9b4c7-w8s9k | grep -A 5 "Events:"Output:
Warning FailedScheduling 45s default-scheduler 0/6 nodes are available: 6 Insufficient cpu. preemption: 0/6 nodes are available: 6 No preemption victims found for incoming pod. -
Check node allocatable CPU:
kubectl describe nodes | grep -A 4 "Allocated resources:"Every worker node has 95% CPU requests committed. The cluster has exhausted its compute capacity.
-
Check why node autoscaler did not launch new nodes:
kubectl describe deployment cluster-autoscaler -n kube-system kubectl logs -n kube-system deployment/cluster-autoscaler --tail=50Output:
MaxNodesTotal (6) reached, cannot scale out cluster.
Root Cause:
The cluster hit the hard capacity ceiling (maxNodes: 6) defined in the Cluster Autoscaler configuration. Even though cloud capacity was available, the autoscaler refused to provision additional instances.
Remediation:
- Increase the maximum node group limit in the autoscaler configuration.
- Consider migrating to Karpenter, which provisions right-sized compute dynamically without rigid node-group size boundaries.
Scenario 4: kubectl drain on a Worker Node Hangs Indefinitely
Symptom:
During a scheduled cluster node OS upgrade, an engineer executes:
kubectl drain node-worker-3 --ignore-daemonsets --delete-emptydir-dataThe command blocks indefinitely, reporting:
evicting pod default/payment-gateway-69d8b7-4m8s9
error when evicting pod "payment-gateway-69d8b7-4m8s9": Cannot evict pod as it would violate the pod's disruption budget.
Step-by-Step Triage:
-
Inspect the PodDisruptionBudget for that workload:
kubectl get pdb -AOutput:
NAMESPACE NAME MIN AVAILABLE ALLOWED DISRUPTIONS CURRENT default payment-gateway-pdb 2 0 2 -
Check payment-gateway pod replica distribution:
kubectl get pods -l app=payment-gateway -o wideOutput:
NAME READY STATUS NODE payment-gateway-69d8b7-4m8s9 1/1 Running node-worker-3 payment-gateway-69d8b7-v8k2p 0/1 CrashLoop node-worker-1
Root Cause:
The PDB requires minAvailable: 2. There are only 2 replicas, and one of them is crashing (0/1 Ready), so 1 healthy Pod exists and the budget is already violated. The Eviction API refuses to remove the remaining healthy Pod. Note that the budget is misconfigured even when both Pods are healthy: 2 replicas with minAvailable: 2 never allows an eviction.
Remediation:
- Fix the crashing pod first: Check
kubectl logs payment-gateway-69d8b7-v8k2p --previousand resolve the crash. That brings the healthy count back to 2, butALLOWED DISRUPTIONSis healthy Pods minusminAvailable, which is 2 − 2 = 0. The drain is still blocked. - Make room in the budget. With two replicas and
minAvailable: 2, no voluntary eviction is ever permitted. Either:- scale up and wait for readiness (
kubectl scale deployment payment-gateway --replicas=3, thenkubectl rollout status), which gives 3 − 2 = 1 allowed disruption and keeps two Pods serving throughout; or - if the service can run on one Pod for a short time, lower the budget (
kubectl patch pdb payment-gateway-pdb --type merge -p '{"spec":{"minAvailable":1}}').
- scale up and wait for readiness (
- Confirm
kubectl get pdb payment-gateway-pdbshowsALLOWED DISRUPTIONSof at least 1, then re-runkubectl drain. The replacement must be able to schedule on another node, or the allowance drops back to 0 after the first eviction.
The lasting fix is a budget that leaves room, such as maxUnavailable: 1, on a workload with more replicas than the budget protects. Lab CKA-02 reproduces a blocked drain end to end.
Scenario 5: Validating Webhook Outage Blocks All Deployments
Symptom:
Every engineer trying to deploy or update workloads receives an API error:
Error from server (InternalError): Internal error occurred: failed calling webhook "validate.security.company.com":
Post "https://security-validator.security.svc:443/validate?timeout=10s": context deadline exceeded
Step-by-Step Triage:
-
Identify the failing webhook configuration:
kubectl get validatingwebhookconfigurations kubectl describe validatingwebhookconfiguration security-validator -
Inspect the failurePolicy on the webhook:
kubectl get validatingwebhookconfiguration security-validator -o jsonpath='{.webhooks[*].failurePolicy}'Output:
Fail. The webhook is configured to fail closed. -
Check the webhook backend pods:
kubectl get pods -n security -l app=security-validatorOutput:
security-validator-xxxx-yyyy 0/1 OOMKilled. The validator pod crashed and is failing to restart.
Root Cause:
A third-party validating admission webhook was configured with failurePolicy: Fail. When the webhook pod crashed, kube-apiserver could not evaluate admission requests and refused all resource mutations cluster-wide.
Emergency Remediation & Architecture Fix:
- Emergency unblock: Temporarily switch the webhook to fail-open while debugging:
kubectl patch validatingwebhookconfiguration security-validator \ --type='json' -p='[{"op":"replace","path":"/webhooks/0/failurePolicy","value":"Ignore"}]' - Architecture Prevention:
- Migrate simple validation rules from dynamic webhooks to ValidatingAdmissionPolicy (CEL), which evaluates natively inside the API server process with zero network dependencies.
- For webhooks that must fail-closed, run them with high availability (≥3 replicas, anti-affinity, and a PDB).
Next Steps
Review the foundational curriculum in Curriculum Hub or practice disaster recovery procedures in Lab CKAD-07 — GitOps Delivery & Lifecycle.