Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Kubernetes & Helm By James Joyner IV · · 9 min read

Kubernetes Error Guide: 'Pod Stuck in Pending' — Get It Scheduled

Fix a Kubernetes pod stuck in Pending: diagnose insufficient CPU/memory, taints, node affinity, unbound PVCs, and image pull holds with kubectl describe and events.

  • #kubernetes
  • #troubleshooting
  • #errors
  • #scheduling

Overview

A pod in Pending has been accepted by the API server but has not yet been scheduled onto a node and started. The pod object exists, but either the scheduler cannot find a node that satisfies its requirements, or the node has been chosen and the kubelet has not finished pulling images or attaching volumes. Until one of those completes, the pod stays parked with no running containers.

You will see this in the pod status column:

NAME                        READY   STATUS    RESTARTS   AGE
api-7d9f4c2b8a-k4l2p        0/1     Pending   0          6m30s

Unlike CrashLoopBackOff, nothing is crashing — nothing has started at all. The cause is almost always a placement constraint the scheduler cannot satisfy (not enough CPU/memory, a taint with no matching toleration, an affinity/selector that no node matches) or a resource the pod depends on that is not yet available (an unbound PersistentVolumeClaim, an image that will not pull). The Events section of kubectl describe pod names the reason directly, most often a FailedScheduling warning. If that is what you see, the dedicated FailedScheduling guide walks through each scheduling predicate.

Symptoms

  • Pod stays at STATUS Pending with READY 0/1 and never advances to ContainerCreating or Running.
  • RESTARTS remains 0 — the container has never been started.
  • kubectl describe pod shows a Warning FailedScheduling event, or the pod is assigned a node but events show volume/image waits.
  • kubectl get events repeats the same scheduling or volume warning every few seconds.
kubectl get pods -l app=api
NAME                        READY   STATUS    RESTARTS   AGE
api-7d9f4c2b8a-k4l2p        0/1     Pending   0          6m30s
kubectl describe pod api-7d9f4c2b8a-k4l2p | grep -A6 Events
Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  2m (x8 over 6m)    default-scheduler  0/3 nodes are available: 3 Insufficient cpu.

Common Root Causes

1. Insufficient CPU or memory on every node

The most common cause: the pod’s resources.requests exceed what any single node has free. The scheduler sums existing requests on each node and finds none with enough headroom, so the pod cannot be placed.

kubectl describe pod api-7d9f4c2b8a-k4l2p | grep -A4 Events
  Warning  FailedScheduling  90s   default-scheduler  0/3 nodes are available: 3 Insufficient memory.

Lower the pod’s requests, free capacity by evicting or right-sizing other workloads, or add a node.

2. Taints without a matching toleration

Nodes carry taints (for example a dedicated GPU pool or a control-plane node) that repel pods lacking a matching toleration. If every schedulable node is tainted and the pod tolerates none of them, it stays Pending.

kubectl describe pod api-7d9f4c2b8a-k4l2p | grep -A4 Events
  Warning  FailedScheduling  45s   default-scheduler  0/3 nodes are available: 3 node(s) had untolerated taint {dedicated: gpu}.

Add the matching tolerations to the pod spec, or target a different node pool.

3. Node affinity or nodeSelector matches no node

A nodeSelector or nodeAffinity rule constrains the pod to nodes with specific labels. If no node carries the label (a typo, or the labeled pool was scaled to zero), the pod has nowhere to go.

kubectl get pod api-7d9f4c2b8a-k4l2p -o jsonpath='{.spec.nodeSelector}'
kubectl get nodes -l disktype=ssd
{"disktype":"ssd"}
No resources found

No node has disktype=ssd, so scheduling fails. Fix the label, correct the selector, or label a node.

4. Unbound PersistentVolumeClaim

If the pod mounts a PVC that is still Pending — no matching PersistentVolume and no working dynamic provisioner — the pod cannot start because the volume it needs does not exist yet.

kubectl get pvc -n prod
NAME          STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
api-data      Pending                                      fast-ssd       5m

A Pending PVC usually means the StorageClass name is wrong or the provisioner is unavailable. Fix the storageClassName or the provisioner and the PVC binds, freeing the pod.

5. Image pull holding the pod before it runs

Once a node is chosen the pod moves toward ContainerCreating, but a slow or failing image pull can keep it effectively stuck. A bad tag or a missing pull secret surfaces as ErrImagePull/ImagePullBackOff rather than a scheduling failure.

kubectl describe pod api-7d9f4c2b8a-k4l2p | grep -A4 Events
  Warning  Failed   30s   kubelet   Failed to pull image "registry.example.com/api:v2": not found

Correct the image reference or attach the imagePullSecrets, and the pod proceeds.

Diagnostic Workflow

Step 1: Confirm the pod is genuinely Pending

kubectl get pod <POD> -n <NS> -o wide

