What Is the Scheduler in Kubernetes?

What Is the Scheduler in Kubernetes?

A beginner-friendly guide to the part of Kubernetes that decides where every pod lives: how it chooses, what rules it follows, how you can steer it, and how to fix a pod that will not find a home. Every new word is explained the moment it appears.

Imagine you arrive at a huge hotel late at night with a group of friends. The person at the front desk does not walk you to your room or carry your bags. Instead, they look at a board showing every room, remove rooms that are already taken or too small, prefer rooms that keep your group together, and finally hand you a key. Once the choice is made, you walk to the room yourself. Without that desk, guests would wander the corridors, knocking on doors, and some rooms would be crowded while others stayed empty.

In Kubernetes, that front desk is called the scheduler. Every time a new pod is created, someone has to decide which machine in the cluster should run it. Making that choice fairly, quickly, and wisely, thousands of times a day, is a surprisingly hard puzzle. This guide explains the puzzle, the method the scheduler uses to solve it, the knobs you can turn, and the mistakes to avoid, always starting from zero.

AThe Short Answer: What Is the Scheduler?

If you only remember one paragraph, make it the next one.

The kube-scheduler is a control plane program that watches for newly created pods that have no machine assigned, and picks the best machine, called a node, for each one. A pod is the smallest unit Kubernetes runs, a small wrapper around one or more containers. A container is a sealed package holding an application and everything it needs. The scheduler makes a decision and records it. It does not start the containers. Another program on the chosen node, the kubelet, does that.

Analogy

The scheduler is the front desk clerk of a hotel. The clerk knows every room, every guest’s special requests, and which floors are already crowded. The clerk decides which key to hand out, but never carries luggage. The guest, meaning the kubelet, walks to the room and settles in.

What the scheduler does in three steps

1

Find the homeless pods

It watches the cluster for pods whose record has no node name filled in.

2

Choose the best node

It removes nodes that cannot run the pod, then gives points to the rest and picks the winner.

3

Write down the decision

It tells the API server the pod belongs to that node. This step is called binding.

2
Main phases of a decision: filter, then score
0-100
Score range each rule gives a node
1
Pod is decided at a time, one after another

How the scheduler differs from its neighbours

Beginners often blur the scheduler together with other Kubernetes parts, so it helps to draw the borders clearly. The controller manager creates and deletes pods to match the count you want, but it does not choose their location. The scheduler chooses the location, but does not create or delete anything. The kubelet starts and stops the containers on its own node, but has no say about which pods it receives. Finally, the API server stores and shares every decision, but makes none of these choices itself.

Because each part does exactly one job, you can reason about problems easily. If pods are missing, look at the controllers. If pods exist but have no node, look at the scheduler. If pods have a node but the containers never start, look at the kubelet and the image. This simple habit of asking “which part owns this step?” solves a surprising share of real debugging sessions.

i
Remember this

The scheduler answers only one question: “on which node should this pod start?” It never moves a running pod, never restarts anything, and never creates pods. Its job ends at the moment it writes the decision.

BWhere the Scheduler Fits and Why the Puzzle Is Hard

To appreciate the scheduler, we first need to see its neighbours and the problem it faces.

A quick look at the cluster

A Kubernetes cluster is a group of computers working as one. The computers that make decisions form the control plane. The computers that run your applications are worker nodes. The control plane holds an API server, which is the single front door, a database called etcd, which stores all records, controller programs that keep things tidy, and our scheduler. Each worker node has a kubelet, an agent that starts containers on instructions.

flowchart TB
    U["Engineer or Deployment controller creates a Pod"] --> API["kube-apiserver"]
    API  ETCD["etcd database"]
    SCH["kube-scheduler"] -->|"1. Watch for Pods with no node"| API
    SCH -->|"2. Filter and score nodes"| SCH
    SCH -->|"3. Bind Pod to chosen node"| API
    API -->|"4. Pod now assigned"| KL1["kubelet on Node 1"]
    KL1 --> RT["Container runtime starts containers"]
    KL2["kubelet on Node 2"]
    KL3["kubelet on Node 3"]
    API --- KL2
    API --- KL3
Figure 1. The scheduler sits beside the API server. It reads homeless pods, decides, and writes back the chosen node.

Why is this hard?

Placing one pod on one node sounds easy. But real clusters have hundreds or thousands of nodes of different sizes, and pods arrive constantly with different needs. Each pod may want a certain amount of processor time, a certain amount of memory, a special kind of machine, or a place close to other pods, or far from them. The scheduler must respect all these wishes at once, and decide in a fraction of a second, because many pods are waiting.

Analogy

The scheduling puzzle is like playing Tetris with pieces of many sizes and falling from every direction, while the board itself keeps changing. Fitting one piece is easy. Fitting all pieces without leaving wasted gaps, without creating fragile towers, and without being too slow is the real challenge. Computer scientists call this family of puzzles bin packing, and finding the perfect answer is very hard, so the scheduler uses fast, sensible rules instead of searching forever.

What a good placement achieves

Goal 1

Fit

The pod must have enough processor, memory, and other resources on its node, or it will fail or slow its neighbours.

Goal 2

Safety

Copies of the same app should be spread out, so one broken node or one broken zone cannot take them all down.

Goal 3

Efficiency

Machines should be used well, so you do not pay for many half-empty servers.

Goal 4

Rules

Special pods must land on special machines, such as those with graphics cards, and ordinary pods must stay away from them.

How the scheduler knows what is happening

