Kubernetes Interview Questions & Answers — Scenarios & Troubleshooting
Real-world Kubernetes troubleshooting scenarios covering CrashLoopBackOff, Pending pods, node issues, connectivity failures, performance incidents, and production debugging walkthroughs.
Ans:
# Step 1: Check pod status and events
kubectl describe pod <pod-name> -n <namespace>
# Look at: Events section, Last State, Exit Code
# Step 2: Check logs (current and previous container)
kubectl logs <pod-name>
kubectl logs <pod-name> --previous # Logs from crashed container
# Step 3: Check exit code
# Exit 0 = success (but CMD finished unexpectedly)
# Exit 1 = application error
# Exit 137 = OOMKilled
# Exit 139 = segfault
# Step 4: Get into the container temporarily
kubectl exec -it <pod-name> -- sh
# Step 5: If container exits too fast to exec, override CMD
kubectl run debug --image=myimage --command -- sleep 3600
kubectl exec -it debug -- sh
# Step 6: Check resource limits
kubectl describe pod <pod-name> | grep -A5 Limits
# Step 7: Check liveness probe config
kubectl describe pod <pod-name> | grep -A10 Liveness
# Common causes:
# - Wrong command/entrypoint
# - Missing env vars or config
# - OOMKilled (need more memory)
# - App crashes on startup
# - Liveness probe too aggressive
Ans:
# Step 1: Verify service exists and has correct port
kubectl get service my-service
kubectl describe service my-service
# Step 2: Check endpoints (are pods registered?)
kubectl get endpoints my-service
# If NO endpoints → selector mismatch or pods not Running
# Step 3: Verify pod labels match service selector
kubectl get pods --show-labels
kubectl describe service my-service | grep Selector
# Both must match!
# Step 4: Test from within the cluster (using a debug pod)
kubectl run test --image=busybox --rm -it -- sh
wget -O- http://my-service:80
nslookup my-service
# Step 5: Test DNS resolution
nslookup my-service.default.svc.cluster.local
# Step 6: Check pod is Running and ready
kubectl get pods -l app=myapp
# STATUS must be Running, READY must be 1/1
# Step 7: Check NetworkPolicies blocking traffic
kubectl get networkpolicies -n <namespace>
kubectl describe networkpolicy <policy-name>
# Step 8: Test directly to pod IP (bypass service)
POD_IP=$(kubectl get pod <pod-name> -o jsonpath='{.status.podIP}')
kubectl run test --image=busybox --rm -it -- wget -O- http://$POD_IP:8080
Ans:
# Step 1: Immediately roll back (fastest)
kubectl rollout undo deployment/my-deployment -n production
# Step 2: Verify rollback is in progress
kubectl rollout status deployment/my-deployment -n production
# Step 3: Confirm old pods are running
kubectl get pods -n production
# Step 4: Check rollout history
kubectl rollout history deployment/my-deployment
# Step 5: Roll back to a specific revision
kubectl rollout undo deployment/my-deployment --to-revision=3
# Step 6: If rollback is slow, force a faster restart
kubectl rollout restart deployment/my-deployment
# Step 7: Verify application is responding
kubectl get svc my-service
curl http://<service-ip>/health
# Prevention for next time:
# - Set minReadySeconds to delay traffic routing
# - Use readiness probes
# - Use PodDisruptionBudget
# - Add deployment annotation: kubernetes.io/change-cause: "version 2.1 - added feature X"
Ans:
# Step 1: Check rollout state and history
kubectl rollout status deployment/payments-api -n prod
kubectl rollout history deployment/payments-api -n prod
# Step 2: A Deployment never touches Pods directly - it creates a NEW ReplicaSet
# for the new template. Find both ReplicaSets:
kubectl get rs -n prod -l app=payments-api
# OLD ReplicaSet: still at (near) full replica count, Pods Running
# NEW ReplicaSet: replicas created, but Pods not Ready
# Step 3: Inspect the failing pods from the NEW ReplicaSet
kubectl describe pod <new-pod> -n prod
kubectl logs <new-pod> -n prod --previous
kubectl get events -n prod --sort-by='.lastTimestamp'
# Step 4: Check the readiness/liveness probe config tied to the new image
kubectl describe pod <new-pod> -n prod | grep -A5 Readiness
Why the old pods are still up: with the default RollingUpdate strategy, the Deployment controller only scales the old ReplicaSet down as Pods from the new ReplicaSet pass their readiness probe. Since the new Pods are crash-looping, they never become Ready, so maxUnavailable/maxSurge keep the old ReplicaSet at capacity to protect availability - this is working as designed, not a stuck rollout.
Common root causes: bad config/env var or missing secret in the new image, a readiness probe pointed at the wrong port/path so healthy Pods never register Ready, ImagePullBackOff mistaken for CrashLoopBackOff, or OOMKill on startup from too-low resource limits.
# Fastest mitigation while you fix the image:
kubectl rollout undo deployment/payments-api -n prod
Prevention: accurate readiness probes, maxUnavailable: 0 on critical services so a bad rollout never reduces capacity, a canary/smoke test gate before full rollout, and progressDeadlineSeconds set so a stuck rollout is flagged instead of hanging silently.
Ans:
Most common causes:
# Cause 1: Using 'latest' tag (most common)
# K8s only pulls 'latest' if imagePullPolicy: Always
# Fix: Always use specific version tags
image: myapp:v1.2.3 # Never: myapp:latest in production
# Cause 2: imagePullPolicy not set correctly
# Default behavior:
# - :latest tag → Always pull
# - other tags → IfNotPresent (uses cached image!)
spec:
containers:
- name: app
image: myapp:v1.2.3
imagePullPolicy: Always # Force pull on every start
# Cause 3: Image not pushed to registry before deploy
# Ensure CI pipeline order:
# 1. docker build
# 2. docker push ← must complete before step 3
# 3. kubectl set image
# Cause 4: Wrong image name/tag in deployment
kubectl describe deployment my-deployment | grep Image
# Cause 5: Old ReplicaSet running (deployment not updated)
kubectl get replicasets
kubectl rollout status deployment/my-deployment
# Cause 6: Cached image on node — delete pod to force fresh pull
kubectl delete pod <pod-name> # New pod will pull fresh
# Or: kubectl rollout restart deployment/my-deployment
🎯 Scenario: You deployed a new pod and it’s been in Pending state for 10 minutes. How do you diagnose and fix it?
Answer:
# Step 1: Always start with describe — read Events section
kubectl describe pod <pod-name> -n <namespace>
# Focus on the LAST few lines of the Events section
# ─── COMMON PENDING REASONS AND FIXES ───────────────────────────────
# Error 1: "0/3 nodes are available: 3 Insufficient memory"
# → Node doesn't have enough resources
kubectl describe nodes | grep -A 10 "Allocated resources"
kubectl top nodes
# Fix: Reduce pod requests, scale up node group, or delete unused pods
# Error 2: "0/3 nodes are available: 3 node(s) had taint {key: value}"
# → Pod doesn't tolerate node taints
kubectl get nodes -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints'
# Fix: Add toleration to pod spec
# Error 3: "0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector"
# → Node labels don't match pod's nodeSelector or nodeAffinity
kubectl get nodes --show-labels
# Fix: Correct nodeSelector/affinity or add missing labels to nodes
# Error 4: "pod has unbound immediate PersistentVolumeClaims"
# → PVC not bound to a PV
kubectl get pvc -n <namespace>
kubectl describe pvc <pvc-name> -n <namespace>
# Fix: Create StorageClass, create PV, or wait for dynamic provisioning
# Error 5: "persistentvolumeclaim ... not found"
# → PVC doesn't exist at all
# Fix: kubectl apply -f pvc.yaml
# Error 6: "Maximum number of Pods is already running"
# → Node has hit pod limit (default 110 pods/node)
kubectl describe node <node> | grep "pods:"
# Fix: Add more nodes, reduce pods per node, or increase --max-pods kubelet flag
# Check scheduler events specifically
kubectl get events -n <namespace> \
--field-selector reason=FailedScheduling \
--sort-by='.lastTimestamp'
# Check if ResourceQuota is blocking
kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>
# Check LimitRange
kubectl get limitrange -n <namespace>
kubectl describe limitrange -n <namespace>
🎯 Scenario: Pod A cannot connect to Service B in the same namespace. How do you diagnose the root cause?
Answer:
# Step 1: Verify pod and service exist
kubectl get pods -l app=service-b -n production
kubectl get svc service-b -n production
# Step 2: Check endpoints — are any pods backing the service?
kubectl get endpoints service-b -n production
# NAME ENDPOINTS AGE
# service-b <none> 5m ← PROBLEM: empty endpoints
# Diagnose empty endpoints:
# a) Get service selector
kubectl get svc service-b -o jsonpath='{.spec.selector}' -n production
# {"app":"service-b","version":"v1"}
# b) Check if any pods match ALL selector labels
kubectl get pods -n production -l app=service-b,version=v1 --show-labels
# If no pods found → label mismatch!
# c) Check pod readiness (unready pods are excluded from endpoints)
kubectl get pods -n production -l app=service-b
# If READY=0/1 → pod failing readiness probe
# Step 3: Test DNS resolution from pod-a
kubectl exec -it pod-a -n production -- nslookup service-b
kubectl exec -it pod-a -n production -- \
nslookup service-b.production.svc.cluster.local
# Step 4: Test port connectivity
kubectl exec -it pod-a -n production -- \
nc -zv service-b 8080
# or:
kubectl exec -it pod-a -n production -- \
curl -v http://service-b:8080/health
# Step 5: Check NetworkPolicies blocking traffic
kubectl get networkpolicy -n production
kubectl describe networkpolicy <policy-name> -n production
# Step 6: Debug with ephemeral container (K8s 1.23+)
kubectl debug -it pod-a -n production \
--image=nicolaka/netshoot \
--target=app-container
# Inside netshoot: full network debugging toolkit
curl -v http://service-b:8080
tcpdump -i eth0 host service-b
nmap -p 8080 service-b
🎯 Scenario: Your cluster is slow and nodes are under pressure. How do you identify the culprit pods?
Answer:
# Check node pressure
kubectl get nodes
# If STATUS shows "MemoryPressure" or "DiskPressure" — node is struggling
kubectl describe node <node-name>
# Look at: Conditions, Allocated Resources, Events
# Find the top CPU consumers
kubectl top pods -A --sort-by=cpu | head -20
# Find the top memory consumers
kubectl top pods -A --sort-by=memory | head -20
# Check container-level usage
kubectl top pods -A --containers --sort-by=memory | head -30
# Find pods near their memory limits (risk of OOMKill)
kubectl get pods -A -o json | jq -r '
.items[] |
.metadata.namespace + "/" + .metadata.name
' | while read pod; do
ns=$(echo $pod | cut -d/ -f1)
name=$(echo $pod | cut -d/ -f2)
kubectl top pod $name -n $ns --containers 2>/dev/null
done
# PromQL queries for investigation:
# Top memory consumers:
# topk(10, container_memory_working_set_bytes{container!="POD"})
# Memory usage % of limit:
# container_memory_working_set_bytes / container_spec_memory_limit_bytes > 0.8
# OOMKill history:
# increase(kube_pod_container_status_restarts_total[24h]) > 0
# kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
# Find and clean up resources wasting cluster capacity
# Deployments with 0 replicas
kubectl get deployments -A --field-selector=spec.replicas=0
# Completed/failed pods not cleaned up
kubectl get pods -A --field-selector=status.phase=Succeeded
kubectl get pods -A --field-selector=status.phase=Failed
# Clean up completed pods older than 1 day
kubectl get pods -A --field-selector=status.phase=Succeeded -o json | \
jq -r '.items[] | select(.status.startTime < "2024-01-14") | .metadata.namespace + "/" + .metadata.name'
Answer:
CrashLoopBackOff means the container starts, exits, and Kubernetes keeps restarting it with exponential backoff. Work through it systematically:
# Step 1: See the restart count and last exit reason at a glance
kubectl get pod my-pod
# STATUS: CrashLoopBackOff RESTARTS: 7
# Step 2: Describe the pod — check Events at the bottom and the exit code
kubectl describe pod my-pod
# Look for: "Last State: Terminated, Reason: Error, Exit Code: 1"
# Step 3: Check logs from the CURRENT crashed attempt
kubectl logs my-pod
# Step 4: Check logs from the PREVIOUS attempt (often more useful —
# the current container may have crashed before logging anything)
kubectl logs my-pod --previous
# Step 5: Check resource limits — was it OOMKilled?
kubectl describe pod my-pod | grep -A5 "Last State"
# Exit Code 137 = OOMKilled → increase memory limits or fix a leak
# Step 6: Check readiness/liveness probes — a misconfigured probe can
# kill an otherwise-healthy container
kubectl get pod my-pod -o yaml | grep -A10 livenessProbe
# Step 7: Verify the image, command, and config are correct
kubectl get pod my-pod -o jsonpath='{.spec.containers[0].image}'
kubectl get configmap,secret -n <namespace> # confirm referenced ones exist
# Step 8: If logs are empty, shell in via a debug container (image itself won't stay up)
kubectl debug my-pod -it --image=busybox --target=my-container
Common root causes, ranked by frequency: application error on startup (bad config/missing env var) → OOMKilled (limits too low) → failing liveness probe (too aggressive initialDelaySeconds) → missing ConfigMap/Secret referenced in the Pod spec → wrong command/entrypoint in the image.
Answer:
This is a scoping question interviewers use to gauge real hands-on scale — there’s no universal “correct” answer, but a strong response is specific and structured rather than vague. Example of how to structure it:
Scale: “I manage 3 EKS clusters — dev, staging, and production — each in a separate AWS account for blast-radius isolation.”
Configuration, be ready to specify:
Cluster version: EKS 1.29, upgraded one minor version at a time
Node groups: Managed node groups (m5.xlarge) for steady-state workloads,
Karpenter for burst/batch workloads on Spot
Networking: VPC CNI, private API endpoint, 3-AZ spread
Add-ons: vpc-cni, coredns, kube-proxy, aws-load-balancer-controller,
cluster-autoscaler (or Karpenter), external-dns
Ingress: AWS Load Balancer Controller provisioning ALBs from Ingress
Observability: Prometheus + Grafana for metrics, Fluent Bit → OpenSearch for logs
Secrets: Secrets Store CSI Driver backed by AWS Secrets Manager
GitOps/Deploys: ArgoCD, syncing from a dedicated gitops repo per environment
Scale (rough numbers): ~40 nodes, ~300 pods in production at peak
Why the structured version is stronger than a one-liner: it shows you actually operate the cluster day-to-day (know the add-ons, autoscaler choice, and observability stack) rather than having only deployed application workloads onto a cluster someone else configured. If you’re newer to Kubernetes, it’s fine to answer honestly at whatever scale you’ve actually worked with (even a single local/minikube cluster or one small EKS cluster) — specificity about what you did configure matters more than the raw cluster count.
A pod in Pending means the scheduler cannot find a suitable node. This is always a resource or constraint issue.
Systematic diagnosis:
# Step 1 — describe the pod (most important command)
kubectl describe pod <pod-name> -n <namespace>
# Look at the 'Events' section at the bottom
# Step 2 — check node resources
kubectl describe nodes | grep -A 5 "Allocated resources"
kubectl top nodes
# Step 3 — check if PVC is bound (if pod mounts one)
kubectl get pvc -n <namespace>
Common causes and fixes:
Cause 1: Insufficient CPU/Memory:
# Events will show:
# "0/3 nodes are available: 3 Insufficient cpu"
# Fix: Scale up node group or reduce resource requests
kubectl get nodes
kubectl describe node <node> | grep -A 10 "Allocated resources"
# Check what's using resources
kubectl top pods --all-namespaces --sort-by=cpu
Cause 2: No nodes match nodeSelector/Affinity:
# Events: "0/3 nodes are available: 3 node(s) didn't match Pod's node affinity"
# Check node labels
kubectl get nodes --show-labels
# Add missing label to node
kubectl label node <node-name> disktype=ssd
Cause 3: PVC not bound:
# Events: "persistentvolumeclaim not found" or PVC stuck in Pending
kubectl describe pvc <pvc-name>
# Check if StorageClass exists
kubectl get storageclass
Cause 4: Taint not tolerated:
# Events: "0/3 nodes are available: 3 node(s) had untolerated taint"
kubectl describe nodes | grep -i taint
# Add toleration to pod spec
Cause 5: Too many pods on nodes (maxPods limit):
# Each node has a default limit of 110 pods
kubectl describe node <node> | grep "Non-terminated Pods"
Quick diagnosis script:
# One command to see all pending pods and their reason
kubectl get pods --all-namespaces --field-selector=status.phase=Pending
kubectl describe pods --all-namespaces | grep -A 10 "Events:"
CrashLoopBackOff = the container starts, crashes, Kubernetes restarts it — in a loop. The backoff time doubles each time (10s → 20s → 40s → … up to 5 min).
Systematic diagnosis:
# Step 1 — describe pod for events and exit codes
kubectl describe pod <pod-name> -n <namespace>
# Step 2 — current logs (may be empty if app crashes immediately)
kubectl logs <pod-name> -n <namespace>
# Step 3 — PREVIOUS container logs (before the crash) — most useful
kubectl logs <pod-name> -n <namespace> --previous
# Step 4 — check exit code
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
Exit code reference:
| Exit Code | Meaning | Fix |
|---|---|---|
0 | Success (not a crash issue) | Check restart policy |
1 | App error | Check app logs |
137 | OOMKilled (Out of Memory) | Increase memory limit |
139 | Segfault | Bug in app or wrong binary |
143 | SIGTERM — graceful shutdown | Check if liveness probe is too aggressive |
Common fixes:
# Fix OOMKill (exit 137) — increase memory limit
kubectl patch deployment <name> -p \
'{"spec":{"template":{"spec":{"containers":[{"name":"app","resources":{"limits":{"memory":"1Gi"}}}]}}}}'
# Fix: App can't connect to database
# Check if DB service is reachable from pod
kubectl exec -it <pod-name> -- nc -zv postgres-svc 5432
# Fix: Wrong image command — override to debug
kubectl run debug-pod \
--image=<same-image> \
--restart=Never \
--rm -it \
--command -- /bin/sh
# Fix: Liveness probe killing app too early — increase initialDelaySeconds
livenessProbe:
initialDelaySeconds: 60 # Give app more time to start
failureThreshold: 5
# Fix: Missing environment variable or secret
kubectl exec -it <pod-name> -- env | grep DB_ # Check env vars
kubectl describe pod <pod-name> | grep -A 5 "Environment"
NotReady means the control plane cannot communicate with the node or the node’s conditions are failing.
Immediate diagnosis:
# Step 1 — check node status and conditions
kubectl get nodes
kubectl describe node <node-name>
# Look for conditions:
# Ready = False/Unknown
# MemoryPressure = True
# DiskPressure = True
# PIDPressure = True
# NetworkUnavailable = True
Step 2 — SSH into the node and check:
# Check kubelet status (most common cause)
sudo systemctl status kubelet
sudo journalctl -u kubelet -f --no-pager | tail -50
# Common kubelet errors:
# "failed to get node info" → network issue
# "certificate expired" → renew kubelet certificates
# "PLEG is not healthy" → pod lifecycle event generator issues (often disk pressure)
# Check node resources
df -h # Disk usage (DiskPressure if >85%)
free -m # Memory (MemoryPressure)
top # CPU and process check
ps aux | wc -l # PID count (PIDPressure if >1000)
Fix common causes:
# Fix 1: kubelet not running
sudo systemctl restart kubelet
# Fix 2: Disk pressure — clean up
docker system prune -af # Clean Docker images/containers
crictl rmi --prune # Clean containerd images
sudo journalctl --vacuum-size=500M # Clean journal logs
# Fix 3: Certificate expired
sudo kubeadm alpha certs renew all
sudo systemctl restart kubelet
# Fix 4: Network plugin not running
kubectl get pods -n kube-system | grep -E "calico|flannel|cilium"
kubectl delete pod -n kube-system <broken-cni-pod> # Restart CNI pod
# Fix 5: Node has too many pods — eviction happening
kubectl describe node <node> | grep -i "eviction\|pressure"
Cordon and drain a problematic node:
# Prevent new pods from scheduling on this node
kubectl cordon <node-name>
# Move existing pods to other nodes
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --force
# After fixing the node, uncordon it
kubectl uncordon <node-name>
Ans:
# Step 1: Check HPA status
kubectl describe hpa my-hpa
# Look for: current metrics, conditions, events
# Step 2: Check if metrics-server is running
kubectl get pods -n kube-system | grep metrics-server
kubectl top pods # If this fails → metrics-server issue
# Step 3: Check that pod has resource requests defined
kubectl describe pod <pod-name> | grep -A5 Requests
# HPA REQUIRES resource requests to calculate utilization %
# If requests not set → HPA can't calculate utilization → won't scale
# Step 4: Check if min/max replicas are hit
kubectl get hpa my-hpa
# If REPLICAS = MAXREPLICAS → HPA is at max, can't scale more
# Step 5: Check cooldown periods
# HPA has a stabilization window (default 5 min scale-down, 3 min scale-up)
# Check scaleUp.stabilizationWindowSeconds in HPA spec
# Step 6: Check API server for RBAC issues
kubectl get --raw /apis/metrics.k8s.io/v1beta1/pods
# Fix: Add resource requests to pod spec
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
Ans:
# Step 1: Describe the pending pod for reasons
kubectl describe pod <pod-name>
# Look in Events section for: Insufficient cpu, Insufficient memory,
# node(s) had untolerated taint, no matching node selector
# Step 2: Check node resources
kubectl describe nodes
kubectl top nodes # Requires metrics-server
# Step 3: Check if nodes joined correctly
kubectl get nodes
# STATUS must be Ready
# Step 4: Check node labels (if using nodeSelector)
kubectl get node <node-name> --show-labels
kubectl describe pod <pod-name> | grep "Node-Selectors"
# Step 5: Check taints (new nodes may have taints)
kubectl describe node <node-name> | grep -i taint
# If node has taint: node.kubernetes.io/not-ready → node still initializing
# Step 6: Check for missing tolerations in pod spec
kubectl describe pod <pod-name> | grep -i toleration
# Step 7: EKS-specific: Check node group IAM role permissions
# Node needs: AmazonEKSWorkerNodePolicy, AmazonEC2ContainerRegistryReadOnly
# Step 8: Check if EKS add-ons are healthy (VPC CNI, kube-proxy, CoreDNS)
kubectl get pods -n kube-system
# Step 9: Check if PVC is Pending (if pod requires PVC)
kubectl get pvc
# StorageClass must exist and dynamic provisioning must work
Answer:
Step 1 — Check if the Ingress resource exists and has an Address:
kubectl get ingress -n my-namespace
# If ADDRESS column is empty, the Ingress controller hasn't reconciled it yet
If ADDRESS is empty, the AWS Load Balancer Controller (or nginx Ingress controller) is not working. Check:
kubectl get pods -n kube-system | grep aws-load-balancer
kubectl logs -n kube-system deployment/aws-load-balancer-controller
Step 2 — Describe the Ingress for events:
kubectl describe ingress my-ingress -n my-namespace
Look at the Events section. Common errors:
- “Failed to build model: couldn’t find nodegroup” — node label missing
- “InvalidParameterException” — security group or subnet annotation wrong
Step 3 — Check the backend Service and Endpoints:
# Does the Service exist?
kubectl get svc my-service -n my-namespace
# Does the Service have any Endpoints (pods selected)?
kubectl get endpoints my-service -n my-namespace
# If ENDPOINTS shows <none>, no pods are matching the Service selector
A Service with no Endpoints means the label selector in the Service does not match any running pods. Compare:
kubectl get pods --show-labels -n my-namespace
kubectl describe svc my-service -n my-namespace | grep Selector
Step 4 — Verify the path and pathType:
A common mistake is using Exact pathType when Prefix is needed. /users/123 will NOT match a rule with path: /users and pathType: Exact. Change to pathType: Prefix.
Step 5 — Check ALB Target Group health:
In the AWS console, go to EC2 → Target Groups → find the target group associated with your ALB. If targets show “unhealthy”, the pods are failing the load balancer health check. The health check path (e.g., /health) may not exist in your application.
Real-world example:
A team’s Ingress worked for /api paths but returned 404 for /api/v2/users. The issue: the annotation was path: /api with pathType: Exact. Changing to pathType: Prefix fixed it — /api/v2/users is a prefix match for /api.
Answer:
Kubernetes deployment history:
Every time you update a Deployment, Kubernetes keeps a revision history (default 10 revisions). You can see and rollback to any previous revision without re-deploying.
Step 1 — Identify that something is wrong:
# Check pod status
kubectl get pods -n production
# Look for pods in CrashLoopBackOff, Error, or OOMKilled state
# Check recent events
kubectl get events -n production --sort-by='.lastTimestamp' | tail -20
# Check pod logs
kubectl logs deployment/my-app -n production --previous
Step 2 — View deployment history:
kubectl rollout history deployment/my-app -n production
# REVISION CHANGE-CAUSE
# 1 Initial deployment
# 2 Updated to v1.1 - added user authentication
# 3 Updated to v1.2 - refactored payment service (CURRENT - broken)
Step 3 — Rollback to previous revision:
# Rollback to the immediately previous version
kubectl rollout undo deployment/my-app -n production
# OR rollback to a specific revision
kubectl rollout undo deployment/my-app -n production --to-revision=2
# Watch the rollback progress
kubectl rollout status deployment/my-app -n production
Step 4 — Verify the rollback succeeded:
kubectl get pods -n production
kubectl describe deployment my-app -n production | grep Image
How Kubernetes performs the rollback:
Kubernetes uses the same rolling update strategy as a forward deployment, just in reverse. It creates pods running the old version and terminates pods running the new version, one by one, respecting maxUnavailable and maxSurge settings. Users see no downtime.
Annotation for change-cause tracking (best practice):
kubectl annotate deployment/my-app kubernetes.io/change-cause="v1.2 - payment service refactor" -n production
This populates the CHANGE-CAUSE column in rollout history, making it easy to identify which revision to roll back to during an incident.
Answer:
Solution: Prometheus + Grafana with kube-state-metrics
Architecture:
Each pod exposes /metrics endpoint
→ Prometheus scrapes metrics every 15 seconds
→ Grafana queries Prometheus and displays dashboards
→ AlertManager sends PagerDuty/Slack alerts on threshold breach
Step 1 — Install kube-prometheus-stack:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.adminPassword=your-secure-password
This installs Prometheus, Grafana, AlertManager, kube-state-metrics, and node-exporter in one command.
Step 2 — Access Grafana:
kubectl port-forward svc/monitoring-grafana 3000:80 -n monitoring
# Open http://localhost:3000
Pre-built dashboards include: Kubernetes cluster overview, node CPU/memory, pod resource usage, namespace resource quotas, and persistent volume usage.
Step 3 — Set up a critical alert:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: pod-crash-alert
namespace: monitoring
spec:
groups:
- name: pod-alerts
rules:
- alert: PodCrashLooping
expr: rate(kube_pod_container_status_restarts_total[5m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"
description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} has been restarting frequently."
Key metrics to monitor:
| Metric | What it tells you | Alert threshold |
|---|---|---|
container_cpu_usage_seconds_total | Pod CPU consumption | > 80% of limit |
container_memory_working_set_bytes | Pod memory usage | > 85% of limit |
kube_pod_status_phase | Pod state (Pending, Running, Failed) | Any pod Pending > 5 min |
kube_deployment_status_replicas_unavailable | Unhealthy replicas | > 0 for production |
node_disk_io_time_seconds_total | Node disk saturation | > 80% utilization |
Answer:
Step 1 — Enable Cost Allocation Tags and use AWS Cost Explorer:
Enable the Kubernetes-specific cost allocation tags:
- eks:cluster-name — identifies which cluster the cost belongs to
- kubernetes.io/service-name — identifies the service
Use Kubecost (open-source) or AWS Container Cost Allocation for per-namespace and per-pod cost breakdown.
Step 2 — Identify the biggest cost drivers:
# Check what instance types your nodes are (GPU instances = very expensive if underused)
kubectl get nodes -o custom-columns=NAME:.metadata.name,TYPE:.metadata.labels.node\\.kubernetes\\.io/instance-type
# Find pods with no resource requests (they may be over-provisioned)
kubectl get pods --all-namespaces -o json | jq '.items[] | select(.spec.containers[].resources.requests == null) | .metadata.name'
Step 3 — Right-size pod resource requests:
The single largest cost optimization in EKS is fixing oversized resource requests. Kubernetes reserves the requested CPU and memory on the node for each pod — even if the pod uses 10% of what it requested.
# Use VPA in recommendation mode to see what pods actually use
kubectl get vpa --all-namespaces
If a pod requests 2 CPU cores but only uses 200m (0.2 cores), you are paying for 10× more capacity than needed. Fix requests based on actual usage data from Prometheus.
Step 4 — Use Spot Instances for non-critical workloads:
# In Karpenter NodePool
spec:
template:
spec:
requirements:
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot", "on-demand"] # prefer spot, fallback to on-demand
Spot Instances save 70–90% on worker node costs. Use them for stateless web services, batch jobs, and dev/test workloads. Keep production databases and stateful workloads on On-Demand.
Step 5 — Scale to zero with KEDA for batch workloads:
Any workload that does not need to run 24/7 should scale to zero when idle. This eliminates the cost of always-on pods for intermittent work.
Real-world impact:
A company reduced EKS costs by 58% through: fixing oversized resource requests (saved 25%), migrating 60% of workloads to Spot Instances (saved 20%), and implementing KEDA scale-to-zero for batch jobs (saved 13%).
Answer:
“Pending” means the Kubernetes scheduler cannot find a suitable node to place the pod. This is always a scheduling problem.
Step 1 — Describe the pod to see scheduler events:
kubectl describe pod <pod-name> -n <namespace>
Scroll to the Events section at the bottom. The message will tell you exactly why scheduling failed. Common messages:
“0/3 nodes are available: 3 Insufficient cpu”
All nodes have less free CPU than the pod requests. Either:
- The pod’s CPU request is too high (a pod requesting 4 CPU when nodes are 2 vCPU each)
- The nodes are genuinely full (Cluster Autoscaler should be adding a new node)
# Check available resources on each node
kubectl describe nodes | grep -A 5 "Allocated resources"
“0/3 nodes are available: 3 node(s) had taint that the pod didn’t tolerate”
All nodes have a taint that the pod doesn’t have a toleration for. Check node taints:
kubectl describe nodes | grep Taints
“0/3 nodes are available: 3 node(s) didn’t match Pod’s node affinity”
The pod has a nodeAffinity or nodeSelector requiring a label that no node has. Check:
kubectl get nodes --show-labels
“persistentvolumeclaim ‘my-pvc’ not found” or “waiting for a volume to be created”
The PVC either doesn’t exist or is not bound to a PV. Check:
kubectl get pvc -n <namespace>
# STATUS should be "Bound" not "Pending"
“exceeded quota”
The namespace has a ResourceQuota and the new pod would exceed it:
kubectl describe resourcequota -n <namespace>
Step 2 — Check Cluster Autoscaler logs (if expecting new nodes):
kubectl logs -n kube-system deployment/cluster-autoscaler | tail -50
If Cluster Autoscaler is failing to add nodes (e.g., insufficient EC2 quota, max node group size reached), the pending pods will never be scheduled.
Answer:
CrashLoopBackOff means the container started, crashed (exited with a non-zero code), Kubernetes restarted it, it crashed again — and this cycle is repeating. Kubernetes adds an exponential backoff delay between restarts (hence BackOff).
Step 1 — Get the exit reason:
kubectl describe pod <pod-name> -n <namespace>
Look for:
Last State: Terminated
Reason: OOMKilled # Out of Memory - increase memory limit
# OR
Reason: Error # Application crashed - check logs
# OR
Reason: ContainerCannotRun # Image entrypoint issue
Step 2 — Read the logs from the previous crashed container:
kubectl logs <pod-name> -n <namespace> --previous
The --previous flag is critical. Without it you get logs from the current (likely empty or starting) container instance. With --previous you get the logs from the last crashed run — which contains the actual error that caused the crash.
Step 3 — Common causes and fixes:
OOMKilled: Container hit its memory limit and was killed.
# Check memory limit vs actual usage
kubectl top pod <pod-name> -n <namespace>
# Increase the memory limit in the Deployment spec
Application startup error: Database connection string wrong, missing environment variable, configuration file not found. The application logs will show the specific error.
Image pull error that becomes CrashLoopBackOff: Sometimes confused with ImagePullBackOff. Check:
kubectl describe pod <pod-name> | grep "Failed to pull"
Readiness probe misconfiguration causing repeated restarts: If the liveness probe path is wrong, Kubernetes kills the container and restarts it repeatedly. Check:
kubectl describe pod <pod-name> | grep -A 10 "Liveness:"
Step 4 — Debug interactively when you cannot read enough from logs:
# Override the entrypoint to get a shell instead of running the crashing app
kubectl run debug-pod \
--image=my-crashing-app:v1.0 \
--restart=Never \
-it \
--command -- /bin/sh
# Now you can explore the container environment manually
Answer:
This is a performance degradation scenario. Work through these layers systematically.
Layer 1 — Is it a pod resource issue?
kubectl top pods -n production --sort-by=cpu
kubectl top nodes
If a pod shows CPU near its limit, it is being CPU-throttled. Kubernetes enforces CPU limits strictly — when a container hits its CPU limit, the kernel throttles it even if the node has spare CPU.
Fix: Increase the CPU limit or reduce CPU request to get better scheduling.
Layer 2 — Is it a downstream dependency?
Use kubectl exec to run a test from inside the pod:
kubectl exec -it <pod-name> -n production -- /bin/sh
# Time a request to the database
time nc -zv postgres-service 5432
# Time an external API call
time curl -o /dev/null -s -w "%{time_total}" https://api.external-service.com/health
If the database call takes 8 seconds, the problem is the database not the application pod.
Layer 3 — Is it a DNS resolution issue?
Slow DNS lookups in Kubernetes are a common but overlooked performance problem. Each hostname lookup goes through CoreDNS, and if CoreDNS pods are overwhelmed:
# Check CoreDNS pods
kubectl get pods -n kube-system | grep coredns
# Check CoreDNS logs for errors
kubectl logs -n kube-system -l k8s-app=kube-dns
# Increase CoreDNS replicas if needed
kubectl scale deployment coredns -n kube-system --replicas=4
Layer 4 — Is it a network issue between pods?
# Check if there are packet drops at the node level
kubectl debug node/<node-name> -it --image=nicolaka/netshoot
# Inside the debug container:
netstat -s | grep -i "packet"
Layer 5 — Check if a recent deployment caused the regression:
kubectl rollout history deployment/my-app -n production
# If a recent rollout correlates with the spike, rollback
kubectl rollout undo deployment/my-app -n production
🎯 Scenario: You deployed a new application and pods show
CrashLoopBackOff. Production is down. What is your step-by-step debugging process?
Answer:
# Step 1: Get overview
kubectl get pods -n production
# NAME READY STATUS RESTARTS AGE
# app-xxx-yyy 0/1 CrashLoopBackOff 8 12m
# Step 2: Describe pod — read Events section carefully
kubectl describe pod app-xxx-yyy -n production
# Look for:
# Exit Code (137=OOM, 1=app error, 139=segfault, 143=SIGTERM)
# Last State: reason, exitCode, finishedAt
# Events: Failed to pull image, Failed to mount volume, etc.
# Step 3: Check CURRENT logs
kubectl logs app-xxx-yyy -n production
# Step 4: Check PREVIOUS container logs (before crash — most useful!)
kubectl logs app-xxx-yyy -n production --previous
# Step 5: Check resource pressure
kubectl top pod app-xxx-yyy -n production
kubectl describe pod app-xxx-yyy | grep -A5 "Limits\|Requests"
# Step 6: Override entrypoint to keep container alive for debugging
kubectl run debug \
--image=your-app-image:tag \
--restart=Never \
--command -- sleep 3600
kubectl exec -it debug -- /bin/sh
# Step 7: Check events across namespace
kubectl get events -n production --sort-by='.lastTimestamp' | tail -20
CrashLoopBackOff diagnosis table:
| Exit Code | Cause | Fix |
|---|---|---|
| 1 | Application error at startup | Check app logs, fix code |
| 137 | OOMKilled | Increase memory limit |
| 139 | Segmentation fault | Debug application |
| 143 | SIGTERM — graceful shutdown | Check liveness probe aggressiveness |
| 126 | Command not executable | Fix file permissions |
| 127 | Command not found | Fix command/args, check image |
🎯 Scenario: A distroless production container has no shell or debugging tools. You need to debug it in production without restarting it.
Answer:
# Ephemeral containers (K8s 1.23+ GA) — inject a debug container into a running pod
# The debug container shares the same network, PID namespace as the target container
# Basic debug with busybox
kubectl debug -it <pod-name> \
--image=busybox:1.35 \
--target=<container-name> # --target shares PID namespace
# Debug with netshoot (full network toolkit)
kubectl debug -it <pod-name> \
--image=nicolaka/netshoot \
--target=main-app
# Debug with a copy of the pod (with modifications)
kubectl debug <pod-name> \
-it \
--copy-to=debug-pod \
--image=myapp:debug \ # Override with debug-enabled image
--share-processes # Share PID namespace to see original app process
# Debug a node (runs privileged container with host filesystem access)
kubectl debug node/<node-name> \
-it \
--image=ubuntu:22.04
# Inside node debug pod:
# chroot /host → access the full node filesystem
# systemctl status kubelet
# journalctl -u kubelet -f
# Alternative: inject a debug sidecar via strategic merge patch
kubectl patch pod <pod-name> -n production \
--patch '{"spec":{"ephemeralContainers":[{
"name":"debug",
"image":"nicolaka/netshoot",
"stdin":true,
"tty":true,
"targetContainerName":"main-app"
}]}}'
kubectl attach <pod-name> -c debug -it
🎯 Scenario: Your API response time is 3 seconds. You have 5 microservices in the call chain. How do you find where the slowness is?
Answer:
# Install Jaeger (distributed tracing)
helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
helm install jaeger jaegertracing/jaeger \
--namespace tracing \
--create-namespace \
--set allInOne.enabled=true \
--set provisionDataStore.cassandra=false
# OpenTelemetry Collector — collects traces from all services
apiVersion: opentelemetry.io/v1alpha1
kind: OpenTelemetryCollector
metadata:
name: otel-collector
namespace: tracing
spec:
config: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch: {}
memory_limiter:
limit_mib: 400
exporters:
jaeger:
endpoint: jaeger-collector:14250
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [jaeger]
# Python app — instrument with OpenTelemetry
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
provider = TracerProvider()
provider.add_span_processor(
BatchSpanProcessor(OTLPSpanExporter(endpoint="otel-collector:4317"))
)
trace.set_tracer_provider(provider)
tracer = trace.get_tracer("my-service")
@app.route("/orders")
def get_orders():
with tracer.start_as_current_span("get-orders") as span:
span.set_attribute("user.id", user_id)
# Call downstream services — trace ID propagated automatically
result = call_inventory_service()
return result
🎯 Scenario: A node is in NotReady state and pods on it are being evicted. How do you investigate?
Answer:
# Step 1: Check node status
kubectl get nodes
# NAME STATUS ROLES AGE
# node-1 NotReady <none> 30d ← Problem
# Step 2: Describe the node — check Conditions
kubectl describe node node-1
# Conditions:
# MemoryPressure False (if True: node is OOM)
# DiskPressure False (if True: disk almost full)
# PIDPressure False (if True: too many processes)
# Ready False ← PROBLEM
#
# Events:
# Warning NodeNotReady kubelet stopped posting node status
# Step 3: SSH to the node and check kubelet
ssh node-1
sudo systemctl status kubelet
sudo journalctl -u kubelet -f --since "1 hour ago"
# Common kubelet issues:
# "Unable to connect to the server" → networking issue
# "certificate has expired" → TLS certs expired
# "failed to get node info" → API server unreachable
# "PLEG is not healthy" → container runtime stuck
# Step 4: Check container runtime
sudo systemctl status containerd
sudo crictl ps # List running containers via CRI
# Step 5: Check disk space
df -h
du -sh /var/lib/containerd # Check container storage
# Step 6: Check memory
free -h
sudo dmesg | grep -i "oom\|killed" # OOM kill events
# Step 7: Check network connectivity to control plane
nc -zv <api-server-ip> 6443
# Step 8: Restart kubelet if needed
sudo systemctl restart kubelet
sudo systemctl restart containerd
Answer:
CrashLoopBackOff means a container is repeatedly crashing and Kubernetes is backing off before restarting it.
Step-by-step troubleshooting:
# 1. Check Pod status and events
kubectl get pods
kubectl describe pod <pod-name>
# 2. Check current logs
kubectl logs <pod-name>
# 3. Check previous container logs (if container already crashed)
kubectl logs <pod-name> --previous
# 4. Check exit code (tells you why the container exited)
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# 5. Debug with a shell (if container has bash)
kubectl debug -it <pod-name> --image=busybox --target=<container-name>
Common exit codes:
| Exit Code | Meaning |
|---|---|
0 | Success (no crash — liveness probe failing?) |
1 | Application error |
137 | OOMKilled (out of memory) |
139 | Segmentation fault |
143 | Graceful termination (SIGTERM) |
Common root causes:
- Wrong command or entrypoint in the container
- Missing environment variables or secrets
- Application fails healthcheck (liveness probe)
- OOMKilled — increase memory limits
- Bad image or missing dependencies
Answer:
Since it’s failing from one specific pod and not every replica, the issue is almost always scoped to that pod’s node, network path, or local state — not the database or the Service itself.
# Step 1: Confirm it's really isolated to this pod, not intermittent everywhere
kubectl get pods -o wide -l app=myapp
# Note which node this pod is scheduled on vs. the healthy pods
# Step 2: Test DNS resolution from inside the failing pod
kubectl exec -it <failing-pod> -- nslookup mydb.namespace.svc.cluster.local
# If this fails but works from a healthy pod → CoreDNS or node-local DNS cache issue
# Step 3: Test raw TCP connectivity to the DB
kubectl exec -it <failing-pod> -- nc -zv mydb.namespace.svc.cluster.local 5432
# Connection refused vs. timeout tells you a lot:
# - refused → reaching the DB, but DB is rejecting (auth, max connections, pg_hba.conf)
# - timeout → not reaching the DB at all (network policy, security group, routing)
# Step 4: Compare NetworkPolicy exposure — is this pod missing a required label?
kubectl get pod <failing-pod> --show-labels
kubectl get networkpolicy -n <namespace> -o yaml
# A NetworkPolicy selector matching on labels can silently exclude one pod
# if its labels drifted (e.g., deployed from a different manifest/branch)
# Step 5: Check the NODE the pod is on for node-level issues
kubectl describe node <node-name> | grep -A5 Conditions
# Node-level security group / CNI issues affect every pod on that specific node
# Step 6: Check for connection pool / max_connections exhaustion on the DB side
# If 99 pods share a connection pool limit and this pod is the "one too many"
# request, the DB itself may be rejecting only the newest connections
# Step 7: Check the pod's actual runtime env vars — a stale ConfigMap/Secret
# mount is one of the most common "works everywhere except this pod" causes
kubectl exec -it <failing-pod> -- env | grep -i db
kubectl get pod <failing-pod> -o jsonpath='{.spec.containers[0].envFrom}'
Most common root causes, in order of likelihood: a NetworkPolicy that matches other pods but not this one (label drift), the pod scheduled on a node with a different Security Group/CNI config, a stale ConfigMap/Secret mounted before a rolling update rolled out to other pods, or the pod being the connection that tips the DB over max_connections.
Answer:
“Pods are running” only means the container process is alive — it doesn’t mean the Service is actually routing traffic to them. Work outward from the Service:
# Step 1: "Running" isn't the same as "Ready" — check READY column, not just STATUS
kubectl get pods -l app=myapp
# NAME READY STATUS RESTARTS
# myapp-abc123 0/1 Running 0 ← Running, but NOT Ready = excluded from Service
# Step 2: If Pods show 0/1 Ready, the readiness probe is failing — check why
kubectl describe pod myapp-abc123 | grep -A5 Readiness
kubectl logs myapp-abc123
# Common cause: app takes longer to warm up than initialDelaySeconds allows
# Step 3: Check the Service actually has endpoints
kubectl get endpoints myapp-svc
# If this is EMPTY, the Service's label selector doesn't match any Pod's labels
# (classic cause: a recent Deployment change altered labels without updating the Service)
# Step 4: Compare the Service selector to the Pod's actual labels
kubectl get svc myapp-svc -o jsonpath='{.spec.selector}'
kubectl get pods --show-labels
# Step 5: If using Ingress/ALB — check the Ingress Controller / Load Balancer
# Controller logs, and confirm target group health (on EKS with ALB Ingress)
kubectl logs -n kube-system deploy/aws-load-balancer-controller
# Step 6: Check for a PodDisruptionBudget or rollout blocking enough Ready replicas
kubectl get pdb
kubectl rollout status deployment/myapp
Most common root cause: Pods are Running but not Ready — a failing or too-aggressive readiness probe removes them from the Service’s endpoint list even though the container itself never crashed. The second most common cause is a label selector mismatch after a Deployment template change, leaving the Service pointing at zero matching Pods.
Answer:
If the name/tag is confirmed correct, the problem is almost always authentication, network reachability, or registry-side state — not the manifest itself:
1. Registry authentication / pull permissions:
# For ECR: does the NODE's IAM role actually have ecr:GetAuthorizationToken +
# ecr:BatchGetImage + ecr:GetDownloadUrlForLayer permissions?
aws iam get-role-policy --role-name eks-node-role --policy-name ecr-pull
# For a private registry needing a Secret: is it actually attached to the Pod
# (or the ServiceAccount), and not just created and forgotten?
kubectl get pod <pod> -o jsonpath='{.spec.imagePullSecrets}'
kubectl get secret <regcred-name> -o yaml # confirm it actually exists, isn't expired
# Describe the pod — the Events section usually names the exact auth failure
kubectl describe pod <pod> | grep -A5 Events
# "unauthorized: authentication required" = credentials issue, not a typo
2. Network path from the NODE to the registry:
# Nodes in a private subnet need a route to the registry — for ECR specifically,
# either a NAT Gateway (internet route) OR an ECR VPC Interface Endpoint
aws ec2 describe-vpc-endpoints --filters Name=service-name,Values=com.amazonaws.us-east-1.ecr.api
# Test reachability directly from a node (via SSM, not SSH)
aws ssm start-session --target <node-instance-id>
curl -v https://<account-id>.dkr.ecr.us-east-1.amazonaws.com/v2/
3. Registry-side / image-side state:
# Does the image tag actually exist in the registry — was it deleted by a
# lifecycle policy, or never successfully pushed by a failed CI job?
aws ecr describe-images --repository-name my-app --image-ids imageTag=v1.2.3
# Rate limiting — public Docker Hub images can hit anonymous pull rate limits
# if many nodes pull simultaneously without authentication
kubectl describe pod <pod> | grep -i "toomanyrequests"
Interview framing: the fact that the image name/tag is confirmed correct should immediately redirect the answer away from the YAML and toward the three layers above the manifest — IAM/credentials, network path, and whether the image genuinely exists and is pullable at that moment, roughly in that order of likelihood.
Answer:
OOMKilled is a cgroup-enforced hard limit, not something the Kubernetes scheduler or kubelet decides in real time — it happens at the Linux kernel level, and Kubernetes just reports what already occurred.
The actual sequence:
1. You set a memory LIMIT on the container:
resources:
limits:
memory: "512Mi"
2. The container runtime (containerd) creates a cgroup for that container
and sets memory.max (cgroups v2) to 512Mi — this is a KERNEL-enforced ceiling
3. The container's process(es) allocate memory normally... until the
cgroup's total usage would exceed 512Mi on the next allocation
4. At that exact moment, the Linux kernel's OOM killer fires — but
SCOPED TO THAT CGROUP, not the whole node (this is the key difference
from a node-level OOM, which is a different, worse scenario)
5. The kernel picks a process to kill within that cgroup (usually the
largest memory consumer, via oom_score_adj) and sends it SIGKILL —
immediate termination, no graceful shutdown, no chance to catch the signal
6. containerd detects the container exited due to OOM and reports it
to the kubelet, which sets:
Reason: OOMKilled
Exit Code: 137 (128 + SIGKILL's signal number 9)
7. The Pod's restart policy (default: Always) kicks in — kubelet
restarts the container, which is why OOMKilled often shows up
together with CrashLoopBackOff if the memory pressure recurs quickly
# Confirm it was OOMKilled specifically (not a normal crash)
kubectl describe pod <pod> | grep -A3 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
# Check what the limit actually was vs. what was likely being used
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].resources}'
Important distinction for an interview: this is different from a node-level OOM (where the node itself runs out of memory across ALL pods, and the kubelet’s node-pressure eviction — or the kernel OOM killer at the node scope — starts killing pods based on QoS class, lowest-priority BestEffort pods first). A single container hitting its own memory.max only ever kills processes inside that container’s cgroup — it doesn’t affect other Pods on the node at all.
Fix: raise the memory limit if the usage is legitimate, fix a memory leak if it’s not, or set memory requests accurately so the scheduler places the Pod on a node that actually has enough headroom in the first place.
Add More Questions to This Guide
Know a question that should be here? Share it and help the community!
Open Google Form