2026
The Kubernetes Scheduler: How Pods Find Their Nodes
A pod without a node assignment sits in Pending, waiting. The scheduler is what resolves that. It filters out nodes that cannot work, scores the ones that can and binds the pod to the best match. Understanding how it makes that decision explains most placement behaviour in a cluster.
When you create a pod in Kubernetes, it does not immediately know which node it will run on. For a brief moment it exists in a kind of limbo: the API server has accepted it, stored it in etcd, but no node has been assigned. The pod is in Pending state and waiting for someone to make a decision.
That someone is the scheduler. It watches the API server for pods without a node assignment, evaluates which nodes could host them and then picks the best one. The whole process happens in under a second for most pods. It is quiet enough that most people running workloads in Kubernetes never think about it. But when something goes wrong and pods sit in Pending, the scheduler is almost always where the answer is.
The scheduler is not a black box. It follows a documented algorithm made up of configurable plugins. Understanding what it does at each step explains why pods land where they do, why some pods cannot be placed at all and how tools like node affinity, taints and resource requests actually influence placement decisions.
How the scheduler makes a decision
The scheduler works in two main phases for every pod it processes. First it eliminates nodes that cannot possibly work. Then it ranks the remaining nodes to find the best fit. A third step writes the result.
Filtering
The scheduler runs every node in the cluster through a set of filter plugins. Each plugin asks a yes or no question about whether the node can run this pod. Does it have enough CPU? Enough memory? Does the pod tolerate the node's taints? Does the pod's node affinity match this node's labels? Any node that fails a filter is removed from consideration. The result is a list of feasible nodes.
Scoring
Each feasible node is given a score by a set of scoring plugins. Plugins reward nodes for having more available resources, for already running images the pod needs, for being in the same zone as other pods the pod wants to be near and for many other factors. Scores are normalised and summed. The node with the highest total score wins.
Binding
The scheduler writes a Binding object to the API server, associating the pod with the chosen node. The kubelet on that node notices the pod is now assigned to it and begins pulling images and starting containers. From the scheduler's perspective the job is done. It moves on to the next unscheduled pod.
Worth noting
If the filtering phase produces zero feasible nodes, the pod cannot be scheduled. It stays in Pending and the scheduler logs a reason. The reason is visible in the pod's events via kubectl describe pod. Reading the events is the fastest way to understand why a pod is stuck. The message is usually specific enough to point directly at the problem.
Filter plugins
The filtering phase runs a series of plugins against each node. Every plugin must pass for the node to remain eligible. The plugins are the implementation of the rules you set through resource requests, affinity rules, taints and volume claims. They translate your intent into scheduling decisions.
NodeResourcesFitChecks that the node has enough CPU and memory to satisfy the pod's resource requests. A node with insufficient capacity is filtered out regardless of anything else.
NodeAffinityEvaluates the pod's node affinity rules against the node's labels. Required affinity rules cause nodes that do not match to be filtered. Preferred rules are handled in the scoring phase.
TaintTolerationChecks that the pod tolerates every taint present on the node. A single untolerated taint with NoSchedule or NoExecute effect causes the node to be filtered.
NodePortsVerifies that the host ports the pod needs are not already in use on the node. A pod that requires port 9100 on the host cannot land on a node where another pod already holds that port.
VolumeBindingChecks that the persistent volumes the pod needs can be bound to the node. For volumes with topology constraints, only nodes in the correct zone qualify.
PodTopologySpreadEnforces topology spread constraints. If a pod declares that its replicas should spread evenly across zones, nodes in zones that are already over their limit are filtered out.
How nodes are scored
Once the filter phase produces a set of feasible nodes, the scoring phase ranks them. The scheduler runs each feasible node through a set of scoring plugins. Each plugin returns a score between zero and one hundred for the node. Scores from all plugins are combined using plugin weights to produce a final score per node.
The most influential default scoring plugin is LeastAllocated. It favours nodes that have more available CPU and memory relative to their total capacity. This spreads pods across the cluster rather than piling them onto a small number of nodes. Other plugins reward nodes that already have the container images the pod needs cached locally, which avoids pulling large images over the network when an equivalent node already has them.
Preferred node affinity rules are also evaluated during scoring. A node that satisfies a preferred affinity expression gets a bonus proportional to the weight assigned to that expression in the pod spec. A pod with a strong preference for a particular zone will reliably land there as long as nodes in that zone have capacity, but it will still be placed elsewhere rather than staying Pending if they do not.
The scheduling queue
The scheduler processes one pod at a time from a priority queue. Pods with higher priority values are scheduled before lower-priority ones. When the cluster has enough resources to accommodate all pending pods, priority does not matter much. It becomes significant when resources are tight and the scheduler must choose which pod to place next.
PriorityClass objects define priority levels. A pod references a PriorityClass by name in its spec. The scheduler uses the integer value associated with that class to order its queue. Critical system components typically use a PriorityClass with a very high value, ensuring they are placed ahead of application workloads when both are pending simultaneously.
PriorityClass for critical workloads
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Critical application workloads"
When preemptionPolicy is set to PreemptLowerPriority, a high-priority pod that cannot be scheduled due to resource constraints can cause the scheduler to evict lower-priority pods to free up space. This is called preemption. The evicted pods are rescheduled when resources become available again. It is a useful tool for ensuring critical workloads are never blocked by less important ones, but it should be used deliberately since it can disrupt running pods.
Why pods stay in Pending
A pod in Pending is almost always a scheduling failure. The most direct way to diagnose it is kubectl describe pod, which shows events at the bottom of the output. The scheduler writes a clear message when it cannot place a pod. These are the most common reasons.
Insufficient resources
The most common reason. No node has enough free CPU or memory to satisfy the pod's requests. The scheduler logs this as Insufficient cpu or Insufficient memory. The fix is either to reduce requests, add nodes or remove other workloads. Lowering resource limits does not help because the scheduler uses requests, not limits.
No nodes match affinity rules
A required node affinity rule that no node in the cluster satisfies will keep the pod Pending forever. This usually means the label being matched does not exist on any node, the value is wrong, or the nodes that should have the label were not labelled correctly. Checking kubectl get nodes --show-labels is the first step.
Untolerated taints
If all nodes carry a taint that the pod does not tolerate, the filtering phase eliminates every node and no binding happens. In smaller clusters this often means every node is a control plane node and the pod is missing the control-plane toleration.
PVC cannot be bound
A pod with a PersistentVolumeClaim that cannot be satisfied will not be scheduled. Either no PersistentVolume matches the claim's storage class and access mode requirements, or the volume exists but is in the wrong zone. The pod stays Pending until the claim is bound.
Where it fits
The scheduler sits between the API server and the kubelets. It watches for work from the API server and hands off assignments to nodes. It does not run containers, manage storage or handle networking. Its only job is deciding where pods go. Everything else in the system depends on that decision being correct.
Most of the configuration surface that Kubernetes exposes for influencing pod placement goes through the scheduler. Resource requests and limits tell it how much capacity a pod needs. Node affinity tells it which nodes are acceptable. Taints and tolerations tell it which nodes are off-limits. Topology spread constraints tell it how to distribute pods across failure domains. None of those features do anything on their own. They are all instructions that the scheduler reads and acts on.
For teams running workloads that have specific placement needs, understanding the scheduler makes the other configuration tools more intuitive. A node affinity rule does not directly move a pod. It changes the output of the filter phase for any pod that carries it. A resource request does not reserve CPU on a node. It sets the threshold that the NodeResourcesFit filter uses to decide whether the node qualifies. The scheduler is the engine. Everything else is configuration fed into it.