The scheduler does not visit each node to look around. It learns about the cluster the same way other control plane parts do, by watching records in the API server. It keeps a private, always-updating copy of every node, every pod, and every relevant storage object in its memory, often called its cache. When a node joins, leaves, or changes a label, or when a pod is created or deleted, the news arrives through a watch, and the cache is updated within moments. Decisions are made against this cache, which is why they are fast.

The cache can be slightly behind reality, for example just after a node has failed. That is acceptable, because the final binding is checked by the API server, and errors are retried. If the chosen node vanished in the meantime, the binding fails, the assumed reservation is removed, and the pod returns to the queue to be decided again.

i
Good to know

The goals sometimes pull in opposite directions. Packing tightly saves money but concentrates risk. Spreading widely is safer but leaves more unused space. The scheduler’s scoring rules exist to balance these forces, and you can adjust the balance.

CInternal Working: Inside the Scheduling Cycle

Behind each placement is a tidy sequence of steps. Learning them makes every later topic easy.

The waiting line: scheduling queues

New pods do not go straight to being placed. They join a waiting line called the scheduling queue, which actually has three parts. The active queue holds pods ready to be tried now, sorted by priority so that important pods go first. The backoff queue holds pods that just failed and must wait a short time, which grows with each failure, before trying again. The unschedulable pool holds pods that cannot fit anywhere right now and are waiting for the cluster to change, such as a new node arriving or another pod leaving.

The two cycles

For each pod taken from the active queue, the scheduler runs two cycles. The scheduling cycle chooses the node and always handles one pod at a time, in order. The binding cycle writes the decision to the API server and can run in the background for many pods at once, because talking to the API server takes time and the scheduler does not want to sit idle. To keep going without waiting, the scheduler first pretends the pod is already on the node inside its own memory. This trick is called assuming the pod, and it stops the next decision from using space that was just promised.

flowchart LR
    Q["Scheduling queue: active, backoff, unschedulable"] --> PF["PreFilter: prepare data"]
    PF --> F["Filter: remove nodes that cannot run the Pod"]
    F --> OK{"Any node left?"}
    OK -->|"No"| PO["PostFilter: try preemption"]
    PO -->|"Failed"| UQ["Back to queue and wait"]
    OK -->|"Yes"| SC["Score: rate remaining nodes 0 to 100"]
    SC --> PICK["Pick highest total score"]
    PICK --> RES["Reserve: assume the Pod is there"]
    RES --> PER["Permit: last approval"]
    PER --> BIND["Bind: write nodeName via API server"]
Figure 2. The full scheduling flow. The first half decides. The last three boxes make the decision real.

Extension points: the plug-in sockets

Modern Kubernetes builds the scheduler out of small plug-ins. The steps in the diagram are called extension points, like sockets into which plug-ins connect. Filtering plug-ins remove nodes. Scoring plug-ins award points. Others prepare data, reserve space, or wait for approval. This design is called the scheduling framework, and it lets the community and companies add new behaviour without rewriting the scheduler.

Analogy

Think of a wedding planner seating guests. First comes a screening pass that removes tables that are full or wrongly placed. Then comes a preference pass that awards points for tables near friends and far from feuding relatives. Finally the planner writes the name on the table card. Each pass is an extension point, and different helpers can be in charge of each pass.

i
Where it listens

The scheduler is a normal program that talks to the API server over encrypted connections. It also serves its own health and metrics on a secure port, 10259 by default, which monitoring tools use.

DPhase One: Filtering, Removing the Impossible

Filtering asks a strict yes-or-no question about every node: can this pod possibly run here?

The scheduler checks all nodes, or a sample of them in very large clusters, against a list of filter rules. A node that fails any single rule is thrown out at once. The nodes that pass every rule are called feasible nodes. If no feasible node exists, the pod cannot be placed now.

The common filters

FilterQuestion it asksExample of a failure
Resources fitIs there enough unreserved processor, memory, and storage?Pod needs 2 GB but the node has 1 GB left unreserved
Node nameIf the pod names a node, is this that node?Pod insists on node seven, this is node three
Node portsIs the network port the pod needs already taken on this node?Two pods both need port 8080 on the host
UnschedulableHas this node been closed to new pods?Node was cordoned for maintenance
Node affinity and selectorsDoes the node carry the required labels?Pod needs a graphics card machine, node has none
Taints and tolerationsDoes the node repel this pod?Node is reserved for special jobs and the pod has no permission
Volume rulesCan the pod’s storage be attached here, in the right zone, within limits?Disk lives in Zone A but the node is in Zone B
Pod affinity and anti-affinityWould placing here break a togetherness or separation rule?Pod must not share a node with another copy of itself
Topology spreadWould this make the spread across zones too uneven?Zone A already holds far more copies than Zone B

A filtering example with real numbers

Suppose a pod requests half a processor core and 1 gigabyte of memory. Node one has 4 cores and 8 gigabytes allocatable, and its running pods already request 3.8 cores and 5 gigabytes. Only 0.2 cores remain, so node one fails the resource filter even though it has plenty of memory. Node two has 2 cores and 4 gigabytes allocatable, with 1 core and 2 gigabytes already requested. It has 1 core and 2 gigabytes left, so it passes. Node three is healthy and roomy, but carries a taint the pod does not tolerate, so it fails the taint filter. Only node two remains feasible.

Notice how many different reasons can remove a node. A node is not rejected because it is “bad”. It is rejected because it does not match one particular need of this particular pod. The same node might be a perfect choice for the next pod in line.

Analogy

