kubernetes

Kubernetes Pod Pending: Scheduler Troubleshooting Guide

Pod stuck in Pending? Learn how to find the cause in Kubernetes: FailedScheduling, resources, taints, PVCs, image pulls, with kubectl commands and fixes.

By Shubhankar Tripathi • • 5 min read

Kubernetes Scheduler Troubleshooting: Why Is My Pod Pending?

bitcodematrix.com | Kubernetes Series

1. Introduction

A Pod in the Pending state is one of the most common problems in Kubernetes, and one of the most confusing, because the word Pending covers several different situations. Sometimes the scheduler cannot find a node. Sometimes a node was chosen but the kubelet is still pulling an image or waiting for a volume. Sometimes nothing has looked at the Pod at all.

This article gives a repeatable method for finding the cause. It starts with the three commands that answer most cases, splits the problem into scheduler-side and kubelet-side causes, explains each common cause with its fix, covers Pods that are missing because a controller could not create them, and ends with a hands-on exercise and a command cheat sheet. It builds on the earlier articles in the scheduling series, and in particular on Scheduling Constraints in Production.

After reading it you should be able to:

  • Explain what the Pending phase means and what it does not mean.

  • Tell scheduler-side problems from kubelet-side problems in under a minute.

  • Read FailedScheduling events and fix resource, taint, selector, affinity, spread and volume causes.

  • Diagnose a scheduler that is not working, and Pods that never appear.

  • Follow a checklist and the right kubectl commands for every case.

2. What Pending Really Means

The Pod phase Pending means that the cluster has accepted the Pod but one or more of its containers is not running yet. This includes the time spent waiting to be scheduled and the time spent downloading images and setting up the Pod on the node. It does not by itself say that something is wrong: a Pod is Pending for a few seconds during every normal startup.

The most useful first question is whether the Pod has been assigned to a node, because that decides which component to investigate.

A Pod with no node points to the scheduler; a Pod with a node points to the kubelet

Figure 1: The NODE column splits the problem in two

Observation

Meaning

NODE is <none>, phase Pending

The scheduler has not placed the Pod. Investigate scheduling.

NODE is set, STATUS is ContainerCreating or similar

The Pod was scheduled. Investigate the kubelet, image, volumes and network.

No Pod exists at all

A controller or admission step failed. Investigate the ReplicaSet, quotas and webhooks.


3. The First Three Commands

  1. See the Pod status and node assignment.

kubectl get pod <pod-name> -o wide


  1. Read the events at the bottom of the description. This is where the reason almost always appears.

kubectl describe pod <pod-name>


  1. If the events are long or have expired, list events for the Pod in time order.

kubectl get events --sort-by=.lastTimestamp --field-selector involvedObject.name=<pod-name>


Events are kept for a limited time, commonly about an hour, so check them early. If the Pod is old and has no events left, delete and recreate it, or look at the scheduler and kubelet logs.

4. Scheduler-Side Causes (No Node Assigned)

4.1 No events at all

If a Pod has no node and no FailedScheduling event, the scheduler is probably not looking at it.

Cause

How to check and fix

The scheduler is not running

Check kubectl get pods -n kube-system for kube-scheduler (on managed services the control plane is hidden, so check the provider status). Fix the control plane or restart the scheduler.

The Pod names another scheduler

Check spec.schedulerName. If a custom scheduler is named and it is not running, the default scheduler ignores the Pod. Fix the name or deploy the scheduler.

The Pod has scheduling gates

Check spec.schedulingGates. A gated Pod is not scheduled until its gates are removed, and kubectl shows the status SchedulingGated. Remove the gate through the controller that added it.

The control plane is overloaded or the scheduler has lost leader election

Read the scheduler logs for leader election messages and API errors.


kubectl get pod <pod-name> -o jsonpath='{.spec.schedulerName}{"\n"}'

kubectl get pod <pod-name> -o jsonpath='{.spec.schedulingGates}{"\n"}'

kubectl get pods -n kube-system | grep scheduler

kubectl logs -n kube-system <kube-scheduler-pod> --tail=50


4.2 FailedScheduling: reading the message

When the scheduler tries and fails, it adds a FailedScheduling event that explains why each node was rejected. The numbers add up to the number of nodes, and a node can appear under only one reason, the first one that rejected it.

Warning  FailedScheduling  default-scheduler

0/6 nodes are available: 1 node(s) were unschedulable,

2 node(s) had untolerated taint {dedicated: gpu},

3 Insufficient cpu.


Message fragment

Cause and fix

Insufficient cpu or Insufficient memory

