A pod stuck in Pending or showing FailedScheduling events means the Kubernetes scheduler cannot find a suitable node to run it. This guide shows you how to diagnose and fix the most common causes.

Symptom

The pod status is Pending and does not progress to Running. Running kubectl describe pod shows events like:

Name:           example-app-5f7c9d4bfb-7x8kq
Namespace:      default
Status:         Pending
Events:
  Type     Reason             Age    Message
  ----     ------             ----   -------
  Warning  FailedScheduling   3m     0/3 nodes are available:
           insufficient cpu on 3 nodes

Other possible messages:

insufficient memory on 3 nodes
node has a taint...that the pod does not tolerate
pod has unbound immediate PersistentVolumeClaims
no nodes match the pod's node selector
pod affinity/anti-affinity not satisfied
exceeds quota
exceeds LimitRange

Why it happens

Ranked by likelihood:

  1. Insufficient CPU or memory on all nodes — The pod requests more resources than any node has available. Either the cluster is oversubscribed or the pod’s requests are unrealistic.
  2. Node selector or affinity constraint not satisfied — The pod is constrained to specific nodes (by label or affinity rules) and those nodes do not exist or are unavailable.
  3. Taints on nodes that the pod does not tolerate — Control plane nodes and other specialized nodes have taints. Pods without matching tolerations cannot schedule there.
  4. PersistentVolumeClaim (PVC) is pending — The PVC cannot bind to a PersistentVolume, so the pod waits for it indefinitely.
  5. Pod quota or LimitRange exceeded — The namespace has resource quotas or limit ranges that prevent new pods from being created.
  6. Topology spread constraints or pod anti-affinity — The pod requires spread across nodes or cannot run on the same node as other pods, but no suitable nodes exist.

Fix

1. Insufficient CPU or memory on all nodes

Check available resources:

kubectl top nodes

Output example:

NAME       CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
node-1     1900m        95%    3800Mi          95%
node-2     1800m        90%    3600Mi          90%
node-3     1950m        98%    3900Mi        98%

All nodes are full. See what is consuming resources:

kubectl top pod -A | sort --reverse --numeric-sort --key 3 | head -20

Option A: Scale down or delete low-priority pods to free up space.

Option B: Increase the pod’s resource requests if they are too high:

kubectl get pod <pod-name> -o yaml | grep -A 5 "resources:"

If the pod requests 1000m CPU but the nodes only have 100m free, lower the requests:

resources:
  requests:
    cpu: "100m"      # Reduced from 500m
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

Apply and retry:

kubectl apply -f deployment.yaml

Option C: Add more nodes to the cluster.

For cloud clusters, enable cluster autoscaling:

# GKE example
gcloud container clusters update <cluster-name> \
  --enable-autoscaling \
  --min-nodes 1 \
  --max-nodes 10

For bare metal or on-prem, manually add nodes:

# Follow your cloud provider's or deployment tool's instructions

2. Node selector or affinity constraint not satisfied

Check the pod’s node selector:

kubectl get pod <pod-name> -o yaml | grep -A 5 "nodeSelector:"

Example:

nodeSelector:
  disktype: ssd
  gpu: "true"

The pod will only run on nodes with both labels. Check if any nodes have these labels:

kubectl get nodes --show-labels | grep ssd
kubectl get nodes --show-labels | grep gpu

If no nodes match, either:

A. Add the labels to existing nodes:

kubectl label node <node-name> disktype=ssd gpu=true

B. Remove the nodeSelector from the pod if it is not actually required:

# Remove or comment out nodeSelector
# nodeSelector:
#   disktype: ssd

For affinity constraints (more complex than nodeSelector), check the pod spec:

kubectl get pod <pod-name> -o yaml | grep -A 20 "affinity:"

Example of affinity that requires a specific label:

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: disktype
          operator: In
          values:
          - ssd

If no nodes have the required label, add it or relax the affinity to a preference:

affinity:
  nodeAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:    # Changed from required
    - preference:
        matchExpressions:
        - key: disktype
          operator: In
          values:
          - ssd
      weight: 100

Apply the updated pod:

kubectl apply -f deployment.yaml
kubectl rollout restart deployment/<deployment-name>

3. Taints on nodes that the pod does not tolerate

Control plane nodes and other specialized nodes have taints. Check for them:

kubectl describe node <node-name> | grep Taint

Example output:

Taints:   node-role.kubernetes.io/control-plane:NoSchedule

This taint means pods cannot run on the control plane unless they tolerate it. If your pod should be allowed there, add a matching toleration:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: special-app
spec:
  template:
    spec:
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        operator: "Equal"
        value: ""
        effect: "NoSchedule"
      containers:
      - name: app
        image: example:latest

Apply and retry:

kubectl apply -f deployment.yaml

For custom taints, the same pattern applies:

# Check the taint
kubectl describe node <node-name> | grep Taint
# Example: my-taint=myvalue:NoSchedule

# Add the matching toleration
tolerations:
- key: "my-taint"
  operator: "Equal"
  value: "myvalue"
  effect: "NoSchedule"

4. PersistentVolumeClaim (PVC) is pending

Check if the PVC exists and is bound:

kubectl get pvc

# Output:
# NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES
# my-pvc     Pending   <none>   <none>     RWO

If the PVC is pending, it cannot bind to any PersistentVolume. Check why:

kubectl describe pvc <pvc-name>

Common reasons:
– No PV exists: Create a PV or a StorageClass.
– StorageClass does not exist: Create it or ensure the cluster has a default.

Create a PersistentVolume:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /data
EOF

Or enable dynamic provisioning by creating a StorageClass:

cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/host-path
EOF

Then update your PVC to use that StorageClass:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  storageClassName: fast
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

Apply and the PVC should bind:

kubectl apply -f pvc.yaml
kubectl get pvc    # Should now be Bound

5. Pod quota or LimitRange exceeded

Check if the namespace has resource quotas:

kubectl get resourcequota -n <namespace>
kubectl describe resourcequota <quota-name> -n <namespace>

Example:

Name:       cpu-quota
Namespace:  default
Resource    Used    Hard
--------    ----    ----
cpu         1000m   2000m
memory      1Gi     2Gi
pods        5       10

Your new pod might exceed the quota. Check the pod’s requests:

kubectl get pod <pod-name> -o yaml | grep -A 5 "resources:"

Option A: Increase the quota:

kubectl edit resourcequota <quota-name>

And increase the hard values.

Option B: Reduce the pod’s resource requests:

resources:
  requests:
    cpu: "50m"       # Reduced
    memory: "128Mi"
  limits:
    cpu: "200m"
    memory: "256Mi"

Also check for LimitRange constraints:

kubectl get limitrange -n <namespace>
kubectl describe limitrange <limit-name>

LimitRange might enforce minimum or maximum resources. Adjust your pod to fit within the range.

Apply and retry:

kubectl apply -f deployment.yaml

6. Topology spread constraints or pod anti-affinity

Check the pod spec for topology spread or anti-affinity:

kubectl get pod <pod-name> -o yaml | grep -A 15 "topologySpreadConstraints:\|affinity:"

Example of topology spread that requires 3 zones:

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: example

If the cluster has only 1 zone or not enough nodes per zone, the pod cannot schedule. Either:

A. Add nodes across multiple zones:

kubectl get nodes -o wide | grep "REGION\|ZONE"

B. Relax the constraint:

topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: ScheduleAnyway    # Changed from DoNotSchedule

Or remove topology spread entirely if not needed.

Apply and retry:

kubectl apply -f deployment.yaml

Prevent it

  1. Always set resource requests and limits:

    resources:
      requests:
        cpu: "100m"
        memory: "256Mi"
      limits:
        cpu: "500m"
        memory: "512Mi"
    

  2. Monitor cluster capacity:

    kubectl top nodes
    kubectl describe node <node-name> | grep -A 10 "Allocated"
    

  3. Use node labels and selectors strategically, only when genuinely needed. Avoid over-constraining pods.

  4. Understand node taints before scheduling:

    kubectl describe node | grep Taint
    

  5. Plan PVC provisioning: Ensure StorageClass or PersistentVolumes exist before creating pods that need them.

  6. Set reasonable resource quotas for namespaces, not too tight:

    kubectl get resourcequota -A
    

  7. Use cluster autoscaling for cloud clusters to add nodes when pods are pending:

    # GKE, EKS, AKS, etc. provide autoscaling configuration
    

FAQ

How do I know why a pod is stuck in Pending?

Run kubectl describe pod <pod> and look at the Events section. It shows the exact scheduler reason, e.g. ‘Insufficient cpu’ or ‘node has a taint’.

What is the difference between Pending and FailedScheduling?

Pending means the scheduler has not yet assigned the pod to a node. FailedScheduling is an event that indicates a scheduling attempt failed. Check kubectl describe pod to see events.

How do I see how much CPU and memory are available on nodes?

Use kubectl top nodes or kubectl describe node to see allocated/available resources. Also check kubectl get nodes to see NotReady status.

What are taints and tolerations, and why do they block pods?

A taint marks a node as unsuitable for certain pods. A toleration on a pod allows it to run on tainted nodes. Check kubectl describe node | grep Taint and your pod spec tolerations.

How this guide was verified

Written by hand from the official documentation, every command re-checked, and reviewed a second time before publishing. Last reviewed on .

Official sources read for this article

Share.

Haythem Rehouma builds and teaches DevOps and AI tooling: Kubernetes, Docker, Terraform, Claude Code, ChatGPT and n8n. Every KubPoint guide is written from the official docs and re-tested before it goes live.

Comments are closed.