Skip to content

Kubernetes Troubleshooting Guide

Systematic approaches to diagnosing and resolving common Kubernetes issues.


Pod Issues

CrashLoopBackOff

The pod starts, crashes, and Kubernetes keeps restarting it with exponential backoff.

Diagnosis

# Check pod status and restart count
kubectl get pod my-pod -o wide

# View pod events
kubectl describe pod my-pod

# Check current and previous container logs
kubectl logs my-pod
kubectl logs my-pod --previous

Common Causes - Application error or unhandled exception on startup - Missing configuration (ConfigMap, Secret not mounted or wrong key) - Database or external service unreachable - Liveness probe failing too quickly - increase initialDelaySeconds - Binary or entrypoint not found in the container image

Resolution Steps 1. Check logs from the previous container: kubectl logs my-pod --previous 2. Verify ConfigMaps and Secrets exist and have expected keys 3. Test the image locally: docker run --rm -it IMAGE_NAME /bin/sh 4. Temporarily remove liveness probes to let the container start 5. Check if resource limits are too low (OOMKilled shows as a different status)


ImagePullBackOff

Kubernetes cannot pull the container image.

Diagnosis

kubectl describe pod my-pod | grep -A 10 Events

Common Causes - Image name or tag is wrong (typo, wrong registry) - Private registry and no imagePullSecret configured - Image tag does not exist (especially latest after rebuild) - Registry is unreachable (network policy, firewall, DNS)

Resolution Steps

# Verify the image exists
docker pull my-registry/my-image:tag

# Check imagePullSecrets on the pod spec
kubectl get pod my-pod -o jsonpath='{.spec.imagePullSecrets}'

# Create an image pull secret
kubectl create secret docker-registry my-secret \
  --docker-server=my-registry.example.com \
  --docker-username=user \
  --docker-password=pass \
  --docker-email=email@example.com

# Verify the secret is in the correct namespace
kubectl get secrets -n my-namespace


Pending Pods

Pod stays in Pending state and never gets scheduled.

Diagnosis

kubectl describe pod my-pod | grep -A 20 Events

Common Causes - Insufficient resources - no node has enough CPU or memory - Node selector or affinity rules cannot be satisfied - Taints on nodes with no matching tolerations - PersistentVolumeClaim in Pending state - Pod topology spread constraints cannot be satisfied - ResourceQuota exceeded in the namespace

Resolution Steps

# Check node resources
kubectl describe nodes | grep -A 5 "Allocated resources"

# Check for taints
kubectl get nodes -o json | jq '.items[].spec.taints'

# Check resource quotas
kubectl describe resourcequota -n my-namespace

# Check PVC status
kubectl get pvc -n my-namespace


OOMKilled

Container is killed because it exceeded its memory limit.

Diagnosis

kubectl describe pod my-pod | grep -A 5 "Last State"
# Look for: Reason: OOMKilled

Resolution Steps 1. Increase the memory limit in the pod spec 2. Profile the application to find memory leaks 3. Check if the JVM or runtime has its own memory settings that conflict with the container limit 4. Set memory requests equal to limits to guarantee QoS class "Guaranteed"

resources:
  requests:
    memory: "512Mi"
  limits:
    memory: "1Gi"

CreateContainerConfigError

Container cannot be created due to a configuration issue.

Diagnosis

kubectl describe pod my-pod | grep -A 10 Events

Common Causes - Referenced ConfigMap or Secret does not exist - Key referenced from ConfigMap/Secret does not exist - ServiceAccount does not exist - SecurityContext settings incompatible with the node

Resolution Steps

# Verify ConfigMap exists and has expected keys
kubectl get configmap my-config -o yaml

# Verify Secret exists
kubectl get secret my-secret

# Check ServiceAccount
kubectl get serviceaccount my-sa -n my-namespace


Service and Networking

Service Endpoints Not Ready

Diagnosis

# Check if endpoints exist for the service
kubectl get endpoints my-service

# Verify selector matches pod labels
kubectl get svc my-service -o jsonpath='{.spec.selector}'
kubectl get pods -l app=my-app --show-labels

Common Causes - Selector on the service does not match any pod labels - Pods exist but are not Ready (failing readiness probe) - Pods are in a different namespace than the service

Resolution Steps 1. Verify the service selector matches pod labels exactly 2. Check pod readiness: kubectl get pods -o wide 3. Fix readiness probe if pods are running but not Ready 4. Ensure service and pods are in the same namespace


DNS Resolution Failures