Requests exceed what is free on every node. Lower requests, add nodes, or free capacity. See section 4.3.

Too many pods

Nodes have reached their maximum Pod count (the kubelet max pods setting, often 110, or fewer on some cloud networks). Add nodes or raise the limit where possible.

node(s) didn't match Pod's node affinity/selector

nodeSelector or node affinity matches no node. Compare with kubectl get nodes --show-labels and fix the label or the rule.

node(s) had untolerated taint

Nodes are tainted. Add a matching toleration, or use untainted nodes. Check kubectl describe node for taints.

node(s) were unschedulable

Nodes are cordoned. Use kubectl uncordon if maintenance is finished.

node(s) didn't match pod anti-affinity rules or pod affinity rules

Affinity rules exclude the nodes. Relax a required rule or add nodes.

node(s) didn't match pod topology spread constraints

A DoNotSchedule spread rule cannot be satisfied. Check capacity per zone, maxSkew and topologyKey labels.

node(s) didn't have free ports for the requested pod ports

Another Pod already uses the same hostPort. Remove hostPort or use a Service.

node(s) had volume node affinity conflict

The volume is in a zone that the Pod is not allowed to run in. Fix the zone rules or recreate the volume.

node(s) didn't find available persistent volumes to bind

No suitable PersistentVolume or provisioner. See section 4.4.

Insufficient nvidia.com/gpu or another extended resource

No node advertises enough of the resource. Check the device plugin and the node pool.


The wording of these messages changes between Kubernetes versions, so match on the meaning rather than the exact text.

4.3 Checking resource capacity

Insufficient cpu or memory is based on requests, not on actual usage. A node can look idle in monitoring and still be full, because the scheduler adds up the requests of the Pods already placed there. Compare the allocatable amount with what is already requested.

kubectl describe node <node-name> | grep -A8 "Allocatable"

kubectl describe node <node-name> | grep -A12 "Allocated resources"

kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].resources.requests}{"\n"}'

kubectl top nodes


  • A Pod's effective request is the sum of its app containers, or the largest init container if that is higher, plus any Pod overhead from its RuntimeClass.

  • A request larger than the allocatable capacity of the biggest node can never be scheduled, however much you scale down other workloads.

  • A LimitRange may add default requests that you did not write in the manifest. Check kubectl get limitrange -n <namespace>.

  • System Pods and DaemonSets also use requests on every node, which lowers what is left for applications.

4.4 PersistentVolumeClaims that are Pending

A Pod that uses a PersistentVolumeClaim cannot start until the claim is bound. If the claim is Pending, the Pod stays Pending as well, and the real cause is on the claim.

kubectl get pvc -n <namespace>

kubectl describe pvc <pvc-name> -n <namespace>

kubectl get storageclass


PVC symptom

Cause and fix

storageclass not found

The claim names a StorageClass that does not exist. Fix the name or create the class.

No default StorageClass and none specified

Set storageClassName, or mark a default class.

waiting for first consumer

Normal with volumeBindingMode WaitForFirstConsumer. The claim binds after the Pod is scheduled, so look at the Pod's scheduling events instead.

Provisioning failed

The CSI driver or cloud permissions failed. Read the PVC events and the driver logs.

Pre-created PV does not match

Capacity, access mode, StorageClass or node affinity of the PersistentVolume does not match the claim.


4.5 Priority, preemption and nominated nodes

A high-priority Pod that cannot be placed may trigger preemption. While victims shut down, the Pod stays Pending and shows a nominated node. This is normal for a short time, as long as it eventually starts.

kubectl get pod <pod-name> -o jsonpath='{.status.nominatedNodeName}{"\n"}'

kubectl get events --field-selector reason=Preempted


5. Kubelet-Side Causes (Node Assigned)

When the NODE column has a value, the scheduler has done its job. The Pod may still show Pending in the phase, but the STATUS column and the events usually name the real state.

Status or event

Cause and fix

ContainerCreating for a long time

The kubelet is pulling an image, mounting volumes or creating the network sandbox. Read the events.

ErrImagePull, ImagePullBackOff

Wrong image name or tag, no access to the registry, or a missing imagePullSecret. Fix the reference or the secret.

FailedMount: configmap or secret not found

The Pod references a ConfigMap or Secret that does not exist in its namespace. Create it, or mark the volume optional if that is correct.

FailedAttachVolume or Multi-Attach error

A block volume is still attached to another node. Wait for detach, or fix the node that holds it.

FailedCreatePodSandBox

CNI plugin or container runtime failure on the node. Check the CNI Pods, node logs and IP address availability.

Node NotReady

The node is unhealthy. Check kubectl describe node conditions and kubelet logs.