Filtering is like a job application screen. Before anyone is interviewed, applicants without the required degree, licence, or work permit are removed from the pile. Nobody is ranked yet. The screen only decides who is allowed to stay in the race.

Large clusters: not every node is checked

Checking every node for every pod would waste time in a huge cluster. So the scheduler stops looking once it has found enough feasible nodes. The share it looks for is controlled by a setting named percentageOfNodesToScore. If you do not set it, Kubernetes chooses an adaptive number: about half the nodes in a small cluster, shrinking as the cluster grows, with a floor of 5 percent, and always at least 100 feasible nodes. In a 5,000-node cluster this works out to roughly 10 percent. The scheduler also walks through the node list in rotation, so different pods start the search at different places and the load stays fair.

!
Watch out

Because of this shortcut, the scheduler may pick the best node among a sample rather than the best node in the whole cluster. That is a deliberate trade of a tiny loss in perfection for a big gain in speed.

EPhase Two: Scoring, Picking the Best of the Rest

After filtering, several nodes may all be acceptable. Scoring decides which one is best.

Each scoring rule looks at every feasible node and gives it a number from 0 to 100. The scheduler multiplies each rule’s number by a weight, which says how much that rule matters, adds the results, and picks the node with the highest total. If two nodes tie, one is chosen at random so that no node is treated unfairly.

The most important scoring rules

Resources

Least allocated

Prefers nodes with the most free room. This spreads pods out, and it is the default strategy.

Resources

Most allocated

The opposite. It prefers already busy nodes to pack pods tightly, which lets empty nodes be switched off to save money.

Balance

Balanced allocation

Prefers nodes where processor and memory use stay in step, so a node does not run out of one while wasting the other.

Speed

Image locality

Prefers nodes that have already downloaded the container image, so the pod starts sooner.

Together

Inter-pod affinity

Awards points for being near pods the new pod likes, and takes them away for pods it should avoid.

Labels

Node affinity

Awards points to nodes that match the pod’s preferred, but not required, labels.

Repulsion

Taint toleration

Prefers nodes with fewer soft taints that the pod merely puts up with.

Spread

Topology spread

Prefers nodes in the least crowded zone or rack, keeping copies of an app evenly distributed.

Analogy

Scoring is the interview after the screening. Each remaining candidate is rated on skills, experience, location, and team fit, and each category carries a different weight. The candidate with the best total wins. If the company cares more about location than experience, it simply gives location a larger weight.

A small worked example

Suppose three nodes survived filtering. Node A is empty and has the image already downloaded. Node B is half full. Node C is nearly full but is in a zone with few copies of the app. With the default rules, Node A gets high marks for free room and image locality. Node C gets high marks for spreading but low marks for free room. Node B is average on everything. Most of the time, Node A wins, unless the topology spread rule has a heavy weight or a hard requirement, in which case Node C could win. The point is that the answer depends on weights, and an engineer can read the rules and predict the outcome.

i
Change the personality

Because scoring is weight-based, you can give the scheduler a different personality. A cost-saving cluster may prefer packing pods tightly. A reliability-focused cluster may prefer spreading. Both are normal, and neither is wrong. They are different business choices.

FResource Requests and Limits: What the Scheduler Really Reads

The scheduler is a careful accountant, and it decides using the numbers you write down, not the numbers your program actually uses.

Requests and limits

Every container can declare two amounts for processor time and memory. A request is the amount the container needs to run properly, a guaranteed reservation. A limit is the ceiling it may not cross. The scheduler looks only at the requests. It adds up the requests of all pods already on a node and checks whether the new pod’s requests still fit in what remains. Limits matter later, when the node itself enforces them.

Analogy

Think of booking seats on a bus. The request is the number of seats you reserve, and the conductor uses that number to decide whether the bus is full. The limit is the largest number of people your group may ever bring on board. The conductor decides who boards based on reservations, not on how many people actually show up.

Allocatable: the space that can really be booked

A node does not offer all of its capacity to pods. Some is kept for the operating system, for the kubelet, and for a safety margin. What remains is called allocatable. The scheduler subtracts the total requests of running pods from the allocatable amount to see what is still free. This is why a node with 16 gigabytes of memory may show only about 14 gigabytes available for pods.

Quality of service classes

Kubernetes sorts pods into three classes, based on how requests and limits are written. The class decides who is removed first when a node runs short of memory.

ClassHow you get itTreatment under pressure
GuaranteedEvery container has requests equal to limits for both processor and memoryProtected, and removed last
BurstableAt least one request or limit set, but not fully equalMiddle, removed after BestEffort pods
BestEffortNo requests or limits at allRemoved first, and hard to schedule fairly

Pods with several containers

Many pods hold more than one container, and some hold special short-lived init containers that run one after another before the main containers start, for example to prepare files. The scheduler adds up the requests of all the main containers, then compares that sum with the largest single init container, because init containers do not run at the same time as the main ones. It uses the bigger of the two numbers as the pod’s total need. Some clusters also add a fixed pod overhead, an extra allowance for the sandbox around the containers, especially when using stronger isolation runtimes. Understanding this arithmetic explains why a pod with a tiny main container can still refuse to fit on a small node.

!
The honest number matters

If you set requests too low, the scheduler crowds too many pods onto a node and they fight for resources. If you set them too high, the scheduler thinks nodes are full while they sit half idle, and you pay for wasted machines. Processor is squeezable, so a starved pod is just slowed down, but memory is not, so a pod that exceeds its memory limit is killed. Measure real usage and set requests close to it.

GSteering the Scheduler: Labels, Affinity, Taints, and Spreading