Diagnosis

# Run a DNS test from inside a pod
kubectl run dns-test --rm -it --image=busybox -- nslookup my-service
kubectl run dns-test --rm -it --image=busybox -- nslookup my-service.my-namespace.svc.cluster.local

# Check CoreDNS/kube-dns pods
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns

Common Causes - CoreDNS pods are not running or are crashlooping - NetworkPolicy blocking DNS traffic (port 53 UDP/TCP to kube-system) - ndots setting causing excessive DNS queries (default ndots:5) - Service name typo or wrong namespace in the DNS name

DNS Name Format

<service>.<namespace>.svc.cluster.local
<pod-ip-with-dashes>.<namespace>.pod.cluster.local


Network Policies Blocking Traffic

Diagnosis

# List network policies in the namespace
kubectl get networkpolicies -n my-namespace

# Describe a specific policy
kubectl describe networkpolicy my-policy -n my-namespace

Common Causes - A default deny policy exists without corresponding allow policies - Egress policies blocking outbound traffic (including DNS) - Label selectors in the policy do not match expected pods - Missing namespace selectors for cross-namespace traffic

Resolution Steps

# Temporarily check by removing network policies (non-production only)
kubectl delete networkpolicy my-policy -n my-namespace

# Always include DNS egress in policies
# Example: allow egress to kube-dns

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Node Issues

Node NotReady

Diagnosis

kubectl describe node my-node | grep -A 10 Conditions

Common Causes - kubelet not running or crashed on the node - Node ran out of disk space, memory, or PIDs - Network connectivity lost between node and control plane - Container runtime (containerd, CRI-O) not responding - Certificate expired

Resolution Steps

# SSH to the node and check kubelet
systemctl status kubelet
journalctl -u kubelet --since "10 minutes ago"

# Check container runtime
systemctl status containerd

# Check disk space
df -h

# Check memory
free -m


Disk Pressure, Memory Pressure, PID Pressure

Diagnosis

kubectl describe node my-node | grep -A 5 Conditions

Disk Pressure (DiskPressure=True) - Node available disk is below threshold (default: 15% for imagefs, 10% for nodefs) - Kubernetes starts evicting pods - Clean up unused images: crictl rmi --prune - Clean up unused containers and logs

Memory Pressure (MemoryPressure=True) - Available memory is below threshold (default: 100Mi) - Pods are evicted starting with BestEffort QoS class - Check for pods without resource limits consuming excessive memory

PID Pressure (PIDPressure=True) - Available PIDs are below threshold - Usually caused by a process fork bomb or application spawning too many threads - Check: ps aux | wc -l on the node


Cordon and Drain

# Prevent new pods from being scheduled on a node
kubectl cordon my-node

# Evict pods from a node (for maintenance)
kubectl drain my-node --ignore-daemonsets --delete-emptydir-data

# Re-enable scheduling
kubectl uncordon my-node

Drain Issues - PodDisruptionBudgets may prevent eviction - check: kubectl get pdb -A - Pods with local storage need --delete-emptydir-data - DaemonSet pods are skipped with --ignore-daemonsets - Pods without a controller (bare pods) need --force


Storage and PVC Issues

PersistentVolumeClaim Stuck in Pending

Diagnosis

kubectl describe pvc my-pvc
kubectl get pv
kubectl get storageclass

Common Causes - No PersistentVolume available that matches the claim - StorageClass does not exist or has no provisioner - Cloud provider quota exhausted (disk quota) - WaitForFirstConsumer binding mode - PVC waits until a pod uses it - Zone mismatch - PV is in a different zone than the node

Resolution Steps 1. Verify the StorageClass exists: kubectl get sc 2. Check the PVC request matches available PV sizes and access modes 3. For dynamic provisioning, verify the CSI driver is installed and running 4. Check cloud provider quotas for persistent disks


Access Mode Mismatch

Access Mode Abbreviation Description
ReadWriteOnce RWO Single node read-write
ReadOnlyMany ROX Many nodes read-only
ReadWriteMany RWX Many nodes read-write
ReadWriteOncePod RWOP Single pod read-write

Common Issues - Using RWO but trying to mount on multiple nodes (use RWX or switch to a shared filesystem) - Not all storage backends support RWX (EBS does not, EFS does) - RWOP is only supported in Kubernetes 1.27+


StorageClass Issues

# List storage classes
kubectl get sc

# Check the default storage class
kubectl get sc -o jsonpath='{.items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")].metadata.name}'

