← Back to list

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…

Subhra Jyoti Paul · 2026-08-27 03:49 · 46 claps · 9.0 min read
#kubernetes #kubernetes-cluster #kubernetes-scheduler #kubernetes-operator
Open on Medium ↗
Wiki topics: TLS · Design Tools & Workflow ☁️ · DevOps & Cloud 📐 · Mathematics

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