Click any failure pattern below to jump directly to its diagnostic runbook and remediation commands:
| Failure Pattern | Severity | Common Root Cause |
|---|---|---|
| 1. CrashLoopBackOff & Container Termination | P1 / Critical | OOMKilled (exit code 137), failed liveness probe, missing ConfigMap |
| 2. ImagePullBackOff & ErrImagePull | P2 / High | Registry rate limits, missing imagePullSecrets, image tag typo |
| 3. Node NotReady & Kubelet Heartbeat Failures | P1 / Critical | Containerd hang, disk/inode pressure, kernel OOM deadlock |
| 4. etcd Performance Degradation & Latency Spikes | P1 / Critical | Disk fsync latency >10ms, leader election timeouts, DB fragmentation |
| 5. Kube-Apiserver Unreachable / 503 Errors | P1 / Critical | Mutating webhook deadlocks, etcd saturation, API priority queue exhaustion |
| 6. PVC / StorageClass Stuck in Pending | P2 / High | Multi-AZ EBS volume mismatch, missing CSI node driver, VolumeAttachment lock |
| 7. Karpenter / Autoscaler Node Provisioning Failures | P2 / High | Insufficient cloud instance capacity (ICE), IAM instance profile mismatch |
| 8. ArgoCD Out-of-Sync & Infinite Sync Loops | P3 / Medium | Mutating admission webhook modifying fields, CRD schema drift |
| 9. Network Policy Lockouts & CoreDNS Timeouts | P1 / Critical | Default-deny ingress/egress policy locking out kube-dns UDP port 53 |
| 10. ResourceQuota Exceeded & Scheduling Deadlocks | P2 / High | Namespace CPU/Memory requests sum exceeded, pods without limits rejected |
| 11. HPA Not Scaling & Metrics Server Failures | P2 / High | Missing metrics-server, container missing resources.requests.cpu |
| 12. Control Plane TLS Certificate Expiration | P1 / Critical | 1-year kubeadm root/peer certificates expired, kubelet client cert rejected |
CrashLoopBackOff indicates that a pod's container successfully passes scheduling and image pulling but terminates repeatedly upon process execution. Kubernetes enforces exponential backoff delays (10s, 20s, 40s up to 300s) between restart attempts.
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{range .status.containerStatuses[*]}{.name}{"\tExitCode:"}{.state.waiting.message}{.lastState.terminated.exitCode}{"\tReason:"}{.lastState.terminated.reason}{"\n"}{end}'
kubectl logs <pod-name> -n <namespace> --previous --tail=100
# Run on the host node or via ephemeral debug container:
dmesg -T | grep -E -i "oom_reaper|killed process|out of memory"
SIGKILL (128 + 9). In 95% of cases, this is an OOMKilled trigger from the Linux kernel cgroup controller when memory limits are exceeded.SIGTERM (128 + 15). Kubelet gracefully terminated the pod, often due to a failed liveness probe or preemption.If exit code is 137, increase memory limits in the deployment manifest or profile application heap allocation. If exit code is 143 due to probe failure, adjust initialDelaySeconds and failureThreshold to allow slow application bootstrap:
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 45 # Increase from default 0-10s
periodSeconds: 10
failureThreshold: 5 # Allow up to 5 consecutive failures before killing
ImagePullBackOff occurs when kubelet cannot pull the specified container image from the container registry (Docker Hub, AWS ECR, GitHub Packages) onto the target worker node.
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Events:"
imagePullSecrets. For AWS ECR, the IAM node role lacks ecr:GetAuthorizationToken and ecr:BatchGetImage.arm64 image on an amd64 node).# Create registry secret and patch default service account:
kubectl create secret docker-registry regcred \
--docker-server=<your-registry-server> \
--docker-username=<your-user> \
--docker-password=<your-token> \
-n <namespace>
# Add to pod deployment:
spec:
imagePullSecrets:
- name: regcred
NotReady when the master control plane has not received a node lease update from the kubelet service within the node-monitor-grace-period (default 40s).
# Check node condition reasons:
kubectl describe node <node-name> | grep -A 8 "Conditions:"
# SSH into the node and inspect kubelet logs:
journalctl -u kubelet -n 150 --no-pager
# Check containerd / runtime daemon status:
systemctl status containerd
If containerd has hung on zombie shim processes:
sudo systemctl restart containerd
sudo systemctl restart kubelet
If the node is under DiskPressure (filesystem >85% capacity):
# Clean dangling images and stopped containers:
crictl rmi --prune
df -h /var/lib/containerd /var/lib/kubelet
# Check etcd member health and cluster alarms:
etcdctl endpoint health --write-out=table
etcdctl alarm list
# Check disk fsync latency warnings in logs:
journalctl -u etcd | grep -E "took too long|lost leader|server is likely overloaded"
Defragment the etcd database and disarm storage quota alarms:
# Defragment all endpoints:
etcdctl defrag --cluster
# Disarm alarms once defragmentation frees space:
etcdctl alarm disarm
# Identify misconfigured validating or mutating webhooks:
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
# Check apiserver container logs:
kubectl logs -n kube-system -l component=kube-apiserver --tail=100 | grep -E "webhook|timeout|503"
If a third-party webhook (e.g., Istio, Cert-Manager, or policy agent) is unreachable and configured with failurePolicy: Fail, it blocks all cluster admissions. Change policy to Ignore in an emergency:
kubectl patch validatingwebhookconfiguration <webhook-name> \
--type='json' -p='[{"op": "replace", "path": "/webhooks/0/failurePolicy", "value": "Ignore"}]'
Pending when the CSI provisioner cannot create an underlying block device or when volume topology constraints (e.g. AWS Availability Zone mismatch) prevent pod scheduling.
kubectl describe pvc <pvc-name> -n <namespace>
kubectl get events -n <namespace> --field-selector reason=FailedBinding
Ensure your StorageClass uses volumeBindingMode: WaitForFirstConsumer. This prevents the cloud provider from creating an EBS/Azure disk in us-east-1a when the pod is scheduled on a worker node in us-east-1b:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer # Critical for multi-AZ clusters
# Check Karpenter controller logs:
kubectl logs -n karpenter -l app.kubernetes.io/name=karpenter --tail=100 | grep -E "ERROR|failed|capacity"
# Inspect unschedulable pod requirements:
kubectl get pods -A --field-selector status.phase=Pending
Broaden your EC2NodeClass / NodePool instance types. Avoid pinning to a single instance family (e.g. m5.large). Allow Karpenter to select across multiple generation families and availability zones:
requirements:
- key: "karpenter.k8s.aws/instance-category"
operator: In
values: ["c", "m", "r"]
- key: "karpenter.k8s.aws/instance-generation"
operator: Gt
values: ["4"]
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot", "on-demand"]
argocd app diff <app-name> --hard-refresh
argocd app get <app-name> --show-operation-details
Add an ignoreDifferences block in your ArgoCD Application manifest to ignore dynamic fields populated by cloud controllers:
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/template/spec/containers/0/imagePullPolicy
# Test DNS resolution from inside a debug pod:
kubectl run dns-test --rm -it --image=busybox:1.28 -- nslookup kubernetes.default
# Check CoreDNS pod status and logs:
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
Ensure any default-deny egress NetworkPolicy includes an explicit rule permitting traffic to CoreDNS in the kube-system namespace:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
ResourceQuota rejection occurs when an attempted deployment requests more aggregate CPU, memory, or object counts than permitted by the namespace quota.
kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>
Identify pods consuming quota without active workloads, right-size requests, or increase the namespace quota specification via Infrastructure as Code.
<unknown>/50% target utilization when metrics-server is not installed or when target containers do not declare resources.requests.cpu.
kubectl describe hpa <hpa-name> -n <namespace>
kubectl top pods -n <namespace>
HPA cannot calculate percentage utilization without a baseline request. Always declare container CPU requests:
resources:
requests:
cpu: 250m
memory: 256Mi
kubeadm certs check-expiration
Renew all control plane certificates with zero downtime:
kubeadm certs renew all
# Restart static control plane pods:
sudo kill -s SIGHUP $(pidof kube-apiserver kube-controller-manager kube-scheduler)
I advise and engineer cloud infrastructure for high-growth tech companies across the UK, Europe, GCC, and India. Schedule a complimentary 30-minute architecture review to discuss cluster stability, GitOps, or cloud cost reduction.
Explore Kubernetes Consulting Practice →Always triage at the node layer first. A single Node NotReady or disk pressure event can cause dozens of cascading pod CrashLoopBackOff and probe failures. Restoring node stability typically self-heals healthy workload replicas.
A liveness probe determines if the container process must be restarted by kubelet. A readiness probe determines if the pod should receive network traffic from the Service endpoint. Never use a liveness probe to check external database dependencies—use readiness probes to avoid death-spiral cascade restarts.