Common Issues - No default StorageClass and PVC does not specify one - Provisioner for the StorageClass is not installed (CSI driver missing) - Storage class parameters are incorrect (disk type, encryption settings) - Volume binding mode is Immediate but needs WaitForFirstConsumer for topology-aware provisioning


RBAC Errors

Forbidden / Unauthorized

Diagnosis

# Check if a user/service account can perform an action
kubectl auth can-i get pods --as=system:serviceaccount:my-namespace:my-sa
kubectl auth can-i create deployments --as=user@example.com -n my-namespace

# List roles and bindings
kubectl get roles,rolebindings -n my-namespace
kubectl get clusterroles,clusterrolebindings

Common Causes - ServiceAccount does not have a RoleBinding or ClusterRoleBinding - Role exists but is in a different namespace than the RoleBinding - ClusterRole is bound with a RoleBinding (limits to namespace scope) - The subject in the binding does not match the requesting identity

Resolution Steps

# Create a role
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n my-namespace

# Bind the role to a service account
kubectl create rolebinding pod-reader-binding \
  --role=pod-reader \
  --serviceaccount=my-namespace:my-sa \
  -n my-namespace

# For cluster-wide access, use ClusterRole and ClusterRoleBinding
kubectl create clusterrolebinding admin-binding \
  --clusterrole=admin \
  --serviceaccount=my-namespace:my-sa

Aggregated ClusterRoles - admin, edit, and view are aggregated - custom roles can contribute to them via labels - Modifying a base role (like edit) will not persist across cluster upgrades


kubectl Debug Techniques

Essential Commands

# Get detailed information about a resource
kubectl describe pod my-pod

# View logs (current and previous container)
kubectl logs my-pod
kubectl logs my-pod --previous
kubectl logs my-pod -c my-container    # specific container in multi-container pod
kubectl logs -l app=my-app --all-containers  # all pods with a label

# Execute commands inside a running container
kubectl exec -it my-pod -- /bin/sh
kubectl exec -it my-pod -c my-container -- /bin/sh

# Port forward to a pod or service
kubectl port-forward pod/my-pod 8080:80
kubectl port-forward svc/my-service 8080:80

# View cluster events sorted by time
kubectl get events --sort-by='.lastTimestamp'
kubectl get events -n my-namespace --sort-by='.lastTimestamp'

Ephemeral Debug Containers

# Attach a debug container to a running pod (Kubernetes 1.23+)
kubectl debug -it my-pod --image=busybox --target=my-container

# Create a copy of a pod with a debug container
kubectl debug my-pod -it --image=ubuntu --copy-to=debug-pod --share-processes

Network Debugging from Inside a Pod

# Run a temporary debug pod with networking tools
kubectl run netdebug --rm -it --image=nicolaka/netshoot -- /bin/bash

# Inside the pod
curl -v http://my-service:8080/health
nslookup my-service.my-namespace.svc.cluster.local
traceroute my-service
tcpdump -i eth0 port 8080

Resource Usage

# View node resource usage (requires metrics-server)
kubectl top nodes

# View pod resource usage
kubectl top pods -n my-namespace --sort-by=memory

# View resource requests and limits
kubectl get pods -n my-namespace -o custom-columns=\
NAME:.metadata.name,\
CPU_REQ:.spec.containers[0].resources.requests.cpu,\
CPU_LIM:.spec.containers[0].resources.limits.cpu,\
MEM_REQ:.spec.containers[0].resources.requests.memory,\
MEM_LIM:.spec.containers[0].resources.limits.memory

Useful JSONPath Queries

# Get all pod IPs
kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.podIP}{"\n"}{end}'

# Get all images running in a namespace
kubectl get pods -n my-namespace -o jsonpath='{range .items[*].spec.containers[*]}{.image}{"\n"}{end}' | sort -u

# Get pods not in Running state
kubectl get pods --field-selector=status.phase!=Running

Troubleshooting Flowchart

Pod not working?
  |
  +-- Pending? --> Check scheduling (resources, taints, PVC, quotas)
  |
  +-- ImagePullBackOff? --> Check image name, registry auth, network
  |
  +-- CrashLoopBackOff? --> Check logs (--previous), config, probes
  |
  +-- Running but not Ready? --> Check readiness probe
  |
  +-- Running and Ready but not reachable?
        |
        +-- Check Service selector matches pod labels
        +-- Check endpoints exist
        +-- Check NetworkPolicies
        +-- Check DNS resolution
        +-- Check ingress/load balancer configuration

Additional Resources