You are not stuck with the default behaviour. Kubernetes gives you several levers to guide where pods land.

Before the levers, one small word: a label is a tag such as “disk equals fast” or “zone equals A” that you attach to nodes and pods. Most steering works by matching labels. Kubernetes also puts standard labels on nodes automatically, such as the zone and the machine type.

Simple

Node selector

The easiest lever: “only run on nodes with this label”. It is a hard requirement and easy to read.

Flexible

Node affinity

A richer version of the selector. Rules can be required or merely preferred, and can use words like “in this list” or “does not exist”.

Together

Pod affinity

“Place me near pods with this label”, useful when two services talk constantly and benefit from being close.

Apart

Pod anti-affinity

“Keep me away from pods with this label”, used to place copies of the same app on different nodes or zones.

Repel

Taints and tolerations

A node carries a taint that pushes pods away. Only pods with a matching toleration may enter.

Even

Topology spread constraints

Ask for an even spread across zones or nodes, and say how much unevenness you will accept.

Required versus preferred

Many rules come in two strengths. A required rule is a hard wall: if no node satisfies it, the pod stays Pending. A preferred rule is a soft wish: it adds points during scoring, but the pod can still land elsewhere if needed. Rule names contain the long phrase “IgnoredDuringExecution”, which simply means the rule is checked only at the moment of scheduling. If node labels change later, Kubernetes does not move a pod that is already running.

Taints in more detail

A taint has an effect. NoSchedule means new pods without permission are kept out, but pods already there may stay. PreferNoSchedule means the scheduler tries to avoid the node but may still use it. NoExecute is the strongest: pods without permission are pushed off even if they are already running. Kubernetes itself adds NoExecute taints to nodes that become unreachable, which is how failed nodes are emptied automatically. Control plane nodes carry a NoSchedule taint by default, so ordinary applications stay away from the brain.

Spreading with topology constraints

A topology spread constraint names a boundary, such as a zone, and a number called maxSkew, meaning the biggest allowed difference in pod counts between the fullest and emptiest zone. With maxSkew of one and three zones, ten pods would be placed as four, three, and three. You also choose whether a broken constraint blocks the pod or is only a soft wish. This is the recommended modern way to spread copies of an application.

Analogy

Imagine a school assigning students to classrooms. A node selector is “only Room 5”. Affinity is “sit near your project partner”. Anti-affinity is “not next to your twin”. A taint is a sign on the lab door saying “Chemistry students only”. Topology spread is the principal’s rule that no classroom may have more than one extra student compared with any other.

i
Skipping the scheduler

A pod that names a specific node in its description bypasses the scheduler completely. This can be handy for debugging, but it also skips every safety check, so it is a poor habit for real applications. Static pods, which the kubelet reads from local files, also never pass through the scheduler.

HPriority, Preemption, and the Waiting Line

What happens when the cluster is full and an important pod arrives? Kubernetes has a polite but firm answer.

Priority classes

A PriorityClass is an object that gives a name to a whole number. A pod that refers to that class receives the number as its priority. Higher numbers are more important. Two effects follow. First, in the active queue, high-priority pods are tried before low-priority ones. Second, if a high-priority pod cannot fit anywhere, the scheduler may make room for it. Values above one billion are reserved for system components, and two built-in classes, one for critical cluster services and one for critical node services, use the top of that range.

Preemption: making room

When a pod fits nowhere, the filtering phase ends with an empty list. The scheduler then reaches the PostFilter step, where a plug-in named DefaultPreemption looks for a node where removing some lower-priority pods would let the new pod fit. It picks the option that harms the fewest and least important pods. The chosen victims are asked to shut down gracefully, and the new pod gets a note called the nominated node, which reserves its place while it waits for the space to appear.

1

No node fits

A high-priority pod arrives, and every node fails the resource check.

2

Look for victims

The scheduler searches for nodes where deleting lower-priority pods would free enough room, while respecting disruption budgets where possible.

3

Evict politely

The chosen low-priority pods receive a graceful termination notice.

4

Retry and place

Once space is free, the high-priority pod is scheduled onto that node.

Analogy

Picture an aircraft that is fully booked when an emergency medical passenger needs a seat. The airline asks a lower-priority passenger, perhaps one on a discount ticket, to give up their seat, and gives them notice first. The rule is only for real priorities. If everyone claimed to be an emergency, the system would break down.

What waits in the queue and how it wakes up

A pod that cannot be placed is not forgotten. It waits in the unschedulable pool. When something in the cluster changes in a way that could help, such as a new node joining, a pod being deleted, or a node label changing, the scheduler moves the affected pods back to the active queue to try again. Even without such events, pods are retried after a maximum wait of a few minutes. Failed attempts also use the backoff queue so the scheduler is not flooded by the same hopeless pod again and again.

Preemption and disruption budgets

Preemption tries to be gentle. A PodDisruptionBudget is a promise that says “at least this many copies of my app must stay running during voluntary disruptions”. The scheduler tries to respect these promises when choosing victims, but it treats them as a preference rather than an unbreakable law, because an urgent high-priority pod may still need the space. It also gives victims their full graceful shutdown time, which is thirty seconds by default, so applications can finish current requests before leaving.

Another helpful detail is the nominated node. While victims are shutting down, the waiting pod keeps a note reserving that node. Other lower-priority pods that arrive in the meantime see the reservation and stay away, so the freed space is not snatched by someone else in the gap. If a better node appears before the victims finish leaving, the scheduler is free to change its mind.

!
Use priority sparingly

