Kubernetes Scheduler Deep Dive: How Pods Find the Right Node
Understand how Kubernetes schedules Pods using filtering, scoring, taints and tolerations, node affinity, resource requests, topology…
Kubernetes Scheduler Deep Dive: How Pods Find the Right Node
Understand how Kubernetes schedules Pods using filtering, scoring, taints and tolerations, node affinity, resource requests, topology constraints, and the Scheduling Framework.

When you create a Pod, you usually don’t tell Kubernetes which Node should run it.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
So who decides where this Pod runs?
The answer is **kube-scheduler**.
The scheduler is a control-plane component responsible for finding a suitable Node for unscheduled Pods. It filters out Nodes that cannot run the Pod, scores the remaining candidates, and binds the Pod to the selected Node.
The interesting part is what happens between:
Pod created
↓
Which Node?
Let’s follow that process from the inside.
1. Where the Scheduler Fits
A simplified Kubernetes flow looks like this:
kubectl apply
│
▼
API Server
│
▼
Pod
nodeName = empty
│
▼
kube-scheduler
│
Choose a Node
│
▼
API Server
│
▼
Pod assigned
to a Node
│
▼
Kubelet
│
▼
Container Runtime
The scheduler does not run the container. Its job ends with the placement decision. The kubelet on the selected Node is responsible for turning that Pod specification into running containers.
2. How Does the Scheduler Know a Pod Needs Scheduling?
A newly created Pod normally has no spec.nodeName.
For example:
spec:
containers:
- name: nginx
image: nginx
The scheduler watches for Pods that have not yet been assigned to a Node. Internally, Pods waiting for scheduling are maintained in the scheduler’s scheduling queues. A simplified view is:
API Server
│
▼
Scheduler watches
│
▼
Scheduling Queue
│
┌───────┴────────┐
▼ ▼
Active Queue Backoff Queue
│
▼
Scheduler
Worker
Current Kubernetes scheduler internals include an active queue, backoff handling, and an unschedulable set. Queueing behavior determines when Pods should be retried after cluster changes.
The important idea is: The scheduler doesn’t continuously scan every Pod from scratch. It maintains scheduling state and processes Pods through its scheduling queue.
3. The Scheduling Cycle
Once the scheduler picks a Pod from the queue, it starts a scheduling cycle.
At the highest level:
Pending Pod
│
▼
Pre-processing
│
▼
Filtering
│
▼
Scoring
│
▼
Select Node
The modern scheduler implements this using the Scheduling Framework, which provides extension points such as PreFilter, Filter, PostFilter, PreScore, Score, Reserve, Permit, PreBind, and Bind.
The two most important decisions remain:
FILTER → Which Nodes can run this Pod?
SCORE → Which feasible Node is preferable?
4. PreFilter — Prepare for Scheduling
Before checking individual Nodes, the scheduler can perform work that applies to the Pod as a whole. This is the PreFilter stage.
Conceptually:
Pod
│
▼
PreFilter
│
├── Validate requirements
├── Calculate shared state
└── Prepare scheduling information
│
▼
Filter Nodes
PreFilter plugins can also reject a Pod before the scheduler evaluates Nodes.
You don’t need to think of PreFilter as choosing a Node. It prepares the information required for the actual placement decision.
5. Filter — Eliminate Nodes That Cannot Run the Pod
This is the first major decision. Imagine the cluster contains:
worker-01
worker-02
worker-03
worker-04
worker-05
The scheduler evaluates whether each Node satisfies the Pod’s requirements.
For example:
worker-01 ❌ insufficient CPU
worker-02 ❌ taint not tolerated
worker-03 ✅
worker-04 ❌ node affinity mismatch
worker-05 ✅
After filtering:
Feasible Nodes:
worker-03
worker-05
A feasible Node is simply a Node on which the Pod can be scheduled according to the applicable constraints.
Filtering is therefore a hard decision:
Can the Pod run here?
YES → keep the Node
NO → eliminate the Node
6. What Does Filtering Check?
The scheduler considers many different constraints.
Resource Requests
Suppose the Pod requests:
resources:
requests:
cpu: "2"
memory: "4Gi"
The scheduler checks whether the Node has enough allocatable resources to satisfy those requests. Importantly, scheduling is based on resource requests, not current CPU or memory utilization.
For example:
Node allocatable CPU: 8 cores
Existing requests: 6 cores
New Pod request: 2 cores
6 + 2 = 8
→ Node can satisfy the request
The scheduler uses resource requests when making placement decisions, while the kubelet and container runtime enforce limits during execution.
Node Selector
A Pod can require specific Node labels:
nodeSelector:
disktype: ssd
Only Nodes with:
disktype=ssd
are eligible. nodeSelector is the simplest form of Node selection constraint.
Node Affinity
Node affinity provides more expressive constraints.
For example:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- zone-a
Here, the requirement is hard:
Node must be in zone-a
There is also:
preferredDuringSchedulingIgnoredDuringExecution
which represents a preference rather than a hard requirement. That distinction is important:
required → must satisfy
preferred → try to satisfy
Preferred rules can therefore influence scoring rather than eliminate every non-matching Node.
7. Taints and Tolerations
Taints provide another way for Nodes to repel Pods. Suppose:
worker-gpu
taint:
gpu=true:NoSchedule
A Pod without the corresponding toleration cannot be scheduled there. A Pod with:
tolerations:
- key: gpu
operator: Equal
value: "true"
effect: NoSchedule
can pass that taint-related constraint. Taints and tolerations are commonly used for dedicated Nodes or specialized hardware such as GPUs.
The important distinction is:
Taint → Node says "keep Pods away"
Toleration → Pod says "I can tolerate this taint"
A toleration by itself does not force a Pod onto that Node; it only removes the taint as a reason for rejection.
8. Pod Affinity, Anti-Affinity and Topology
Scheduling can also depend on where other Pods are running.
For example:
Pod A
│
└── application=frontend
A new Pod could request:
"Prefer to run near frontend Pods"
That’s Pod affinity.
Or:
"Don't run near frontend Pods"
That’s Pod anti-affinity.
The scheduler can evaluate these relationships across topology domains such as:
Node
Rack
Zone
Region
Kubernetes also provides topology spread constraints for distributing Pods across failure domains. This means scheduling is not simply:
Does the Node have enough CPU?
It can be a much larger constraint problem involving:
Resources
+ Labels
+ Affinity
+ Anti-affinity
+ Taints
+ Topology
+ Storage
+ Other scheduling constraints
9. Scoring — Which Feasible Node Is Better?
Suppose filtering leaves:
worker-03 ✅
worker-05 ✅
Both can run the Pod. Now the scheduler needs to decide which one is preferable. That’s the purpose of scoring.
worker-03 → 72
worker-05 → 91
Winner → worker-05
The scheduler runs scoring plugins against the feasible Nodes and combines their scores according to the configured plugin weights.
The key distinction is:
Filter:
"Can I run here?"
Score:
"How desirable is this Node?"
10. Scheduler Plugins
The scheduler’s behavior is implemented through plugins.
Some built-in plugins include:
NodeResourcesFit
NodeAffinity
TaintToleration
PodTopologySpread
NodePorts
ImageLocality
VolumeBinding
They participate in different scheduling stages.
For example:
NodeAffinity
→ Filter
→ Score
TaintToleration
→ Filter
→ Score
NodeResourcesFit
→ Filter
→ Score
PodTopologySpread
→ Filter
→ Score
The exact plugin configuration determines which plugins participate at which extension points.
This plugin architecture is what makes the modern scheduler extensible instead of hard-coding every scheduling policy into one large algorithm.
11. The Scheduling Framework
The complete Scheduling Framework is more detailed than just:
Filter → Score → Bind
A simplified pipeline is:
Scheduling Queue
│
▼
PreEnqueue
│
▼
QueueSort
│
▼
PreFilter
│
▼
Filter
│
┌──────┴──────┐
│ │
feasible none feasible
│ │
▼ ▼
PreScore PostFilter
│ │
▼ Preemption
Score
│
▼
NormalizeScore
│
▼
Reserve
│
▼
Permit
│
▼
PreBind
│
▼
Bind
│
▼
PostBind
Not every plugin implements every stage. The framework provides these extension points so that scheduling behavior can be composed from independent plugins.
12. Scheduling Cycle vs Binding Cycle
One subtle but important detail is that Kubernetes separates scheduling from binding.
Scheduling cycle
Determines:
Which Node should run this Pod?
Binding cycle
Carries out the decision:
Record that the Pod belongs to that Node.
Kubernetes runs scheduling cycles serially, while binding cycles can execute concurrently.
Conceptually:
Scheduling Cycle
│
▼
Select worker-03
│
▼
Binding Cycle
│
▼
Bind Pod → worker-03
13. Reserve and Permit
Between selecting a Node and binding the Pod, the Scheduling Framework provides additional control points.
Reserve
Reserve allows a plugin to reserve resources or state for the Pod on the selected Node before binding.
Select Node
↓
Reserve
↓
Permit
↓
Bind
If a later stage fails, the framework can run the corresponding unreserve logic to clean up the reservation.
Permit
Permit can delay or reject binding.
This is useful for scheduling mechanisms that need coordination between multiple Pods.
You don’t need to think of these as additional Node-selection algorithms. The Node has already been selected; these stages control what happens before the final binding.
14. What If No Node Is Suitable?
Suppose filtering produces:
worker-01 ❌
worker-02 ❌
worker-03 ❌
worker-04 ❌
There are no feasible Nodes. The scheduler doesn’t bind the Pod. The Pod remains unscheduled and can be retried when conditions change. The framework’s PostFilter stage is invoked when filtering produces no feasible Nodes. A common PostFilter implementation is preemption.
15. Preemption
Imagine:
Node
──────────────────────
Pod A priority: 10
Pod B priority: 20
Pod C priority: 30
──────────────────────
New Pod
priority: 100
The new Pod cannot fit.
The scheduler can consider whether removing lower-priority Pods would make the new Pod schedulable.
Conceptually:
High-priority Pod
│
▼
No feasible Node
│
▼
Consider preemption
│
▼
Select lower-priority victims
│
▼
Free resources
│
▼
Schedule high-priority Pod
Preemption is therefore a recovery mechanism when normal filtering cannot find a feasible placement.
16. Binding — Making the Decision Real
Eventually, the scheduler selects a Node. For example:
Pod nginx
↓
worker-03
The scheduler then performs the binding operation through the API server. Conceptually:
kube-scheduler
│
▼
API Server
│
▼
Pod assigned to worker-03
The kubelet on worker-03 then observes the Pod assignment and takes over the execution side:
API Server
│
▼
Kubelet
│
▼
Container Runtime
│
▼
Containers
The scheduler’s placement responsibility is complete once the Pod is bound.
17. A Complete Example
Consider a Pod with:
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
nodeSelector:
workload: production
resources:
requests:
cpu: "2"
memory: "4Gi"
containers:
- name: web
image: nginx
Suppose the cluster contains:
worker-01
workload=general
CPU available: 4
worker-02
workload=production
CPU available: 1
worker-03
workload=production
CPU available: 4
The scheduler evaluates the Nodes.
Step 1 — Node selector
worker-01 → ❌ workload mismatch
worker-02 → ✅
worker-03 → ✅
Step 2 — Resources
The Pod requests:
2 CPU
4Gi memory
Assume:
worker-02 → ❌ only 1 CPU available
worker-03 → ✅ enough resources
Now only:
worker-03
remains feasible.
Step 3 — Score
There is only one feasible Node:
worker-03 → selected
Step 4 — Bind
Pod
↓
worker-03
Step 5 — Kubelet
worker-03 Kubelet
↓
Container Runtime
↓
nginx container
The entire decision can therefore be summarized as:
Pod
│
▼
Queue
│
▼
PreFilter
│
▼
Filter
│
├── worker-01 ❌
├── worker-02 ❌
└── worker-03 ✅
│
▼
Score
│
▼
worker-03
│
▼
Bind
│
▼
Kubelet
18. What “Best Node” Actually Means
The scheduler is not trying to find a universally perfect Node. It solves a constrained placement problem:
First:
Which Nodes are valid?
Then:
Which valid Node is preferable?
Finally:
Bind the Pod to the selected Node.
That distinction is important. A Node can have plenty of CPU but still be rejected because:
Taint
Affinity
Topology
Ports
Storage
Resource requirements
make it unsuitable. Likewise, several Nodes can be valid, and scoring determines which one is preferred.
19. Scheduler Performance in Large Clusters
With a small cluster:
10 Nodes
evaluating every Node is inexpensive. With thousands of Nodes, scheduler performance becomes more important.
Kubernetes provides percentageOfNodesToScore, which allows the scheduler to stop looking for additional feasible Nodes after reaching a configured percentage of the cluster. This trades some search breadth for scheduling performance. The scheduler also has mechanisms for reusing scheduling-related information for compatible Pods, reducing repeated work in suitable workloads.
The important takeaway is: Scheduler performance is not only about the complexity of the filtering rules; it is also about how many Nodes and Pods those rules must evaluate.
20. The Complete Mental Model
Put everything together:
POD
│
▼
Scheduling Queue
│
▼
PreFilter
│
▼
FILTER
│
┌───────────┴───────────┐
│ │
Rejected Feasible
Nodes Nodes
│ │
X ▼
PreScore
│
▼
SCORE
│
▼
Best Node
│
Reserve
│
Permit
│
PreBind
│
Bind
│
▼
API Server
│
▼
Kubelet
│
▼
Container Runtime
│
▼
Running Pod
The scheduler can therefore be reduced to one core idea:
Pod
│
▼
"Where can I run?"
│
▼
FILTER
│
▼
"Where should I run?"
│
▼
SCORE
│
▼
"Make it happen."
│
▼
BIND
Final Takeaway
Kubernetes scheduling is not simply: “Find a Node with enough CPU.”
It is a multi-stage decision process.
The scheduler first determines which Nodes are feasible based on resources and scheduling constraints. It then scores those candidates using scheduling plugins, selects a Node, and finally binds the Pod to it.
Once binding is complete, the scheduler’s job is done.
The kubelet takes over:
kube-scheduler
│
│ placement
▼
API Server
│
│ assigned Pod
▼
Kubelet
│
▼
Container Runtime
│
▼
Running Pod
So the next time you run:
kubectl apply -f pod.yaml
and see:
pod/web created
remember that the Pod still has a journey ahead of it:
API Server
↓
Scheduling Queue
↓
PreFilter
↓
Filter
↓
Score
↓
Reserve / Permit / PreBind
↓
Bind
↓
Kubelet
↓
Container Runtime
↓
Running Pod
The scheduler doesn’t run your Pod. It solves the placement problem that determines where your Pod will run.
Official Kubernetes References
메타데이터
- post_id
- f876bcefee87
- slug
- kubernetes-scheduler-deep-dive-how-pods-find-the-right-node-f876bcefee87
- url
- https://medium.com/@subhrajyotipaul06/kubernetes-scheduler-deep-dive-how-pods-find-the-right-node-f876bcefee87
- canonical_url
- https://medium.com/@subhrajyotipaul06/kubernetes-scheduler-deep-dive-how-pods-find-the-right-node-f876bcefee87
- author_url
- https://medium.com/@subhrajyotipaul06
- status
- ok
- fetched_at
- 2026-09-06 22:12:07