A Pending status with no assigned node points at scheduling; a status of ContainerCreating with a node points at volumes or image pull.

Step 2: Read the pod’s Events

kubectl describe pod <POD> -n <NS>

The Events section names the reason — FailedScheduling with a predicate message (Insufficient cpu, untolerated taint, node affinity), or a volume/image warning.

Step 3: List cluster-wide events

kubectl get events -n <NS> --sort-by='.lastTimestamp'

This shows repeated scheduling attempts and any autoscaler activity in one place.

Step 4: Check node capacity and taints

kubectl describe nodes | grep -A5 'Allocated resources'
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

Compare free CPU/memory against the pod’s requests and confirm whether nodes are tainted.

Step 5: Verify selectors, PVCs, and autoscaling

kubectl get pod <POD> -n <NS> -o jsonpath='{.spec.nodeSelector}{"\n"}{.spec.tolerations}'
kubectl get pvc -n <NS>
kubectl -n kube-system logs deployment/cluster-autoscaler | tail -20

Confirm the selector matches a real node label, every PVC is Bound, and — if you run the cluster-autoscaler — whether it is scaling up or reporting that the pod is unschedulable.

Example Root Cause Analysis

A new report-worker Deployment rolls out and its pod never leaves Pending.

kubectl get pods -l app=report-worker -n prod
NAME                              READY   STATUS    RESTARTS   AGE
report-worker-6c8b7d5f9c-2t7xr    0/1     Pending   0          4m10s

The describe events point straight at CPU:

kubectl describe pod report-worker-6c8b7d5f9c-2t7xr -n prod | grep -A3 Events
  Warning  FailedScheduling  20s (x6 over 4m)  default-scheduler  0/3 nodes are available: 3 Insufficient cpu.

Checking what the pod asks for versus what the nodes have free:

kubectl get pod report-worker-6c8b7d5f9c-2t7xr -n prod \
  -o jsonpath='{.spec.containers[0].resources.requests.cpu}'
4

The pod requests 4 CPU, but each node in the pool has only ~1.5 CPU allocatable after existing workloads. No single node can hold it. The request was copied from a larger environment by mistake.

Fix: right-size the request to what the worker actually needs, then let the rollout reschedule:

kubectl set resources deployment report-worker -n prod --requests=cpu=500m
kubectl rollout restart deployment report-worker -n prod

The scheduler now finds a node and the pod moves to Running.

Prevention Best Practices

  • Set resources.requests to realistic values measured from actual usage, not copied from a larger cluster — oversized requests are the top cause of unschedulable pods.
  • Keep node labels and pod nodeSelector/nodeAffinity rules in sync, and verify a label exists (kubectl get nodes -l <label>) before shipping a selector that depends on it.
  • Pair every node taint with an explicit toleration in the workloads meant to run there, and document which pools are tainted.
  • Provision StorageClasses and dynamic provisioners before deploying stateful workloads so PVCs bind immediately instead of leaving pods Pending.
  • Run the cluster-autoscaler (or Karpenter) so genuine capacity shortfalls trigger a scale-up instead of an indefinite wait. See more in Kubernetes & Helm guides.
  • Validate manifests against your cluster’s constraints with the Kubernetes validator before applying.

Quick Command Reference

# Confirm Pending and whether a node was assigned
kubectl get pod <POD> -n <NS> -o wide

# The reason lives in the pod's Events
kubectl describe pod <POD> -n <NS>

# Cluster-wide events, newest last
kubectl get events -n <NS> --sort-by='.lastTimestamp'

# Node free capacity and taints
kubectl describe nodes | grep -A5 'Allocated resources'
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

# Pod placement constraints
kubectl get pod <POD> -n <NS> -o jsonpath='{.spec.nodeSelector}{"\n"}{.spec.tolerations}'

# PVC binding status
kubectl get pvc -n <NS>

# Autoscaler activity
kubectl -n kube-system logs deployment/cluster-autoscaler | tail -20

# Right-size and reschedule
kubectl set resources deployment <DEPLOY> -n <NS> --requests=cpu=500m
kubectl rollout restart deployment <DEPLOY> -n <NS>

Conclusion

A Pending pod has been accepted but not placed or not yet started. The usual root causes:

  1. Every node has insufficient CPU or memory for the pod’s requests.
  2. Schedulable nodes carry taints the pod does not tolerate.
  3. A nodeSelector/nodeAffinity rule matches no node’s labels.
  4. A PersistentVolumeClaim is unbound, so the volume the pod needs does not exist.
  5. A slow or failing image pull holds the pod after a node is chosen.

Start with kubectl describe pod and read the Events — the scheduler states its reason there in plain language. When the message is FailedScheduling, the FailedScheduling guide breaks down each predicate. For fast triage, the free incident assistant can turn a Pending describe dump into the likely fix.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.