If many teams give their pods the highest priority, priority stops meaning anything, and preemption can cause chains of evictions. Keep a short, clearly documented list of classes, such as critical, standard, and batch, and control who may use each one.

ICustomising the Scheduler: Profiles, Plug-ins, and Friends

The default scheduler suits most clusters, but Kubernetes lets you reshape it and even replace it.

Profiles: one scheduler, many personalities

A single scheduler program can run several profiles at once. A profile is a named bundle of settings that says which plug-ins are switched on, which are off, and what weight each scoring plug-in carries. Every pod can pick a profile by writing its name in the pod description under a field called schedulerName. If nothing is written, the pod uses the profile named default-scheduler. In this way, one team can run batch jobs that pack tightly while another runs web servers that spread widely, all inside the same cluster.

Custom and second schedulers

If plug-in settings are not enough, you can write your own scheduler or plug-in. Because scheduling is just “watch for pods with no node, then write a binding”, a second scheduler can run beside the first one. Pods choose between them by name. Companies with special workloads, such as machine learning training or scientific computing, sometimes do this.

Batch

Volcano

A project in the Cloud Native Computing Foundation family that adds batch scheduling ideas such as gang scheduling, where a group of pods must start together or not at all.

Queues

Kueue

A Kubernetes community project that manages queues and quotas for batch jobs, deciding when a job may start before the normal scheduler places its pods.

Growth

Cluster Autoscaler and Karpenter

These tools watch for pods that are Pending because no node has room, and add new machines. Once nodes appear, the scheduler places the waiting pods.

Rebalance

Descheduler

A separate tool that evicts pods from badly balanced nodes so the scheduler can place them again in better spots.

Analogy

The scheduler is a seating manager who only seats people as they arrive and never asks anyone to move. The cluster autoscaler is the building manager who opens a new hall when the seating manager runs out of tables. The descheduler is an usher who, after the party has been going a while, politely asks a few guests to move so that the crowd is more even.

Why the descheduler exists

The scheduler makes its choice once, at the moment a pod is created, using the information available then. Later, conditions change: new nodes join, other pods leave, labels are edited. A perfect choice earlier may look poor now. Since the scheduler never moves running pods, the descheduler fills that gap by removing pods that no longer fit the ideal picture. The controller that owns the pod then creates a replacement, and the scheduler decides again.

i
Start with the default

Most teams never need a custom scheduler. Try node affinity, taints, topology spread, and priority classes first. They solve most real problems with far less risk than replacing a core component.

JData Flow and Lifecycle: From Pod Creation to a Running Container

Let us follow one pod from birth to its first heartbeat, and then learn to read what the scheduler says when things go wrong.

sequenceDiagram
    participant D as Deployment controller
    participant A as kube-apiserver
    participant E as etcd
    participant S as kube-scheduler
    participant K as kubelet on chosen node
    D->>A: Create Pod record with no node
    A->>E: Save Pod as Pending
    S->>A: Watch shows new Pod without a node
    S->>S: Queue it, filter nodes, score nodes
    S->>S: Reserve space in local cache
    S->>A: Create Binding to chosen node
    A->>E: Save nodeName on the Pod
    K->>A: Watch shows Pod assigned to my node
    K->>K: Pull image and start containers
    K->>A: Report status Running
Figure 3. The scheduler only contributes the middle steps. Everything else is done by other parts reading and writing records.

The life of a pod’s placement

1

Created as Pending

A controller creates the pod record. It has no node yet, and its phase is Pending.

2

Queued

The scheduler sees the pod and puts it in the active queue according to priority.

3

Filtered and scored

Impossible nodes are removed, and the remaining nodes are rated.

4

Bound

The winner is written into the pod record as its node.

5

Started by the kubelet

The node’s kubelet pulls the image and starts the containers. The scheduler’s work is finished.

Reading the scheduler’s messages

When a pod stays Pending, the scheduler leaves a short sentence in the pod’s events explaining why. A typical message says that zero of some number of nodes are available, followed by a count for each reason. Learning these reasons turns confusion into a quick fix.

What the event saysPlain meaningUsual fix
Insufficient cpu or insufficient memoryNo node has enough unreserved room for the pod’s requestsLower the requests, free space, or add nodes
Node had untolerated taintThe nodes repel this podAdd a toleration or use different nodes
Node did not match node selector or affinityNo node carries the required labelsFix the labels or the rule
Did not match pod anti-affinity rulesEvery suitable node already has a pod the new one refuses to share withRelax the rule from required to preferred, or add nodes
Did not satisfy topology spread constraintsPlacing here would make the spread too unevenIncrease allowed skew or add capacity in the lighter zones
Volume node affinity conflictThe pod’s disk lives in a zone where no node has roomAdd capacity in that zone or use storage that can move
Unschedulable nodeNodes are cordoned for maintenanceUncordon them when work is finished
Too many podsEach node has a limit on how many pods it can holdAdd nodes or raise the limit where safe

A calm checklist for a stuck pod

1

Confirm that it is really unscheduled

Check whether the pod has a node name. If it does, the scheduler has finished, and you should investigate the kubelet, the image download, or the application itself.

2

Read the event summary

Note how many nodes failed for each reason. The largest number usually points to the main blocker.

3

Compare requests with node room

List the free allocatable room on your nodes and check whether the pod’s requests can ever fit on one node. A pod asking for more than the biggest node can never be placed.

4

Check labels, taints, and storage zones

Make sure the labels the pod asks for exist, that it tolerates the taints it meets, and that its disks live in a zone with spare nodes.

