🚨 First-Responder Triage Framework
When an alert fires, follow a structured funnel: Scope → Locate → Diagnose → Mitigate → Fix → Post-mortem. Resist the urge to make changes without first understanding the blast radius.
Universal First Commands
# Cluster-wide health snapshot — always start here
kubectl get nodes -o wide
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded
kubectl get events -A --sort-by='.lastTimestamp' | tail -40
# Check component health
kubectl get componentstatuses # deprecated but still useful
kubectl -n kube-system get pods | grep -v Running
# Quick node resource view
kubectl top nodes
kubectl top pods -A --sort-by=memory | head -20
# Recent events for a namespace
kubectl get events -n my-app --sort-by='.lastTimestamp'
# Who changed what recently? (audit log shortcut)
kubectl get events -A --sort-by='.lastTimestamp' \
| grep -i "Warning\|Error\|Failed\|BackOff"
🐛 Pod & Node Failure Runbooks
🔴 Pod CrashLoopBackOff
- Check restart count and exit code:
kubectl describe pod <name> | grep -A5 "Last State" - Read current logs:
kubectl logs <pod> --tail=100 - Read previous container logs:
kubectl logs <pod> --previous --tail=100 - Check events:
kubectl get events --field-selector involvedObject.name=<pod> - Exit code 1 → application error; exit code 137 → OOMKilled; exit code 139 → segfault
- Override command to debug: patch with
command: ["sleep","infinity"], then exec in
🧠 Pod OOMKilled (exit code 137)
- Confirm:
kubectl describe pod <name> | grep -i "OOMKilled" - Check actual usage vs limit in Prometheus:
container_memory_working_set_bytes - Get current limits:
kubectl get pod <name> -o jsonpath='{.spec.containers[*].resources}' - If limit too low → increase
resources.limits.memory - If app leaks memory → profile + fix app; set
requests == limitsfor Guaranteed QoS - Check namespace LimitRange:
kubectl get limitrange -n <ns>
⏳ Pod Stuck Pending
- Check events:
kubectl describe pod <name> | grep -A20 Events - "Insufficient CPU/memory" → no capacity; scale cluster or reduce requests
- "No nodes match selector" → fix nodeSelector or label a node
- "Unschedulable: taint" → add toleration or remove taint
- "PVC not bound" →
kubectl get pvc -n <ns>; check StorageClass - Check scheduler is running:
kubectl -n kube-system get pods | grep scheduler
⚠️ Node NotReady
- Check conditions:
kubectl describe node <name> | grep -A20 Conditions - SSH and check kubelet:
systemctl status kubelet - Kubelet logs:
journalctl -u kubelet -n 100 --no-pager - Check disk full:
df -h→ prune images:crictl rmi --prune - Check containerd:
systemctl status containerd; restart if crashed - If unrecoverable:
kubectl cordon <node> && kubectl drain <node> --ignore-daemonsets
Pod Debugging Toolkit
# Ephemeral debug container (K8s 1.23+)
kubectl debug -it <pod> --image=nicolaka/netshoot --target=<container>
# Copy crashing pod with a shell override
kubectl debug <pod> -it --copy-to=debug-pod \
--image=<same-image> --container=<container> -- /bin/sh
# Real-time resource usage
kubectl top pod <name> --containers
# Port-forward to test directly
kubectl port-forward pod/<name> 8080:8080
# Check env vars in a pod
kubectl exec <pod> -- env | sort
🏛️ Control-Plane & Networking Incidents
🏛️ API Server Unavailable / kubectl Timeout
- Check if LB is healthy:
curl -k https://<LB-VIP>:6443/healthz - Check each apiserver pod directly:
curl -k https://<CP-IP>:6443/healthz - Check control-plane pods:
ssh cp-1 "crictl ps | grep kube-api" - Check apiserver logs:
ssh cp-1 "crictl logs <apiserver-container-id> 2>&1 | tail -50" - Check etcd health:
etcdctl endpoint health --cluster ... - If etcd is unhealthy → etcd quorum lost; restore from snapshot (lesson 83)
- If apiserver is up but unresponsive → check
etcd_request_duration_seconds; etcd may be overloaded - Mitigation: if one CP node is bad, remove it from LB target group temporarily
🌐 Service DNS Resolution Failing
- Test from a pod:
kubectl run dns-test --image=busybox --rm -it -- nslookup kubernetes - Check CoreDNS pods:
kubectl -n kube-system get pods -l k8s-app=kube-dns - Check CoreDNS logs:
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50 - Check CoreDNS config:
kubectl -n kube-system get configmap coredns -o yaml - Test ClusterIP directly to bypass DNS:
curl http://<ClusterIP>:<port> - If ClusterIP works but DNS fails → CoreDNS issue; restart CoreDNS pods
- Check kube-dns Service:
kubectl -n kube-system get svc kube-dns— IP must match cluster DNS IP - Check node's resolv.conf:
cat /etc/resolv.confon failing pod's node
🔌 Service Not Reachable (ClusterIP)
- Check endpoints are populated:
kubectl get endpoints <svc-name> -n <ns> - If endpoints are empty → pods are not Ready; check readiness probe failures
- Check label selector matches pod labels:
kubectl get svc <name> -o yaml | grep selector - Check pod labels:
kubectl get pods -n <ns> --show-labels - Verify kube-proxy is running:
kubectl -n kube-system get pods -l k8s-app=kube-proxy - Check iptables rules exist:
iptables -t nat -L KUBE-SERVICES -n | grep <ClusterIP> - Check NetworkPolicy is not blocking:
kubectl get networkpolicy -n <ns>
Network Connectivity Test Matrix
# Run from a debug pod in the affected namespace
kubectl run netshoot --image=nicolaka/netshoot --rm -it -- bash
# Pod → Service (DNS)
curl -v http://my-svc.my-ns.svc.cluster.local
# Pod → Pod direct
curl -v http://10.0.2.11:8080
# Pod → External
curl -v https://api.example.com
# Check traceroute
traceroute 10.96.0.1 # ClusterIP of kubernetes service
# Check conntrack entries
conntrack -L | grep 10.96.0.10
# Test NodePort from outside
curl -v http://<node-IP>:<nodePort>
💾 Storage & Performance Incidents
💾 PVC Stuck in Pending
- Check PVC status:
kubectl describe pvc <name> -n <ns> - "no persistent volumes available" → no matching PV; check StorageClass provisioner
- Check StorageClass:
kubectl get sc— is the right one the default? - Check CSI provisioner pods:
kubectl -n kube-system get pods | grep csi - Check CSI provisioner logs for the failing PVC name
- "waiting for first consumer" →
volumeBindingMode: WaitForFirstConsumer; PVC binds only when pod is scheduled — this is normal - Check VolumeAttachment if PV exists but won't mount:
kubectl get volumeattachments
📈 High Latency / Performance Degradation
- Check if the issue is cluster-wide or isolated:
kubectl top nodes - Identify CPU/memory hot spots:
kubectl top pods -A --sort-by=cpu | head -20 - Check for CPU throttling: Prometheus
container_cpu_cfs_throttled_seconds_total - Check HPA status:
kubectl get hpa -A— is it failing to scale? - Check Cluster Autoscaler:
kubectl -n kube-system logs deployment/cluster-autoscaler | tail -30 - Check API server latency: Prometheus
apiserver_request_duration_secondsp99 - Check etcd latency:
etcd_disk_wal_fsync_duration_secondsp99 > 10ms is a red flag - Check for noisy neighbours: identify pods without requests/limits:
kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[].resources.requests == null) | .metadata.name'
Quick Incident Reference
| Symptom | First check | Likely cause |
|---|---|---|
| Pod CrashLoopBackOff | kubectl logs --previous | App error, OOMKill, bad config |
| Pod Pending forever | kubectl describe pod events | Insufficient resources, taint, PVC unbound |
| Node NotReady | journalctl -u kubelet | Disk full, containerd crash, network partition |
| DNS resolution fails | kubectl logs on CoreDNS pods | CoreDNS crash, wrong configmap, networkpolicy |
| Service not reachable | kubectl get endpoints | No ready pods, label mismatch, kube-proxy down |
| PVC stuck Pending | kubectl describe pvc | No matching PV, CSI driver error, wrong SC |
| API server slow | etcd latency metrics | etcd overloaded, disk pressure, leader election |
| kubectl connection refused | curl -k https://<LB>:6443/healthz | LB down, all apiservers down |
| High pod restart rate | Liveness probe events | Probe misconfigured, app health endpoint broken |
| Pods not evicted from dead node | Node taint + pod tolerations | tolerationSeconds too high, node not tainted yet |
📝 Knowledge Check
Q1. A pod has status
OOMKilled and keeps restarting. kubectl describe pod shows exit code 137. What is the immediate fix and longer-term solution?✅ B) Increase limit; then profile. Exit code 137 = SIGKILL from the Linux OOM killer, triggered when the container exceeds its
limits.memory. Immediate fix: increase the limit. Long-term: profile with memory analysis tools to find leaks. Removing limits entirely (C) is dangerous — it makes the pod BestEffort QoS and risks killing other pods on the node.Q2.
kubectl get endpoints my-svc returns <none>. Pods for the service exist and are Running. What is the most likely cause?✅ B) Label selector mismatch. The EndpointSlice controller builds endpoints from pods whose labels match the Service's
spec.selector. If pods are Running but endpoints are empty, the selector doesn't match. Compare kubectl get svc my-svc -o yaml | grep -A5 selector with kubectl get pods --show-labels.Q3. All
kubectl commands return "connection refused" but you can SSH to all control-plane nodes and the apiserver pods are Running. What should you check first?✅ B) Check the load balancer. kubectl connects to the LB VIP/DNS (from kubeconfig). If apiserver pods are Running but kubectl gets "connection refused", the LB is the broken component — health checks may have marked all backends unhealthy, or the LB itself crashed. Test by curling the LB endpoint directly:
curl -k https://<LB-VIP>:6443/healthz.