KubernetesXXII · Scheduling FundamentalsScheduling fundamentals
Score phase — how the scheduler ranks feasible nodes
What you'll learn
- Describe the score phase: ranking feasible nodes
- Identify the standard scoring plugins and their preferences
- Explain how scoring weights combine into a final score
- Configure custom scoring via scheduler profiles
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
The scheduler’s score phase ranks the nodes that survive the filter phase. Each node gets a score from each scoring plugin; the weighted sum determines the winner. This lesson covers the standard scoring plugins, how they combine, and how to customise them via scheduler profiles.
The score pipeline
flowchart LR
A[Feasible nodes] --> B["Score plugin 1<br/>LeastAllocated"]
B --> C["Score plugin 2<br/>BalancedResourceAllocation"]
C --> D["Score plugin 3<br/>NodeAffinity preferred"]
D --> E["Score plugin 4<br/>InterPodAffinity"]
E --> F["Score plugin N<br/>..."]
F --> G[Weighted sum]
G --> H["Highest score<br/>selected"]
Each plugin scores a node from 0 to 100 (or 0 to 10 in older versions). The scheduler configures a weight per plugin; the final score is the weighted sum, normalised to 0-100.
Standard scoring plugins
LeastAllocated (default)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default
plugins:
score:
enabled:
- name: LeastAllocated
weight: 1
LeastAllocated favours nodes with the most free resources:
flowchart TB
A["Node A: 50% used"] --> B["Score: 50<br/>(50% free)"]
C["Node B: 90% used"] --> D["Score: 10<br/>(10% free)"]
The Pod lands on Node A (more headroom). The score is proportional to the free fraction; a fully-allocated node gets 0.
LeastAllocated is the default scoring rule. It produces
even distribution but may not be the right policy for
bin-packing (where the goal is high utilisation).
BalancedResourceAllocation
plugins:
score:
enabled:
- name: BalancedResourceAllocation
weight: 1
BalancedResourceAllocation favours nodes where CPU and
memory utilisation are similar:
flowchart TB
A["Node A: CPU 80%<br/>memory 20%"] --> B["Score: low<br/>(imbalanced)"]
C["Node B: CPU 50%<br/>memory 50%"] --> D["Score: high<br/>(balanced)"]
A node with high CPU and low memory utilisation is imbalanced; a node with even utilisation is balanced. This produces predictable resource usage patterns.
NodeAffinity (preferred)
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: disk
operator: In
values: ["ssd"]
The weight is added to the node’s score if the node
matches the preference. A weight of 50 means “50 out of 100
extra points.” Multiple preferences are summed.
InterPodAffinity
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
Co-locate with Pods labelled app: cache on the same node.
The score increases for nodes that already host such Pods.
TaintToleration
plugins:
score:
enabled:
- name: TaintToleration
weight: 1
The TaintToleration plugin favours nodes the Pod tolerates without preferring. It is rarely customised; the filter phase handles taints more strictly.
Combining scores
flowchart TB
A[Node X] --> B["LeastAllocated: 80"]
A --> C["Balanced: 70"]
A --> D["NodeAffinity: 50"]
A --> E["InterPodAffinity: 0"]
A --> F[Weighted sum]
F --> G["Score: (80 + 70 + 50 + 0) / 4 = 50"]
The scheduler configures a weight for each plugin; the final score is the weighted sum. The default kube-scheduler configuration uses equal weights for the built-in plugins.
Tiebreakers
When two nodes have the same score, the scheduler uses tiebreakers:
flowchart TB
A["Two nodes<br/>score 80"] --> B{Tiebreaker 1:<br/>BinPacking?}
B -->|yes| C["Node with more<br/>existing Pods"]
B -->|no| D{Tiebreaker 2:<br/>NodeName hash?}
D -->|yes| E["Hash of node name<br/>deterministic"]
D -->|no| F[Random selection]
The default behaviour: random selection. Determinism is useful for testing but not for production.
Custom scoring
A scheduler profile can enable, disable, or replace scoring plugins:
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: gpu-scheduler
plugins:
score:
enabled:
- name: LeastAllocated
weight: 1
- name: NodeAffinity
weight: 2
- name: InterPodAffinity
weight: 1
disabled:
- name: BalancedResourceAllocation
The gpu-scheduler profile weights NodeAffinity twice as
much as LeastAllocated, prefers nodes with the GPU label,
and disables BalancedResourceAllocation (a GPU workload
does not benefit from balanced scoring).
Real-world patterns
Pattern 1: prefer SSD nodes
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disk
operator: In
values: ["ssd"]
The Pod lands on an SSD node if available; falls back to spinning disk if not.
Pattern 2: spread across nodes
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
The Pod prefers a node with no other app: web Pods.
Multiple replicas of the Deployment spread across nodes.
Pattern 3: prefer same node as cache
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
The Pod prefers to land on a node already hosting a cache. This co-locates the workload with its dependency.
Inspecting scores
The scheduler does not expose the score per node by default. To debug:
# Enable verbose logging on the scheduler
kubectl logs -n kube-system kube-scheduler-master-0 --v=4
# Look for "ScoreNoderesults" entries
Production operators correlate the scheduling decision with
the cluster’s state at the time (kubectl describe nodes,
kubectl top nodes).
Quiz
Knowledge check · 4 questions
Q1. Which scoring plugin favours nodes with the most free resources?
Q2. The scheduler's score phase randomly picks among feasible nodes; the scoring plugins have no real effect.
Q3. Your team deploys a Pod with preferredDuringSchedulingIgnoredDuringExecution for SSD nodes. The Pod lands on a non-SSD node. Diagnose.
Pod web-cache with Node Affinity preferred weight 100 for disk ssd. Cluster has 4 SSD nodes and 4 HDD nodes. The Pod lands on an HDD node.
Q4. Explain how scoring plugins combine into a final score, and how tiebreakers work.
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Default scoring is a starting point. Production workloads may need different weights (e.g., a latency-sensitive workload that prefers nodes with low CPU).
- Use scheduler profiles for workload-specific scoring. Different profiles for different workloads (GPU, latency-sensitive, batch) allow tailored scoring.
- Verify scoring with logs. The scheduler’s debug logging surfaces the per-node scores; use it to validate the configuration.
- Audit the scheduler’s behaviour. A Pod that lands on an unexpected node may be the result of a scoring plugin the operator did not intend.
The score phase is the difference between a Pod landing on the right node and an arbitrary one. Operators who understand scoring have Pods that land predictably.