5

Relax or add capacity

Loosen an over-strict rule, lower an inflated request, or add nodes. Then watch the pod move from Pending to Running.

Cordon and drain: telling the scheduler a node is leaving

Nodes need maintenance such as operating system updates. Before touching a node, operators first cordon it, which marks the node as unschedulable so that the scheduler sends no new pods there. Existing pods keep running. Next they drain the node, which politely evicts its pods one by one while respecting disruption budgets. The controllers notice the missing pods, create replacements, and the scheduler places them on other nodes. When maintenance is done, the operator uncordons the node, and it becomes available again. This routine shows the scheduler doing its ordinary job in a planned event, without any special code.

i
First move for any Pending pod

Ask Kubernetes to describe the pod and read the events at the bottom. In nearly every case the reason is written there in plain words, together with how many nodes failed for each cause.

i
Pending is not always the scheduler

A pod may be scheduled successfully yet show as not ready because the image is downloading or a container is crashing. In that case the pod already has a node name, and the problem lies with the kubelet or the application, not with the scheduler.

KPerformance, Scalability, and High Availability

A scheduler that is slow or that stops working can freeze a whole cluster’s ability to grow. Here is how it stays fast and reliable.

Speed

Trick 1

Sampling nodes

As described earlier, the scheduler stops after finding enough feasible nodes, instead of checking every node in a huge cluster.

Trick 2

Parallel checks

Filter and score steps for different nodes run in parallel across several worker threads inside the scheduler.

Trick 3

A local cache

The scheduler keeps its own up-to-date picture of nodes and pods, so it rarely needs to ask the API server during a decision.

Trick 4

Asynchronous binding

Writing the decision runs in the background, so the next pod can be considered right away.

Trick 5

Smart retrying

Pods that cannot fit wait for a helpful cluster change instead of being retried endlessly.

Kubernetes officially supports clusters of up to 5,000 nodes and 150,000 pods, and the scheduler is designed to keep up at that size. The number of pods created at one moment matters as much as the cluster size. A sudden burst, such as a batch system launching ten thousand pods together, can form a long queue, so large batch users often add specialised queueing tools.

High availability: many copies, one leader

Production clusters run several copies of the scheduler, usually one on each control plane node. But two schedulers deciding at once could bind the same pod twice or double-book a node. So the copies hold an election through a shared record called a Lease. Only the elected leader makes decisions. The others stay ready, and if the leader stops renewing its Lease, another copy takes over within a few seconds.

SituationWhat happens
The leader scheduler crashesAnother copy wins the election within seconds and scheduling continues, with a short pause
All schedulers are downRunning pods keep running, but new pods stay Pending with no node until a scheduler returns
The API server is unreachableThe scheduler cannot read or write, so decisions pause and resume when it returns
A node disappearsThe scheduler is not involved in the removal. Controllers create replacement pods, and the scheduler then places them
Analogy

A large hotel has several front desk clerks on duty, but only one holds the master booking sheet at a time. If that clerk steps away, another picks up the sheet. This stops two clerks from giving the same room to two guests, while still keeping the desk staffed.

!
Scheduler down means growth freezes

Existing applications continue to serve users, so an outage may go unnoticed at first. But new deployments, scaling events, and replacements for crashed pods will all stall. Alert on a growing number of Pending pods so you find out quickly.

LSecurity, Monitoring, Logging, and Metrics

The scheduler holds power over where workloads run, so it must be protected and watched.

Security

The scheduler speaks to the API server using a special identity, and role-based access control limits it to exactly what it needs: reading pods, nodes, and storage information, and creating bindings. It should never have broader rights. All its connections use encrypted transport. Placement rules are also a security tool. Taints and node labels can keep sensitive workloads on dedicated machines, apart from untrusted ones, and anti-affinity can prevent a risky pod from sharing a node with a critical one.

Access

Least privilege

The scheduler’s role includes only reading cluster objects and writing bindings. Custom schedulers should follow the same discipline.

Isolation

Dedicated node pools

Taints, tolerations, and labels keep payment systems or sensitive data on their own machines.

Control

Guard priority classes

A pod with a high priority can evict others. Use admission rules or quotas to control who may use the top classes.

Trust

Protect scheduler settings

Changing the scheduler’s configuration changes placement for the whole cluster, so restrict who can edit it.

Monitoring and metrics

The scheduler publishes a metrics page, which Prometheus, the popular monitoring tool, collects, and Grafana turns into dashboards. Its logs go to standard output. Important signals include the number of pods waiting in each queue, the time each pod takes to be placed, the count of successful and failed attempts, and the number of preemptions.

What to watchWhy it matters
Pending pods by queueA rising number means pods cannot find homes, often from lack of capacity or over-strict rules
Time from creation to placementShows whether users wait too long for their pods to start
Schedule attempts by resultMany failures or errors point to configuration problems or shortage
Preemption attemptsFrequent preemption reveals clusters run too full or priorities are misused
Leader electionsRepeated changes of leader suggest a crashing or starved scheduler
Node utilisation and fragmentationHelps you see wasted space, where free room exists but in pieces too small for large pods

Using logs when events are not enough

Events give a short summary, but the scheduler’s own logs can show more detail when you turn up the verbosity. They record which plug-in rejected which node, how scores were added up, and when a pod moved between queues. This level of detail is noisy, so most teams turn it on only for a short investigation. A useful habit is to keep one test pod with a clear, deliberately impossible request, and use it to confirm that your alerts and dashboards react properly when the scheduler cannot place something.