kubectl describe pod <pod-name> | grep -A15 Events

kubectl describe node <node-name> | grep -A10 Conditions

kubectl get events -A --field-selector reason=FailedMount


CrashLoopBackOff, which starts after a container has run and failed, is a different problem from Pending.

6. The Pod Does Not Exist: Controller and Admission Failures

Sometimes a Deployment shows fewer Pods than requested and no Pending Pod exists. In this case the Pod was rejected before it was created, and the explanation is on the ReplicaSet or the controller, not on a Pod.

kubectl get deployment <name>

kubectl describe replicaset -l app=<label>

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


Event on the ReplicaSet or Job

Cause and fix

exceeded quota

A ResourceQuota in the namespace is full. Raise the quota or free resources. Also check that Pods set requests, if the quota requires them.

failed quota: must specify cpu, memory

The quota requires requests and limits and the Pod has none. Add them or a LimitRange default.

forbidden: violates PodSecurity

The namespace Pod Security level rejects the Pod. Adjust securityContext or the namespace label.

admission webhook denied the request

A policy or mutation webhook rejected the Pod. Read the message and fix the manifest or the policy.

serviceaccount not found

The named ServiceAccount does not exist. Create it.


7. Hands-On Walkthrough

This exercise creates a Pod that cannot be scheduled for two different reasons and fixes them one at a time. Use a test cluster.

  1. Create a Pod with a very large memory request and a nodeSelector that no node has.

apiVersion: v1

kind: Pod

metadata:

  name: pending-demo

spec:

  nodeSelector:

    disktype: nvme

  containers:

  - name: app

    image: nginx:1.27

    resources:

      requests:

        cpu: 100m

        memory: 500Gi


  1. Apply it and check the status.

kubectl apply -f pending-demo.yaml

kubectl get pod pending-demo -o wide


  1. Read the events. You should see the Pod is not placed and the message lists the nodes that failed the selector.

kubectl describe pod pending-demo | grep -A8 Events


  1. Fix the first problem by labeling a node, or by removing the selector, then recreate the Pod. Check the events again: now the reason should be insufficient memory.

kubectl label node <node-name> disktype=nvme

kubectl delete pod pending-demo

kubectl apply -f pending-demo.yaml

kubectl describe pod pending-demo | grep -A8 Events


  1. Fix the second problem by lowering the memory request to something realistic, such as 128Mi, then recreate the Pod and confirm it is Running. Clean up afterwards.

kubectl delete pod pending-demo

kubectl label node <node-name> disktype-


The lesson is that the scheduler reports the first reason that rejected each node, so fixing one cause often reveals the next. Work through the events until the Pod runs.

8. Quick Reference

Question

Command

Is a node assigned?

kubectl get pod <pod> -o wide

Why was it not scheduled?

kubectl describe pod <pod>

What are the recent events?

kubectl get events --sort-by=.lastTimestamp

What do the nodes offer?

kubectl describe node <node>

What labels do nodes have?

kubectl get nodes --show-labels

Which taints do nodes have?

kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

Is the claim bound?

kubectl get pvc -n <namespace>

Which Pods are Pending in the cluster?

kubectl get pods -A --field-selector=status.phase=Pending

Is the scheduler healthy?

kubectl get pods -n kube-system, then kubectl logs on the scheduler Pod

Did the controller create the Pod?

kubectl describe replicaset, kubectl get events


9. Prevention and Best Practices

  • Set realistic CPU and memory requests on every container, and use LimitRange defaults as a safety net.

  • Treat hard scheduling rules as requirements only, and use soft rules for preferences.

  • Keep headroom in every node pool and zone, and let the Cluster Autoscaler or an equivalent add nodes when Pods are Pending for capacity reasons.

  • Make sure labels and taints on node pools match what workloads require.

  • Alert on Pods Pending longer than a few minutes, and on FailedScheduling events, instead of waiting for users to report problems.

  • Use a StorageClass with WaitForFirstConsumer for zonal volumes.

  • Keep quotas, Pod Security levels and admission policies documented so that teams know why a Pod was rejected.

  • Record common causes and fixes in a team runbook that uses the commands from this article.

10. Conclusion

A Pending Pod always has a reason, and Kubernetes usually tells you what it is in the Pod events. The fastest path is to check whether a node has been assigned. If not, read the FailedScheduling message and compare it with requests, labels, taints, affinity rules, spread constraints, volumes and quotas. If a node is assigned, look at the kubelet side: images, volumes and the network. If no Pod exists, look at the ReplicaSet and admission. With this method, most Pending Pods can be diagnosed in a few minutes.