flowchart LR
    SCH["kube-scheduler metrics and logs"] --> PR["Prometheus"]
    SCH --> LA["Log agent"]
    EV["Pod events from the API server"] --> LA
    PR --> GF["Grafana dashboards"]
    PR --> AM["Alertmanager"]
    AM --> OC["On-call engineer"]
    LA --> LS["Central log store"]
    LS --> INV["Investigate why a Pod was Pending"]
    GF --> INV
Figure 4. Metrics show that something is wrong. Events and logs explain why a specific pod was not placed.

A simple alert can save you from a silent outage: warn when the number of Pending pods stays above zero for several minutes. Add a second alert for the time-to-placement, and you will spot both shortage and stuck scheduling early.

i
Events expire

Kubernetes events are kept for only about an hour by default. If you want to study old scheduling problems, ship events to a log store as they happen.

MDesign Patterns and Anti-Patterns

Teams have found a few placement shapes that work again and again, and a few tempting shortcuts that cause trouble.

Patterns that work well

Pattern

Spread across zones

Use topology spread constraints so copies of each service live in different zones. A whole-zone failure then removes only a fraction of your capacity.

Pattern

Dedicated node pools

Put special machines, such as those with graphics processors, behind a taint and label. Only pods that need them carry the matching toleration and selector.

Pattern

Cheap capacity for flexible work

Run interruptible batch work on low-cost spare machines with a taint, and keep customer-facing services on stable machines.

Pattern

Priority tiers

Define three or four classes such as critical, standard, and batch. Under pressure, batch work makes way for critical services.

Pattern

Co-locate chatty pairs

Use preferred pod affinity to place two services that talk constantly close together, reducing network delay.

Pattern

Autoscale with headroom

Combine pod autoscaling with node autoscaling, and keep a little spare room so new pods do not wait for a machine to boot.

Analogy

A good airline does not put all its flights on one runway, or all its crew in one hotel. It spreads risk, keeps special aircraft for special routes, and lets urgent flights take precedence. These are placement habits, and the scheduler’s rules let you write the same habits into your cluster.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What it is

Leaving out resource requests, so pods are BestEffort, or copying one huge request number to every workload.

Why it hurts

The scheduler is blind. It piles pods onto nodes as if they used nothing, and then they fight and get killed. With inflated requests it thinks nodes are full while they sit idle.

Better approach

Measure real usage, set honest requests, and review them every few months. Use LimitRange defaults so no pod is created without values.

ANTI-PATTERN 02Avoid
What it is

Using strict required anti-affinity so that every replica must sit on its own node, without checking that there are enough nodes.

Why it hurts

With more replicas than nodes, the extra pods stay Pending forever. During a rolling update, new pods may also be unable to start because old ones still occupy the only allowed spots.

Better approach

Prefer topology spread constraints, or make the rule preferred instead of required, so the scheduler does its best without deadlocking.

ANTI-PATTERN 03Avoid
What it is

Hard-coding a node name in the pod description to force placement.

Why it hurts

The pod skips every safety check. If that node fails, the pod is not moved anywhere. Resource fit, taints, and storage rules are all ignored.

Better approach

Label the node and use a node selector or node affinity, so the scheduler still checks fit and can react to changes.

ANTI-PATTERN 04Avoid
What it is

Giving every team’s pods the highest priority class.

Why it hurts

When everything is urgent, nothing is. Preemption then removes important pods to make room for other important pods, causing chains of restarts.

Better approach

Keep a small, documented set of classes, restrict the top ones with quotas, and reserve them for truly critical services.

NBest Practices and Common Mistakes

These habits come from clusters that ran hot, ran empty, or ran into walls. Copying them saves months of trial and error.

1

Always set requests

Base them on measured usage. They are the numbers the scheduler trusts, and everything else depends on them being honest.

2

Run at least two or three replicas and spread them

Use topology spread across zones so a single failure cannot remove the whole service.

3

Prefer soft rules to hard rules

Use required rules only when a wrong placement would be truly harmful. Preferred rules give the scheduler room to succeed.

4

Use taints for dedicated hardware

Pair every taint with a label and a selector, so the right pods find the right machines and nobody else wanders in.

5

Protect availability during upgrades

Add PodDisruptionBudgets so maintenance never removes too many replicas at once.

6

Keep spare capacity and autoscale

Watch Pending pods and let a node autoscaler add machines, so surges do not wait for a human.

Common mistakes and fixes

MistakeWhat happensFix
Requests far above real usageNodes look full while machines sit idle, and the bill growsRight-size using usage history
Requests far below real usageToo many pods per node, causing slowdowns and memory killsRaise requests to match actual peaks
Forgetting that limits are ignored by the schedulerPods with huge limits and tiny requests squeeze neighboursKeep requests and limits reasonably close for important pods
Required anti-affinity with too few nodesExtra replicas remain Pending foreverUse topology spread or preferred rules
Taints without tolerationsPods have nowhere to go after a node pool is reservedAdd matching tolerations to the intended pods
Storage in one zone, nodes in anotherPods wait because their disk cannot attachEnsure capacity exists in every zone that holds volumes
Ignoring fragmentationPlenty of total free room, but no single node fits a big podUse fewer large node sizes, or repack with the descheduler
No alert on Pending podsProblems are discovered by angry usersAlert when pods stay Pending for several minutes
i
Learning order

Practise on a small cluster. Create a pod with a request larger than any node, read the event, then fix it. Add a taint, watch pods avoid it, then add a toleration. Each experiment turns an abstract rule into a lesson you will not forget.

OReal-World Examples

Scheduling is not just a Kubernetes idea. It is a problem every large computer operator has had to solve.

Google Borg: the ancestor

Kubernetes grew out of lessons from Google’s internal Borg system. Borg’s scheduler also worked in two steps, checking which machines are feasible and then scoring them, which is the same filter-then-score idea used in Kubernetes today. Google runs enormous numbers of jobs on shared machines, and this design showed that a simple two-step method scales.

An online shop spreading for safety

A shopping site runs its checkout service with six replicas. Using topology spread constraints, two land in each of three zones. When one zone suffers a power problem, two thirds of the capacity remains, and the site stays open while Kubernetes creates replacements elsewhere.

A machine learning team with graphics machines

Graphics processors are expensive, so the platform team taints the graphics node pool. Only training jobs carry the toleration and a node selector. Web services cannot accidentally occupy the costly machines, and idle graphics nodes can be scaled down when no job needs them.

Batch computing with queues

Research groups running thousands of jobs use projects such as Volcano or Kueue. Volcano can start a group of related pods together or not at all, which avoids the deadlock where half a job holds resources waiting for the other half. Kueue adds fair queues and quotas, so one team cannot swallow the entire cluster.

Fast growth on AWS with automatic nodes

A company on Amazon EKS uses a node autoscaler such as Karpenter. When a traffic surge leaves pods Pending, the tool reads their requests and constraints and launches machines of a fitting size. As soon as those nodes join, the scheduler places the waiting pods, and later the extra machines are removed when they are empty.

Cost saving with spot capacity

An analytics team runs report-generating jobs on spare, discounted cloud machines that can be taken away with short notice. A taint keeps customer-facing services off those machines, while the jobs tolerate it, retry on interruption, and save a large share of their compute bill.

i
Common thread

In every story, the scheduler stays simple. The wisdom lies in the rules people write for it: honest requests, sensible spreading, clear taints, and thoughtful priorities. Good scheduling is mostly good description.

PFrequently Asked Questions

Short, plain answers to the questions beginners ask most.

Q1What does the Kubernetes scheduler do?
It watches for pods that have no node assigned and chooses the best node for each. It filters out nodes that cannot run the pod, scores the rest, and records the winner. It does not start containers.
Q2Does the scheduler start my containers?
No. After the scheduler assigns a node, the kubelet on that node pulls the image and starts the containers.
Q3Why is my pod stuck in Pending?
Usually the scheduler cannot find a node that satisfies the pod’s needs. Describe the pod and read the events. Common causes are not enough processor or memory, taints without tolerations, unmatched labels, or storage in another zone.
Q4Does the scheduler look at limits or requests?
It uses requests. Limits are enforced later on the node and do not affect where a pod is placed.
Q5Will the scheduler move a running pod to a better node?
No. It decides once, when the pod is created. A separate tool such as the descheduler can evict pods so that they are placed again.
Q6What is the difference between a taint and a node selector?
A taint pushes pods away from a node unless they have permission. A node selector pulls a pod towards nodes with a certain label. Dedicated machines usually need both.
Q7What is preemption?
When a high-priority pod cannot fit, the scheduler may evict lower-priority pods from a node to make room. The evicted pods are given time to shut down gracefully.
Q8Can I run more than one scheduler?
Yes. You can run several profiles inside one scheduler, or run separate scheduler programs. Each pod chooses one by naming it in its description.
Q9What happens if the scheduler is down?
Running pods continue as normal. New pods stay Pending, and scaling, rollouts, and replacements stall until a scheduler is available again. Production clusters run several copies for this reason.
Q10How does the scheduler work with the autoscaler?
If pods are Pending because no node has room, a node autoscaler adds machines. Once the machines join, the scheduler places the waiting pods on them.
Q11Do DaemonSet pods use the scheduler?
In current Kubernetes versions, yes. The DaemonSet controller creates one pod per node with a rule that pins it to that node, and the default scheduler performs the binding. Static pods, however, are managed directly by the kubelet.
Q12Can I change how the scheduler scores nodes?
Yes. You can enable or disable plug-ins and change their weights through scheduler profiles, for example to prefer packing pods tightly instead of spreading them out.

QSummary and Key Takeaways

Back to the hotel front desk for one last look.

The Kubernetes scheduler is the front desk of the cluster. It waits for pods without a home, removes every node that cannot host them, awards points to the rest, and writes the winner’s name on the pod. It follows the rules and numbers you give it, so honest resource requests, clear labels, sensible taints, and well-chosen priorities are what make it wise. Understand its two-step method and its handful of levers, and you can predict where any pod will land and fix any pod that will not.

Key Takeaways

  • One question: The scheduler decides which node runs each new pod. It never starts containers, moves running pods, or creates pods.
  • Filter, then score: Filters remove impossible nodes. Scoring rules rate the rest from 0 to 100, weights are applied, and the highest total wins.
  • Requests rule: The scheduler reads resource requests, not limits or real usage, so honest requests are the foundation of good placement.
  • Steer with labels: Node selectors, affinity, anti-affinity, taints, tolerations, and topology spread constraints guide placement. Prefer soft rules to hard rules where possible.
  • Priority and preemption: Important pods can go first and can evict lower-priority pods, so keep priority classes few and well guarded.
  • Diagnose with events: A Pending pod explains itself in its events, with a count of nodes failing for each reason.
  • Reliable by design: Several scheduler copies run with one elected leader. If all are down, existing apps keep running but nothing new can be placed, so alert on Pending pods.