Deep Dive into Kubernetes Scheduler: From Scheduling Cycle to Managing Ten‑Thousands of Pods
This guide explains why the kube‑scheduler becomes a bottleneck in large clusters, walks through its architecture, scheduling cycle, queue mechanics, constraint handling, plugin framework, multi‑scheduler designs, production‑grade tuning, observability, and real‑world case studies for achieving stable, high‑throughput pod placement.
1. Why Scheduling Failures Appear as Resource Shortage
Many teams encounter pending pods even when node CPU is only 45% and memory is low; the root cause is scheduler degradation under conditions such as massive pod creation, combined podAntiAffinity, topologySpreadConstraints, taints, heterogeneous node pools, priority preemption, and frequent node status flapping.
In these scenarios the kube‑scheduler is no longer a simple "place pod on an empty node" but a distributed decision engine balancing constraints, resource fragmentation, latency, fairness, and scalability.
2. Scheduler as a Constraint Solver
The scheduler solves the problem: given a pod and a changing set of nodes, select the optimal node that satisfies hard constraints while minimizing fragmentation, zone imbalance, priority fairness, and throughput latency.
Feasibility : pod must fit on at least one node (CPU, memory, taints, etc.).
Local optimality : choose the best node among feasible ones (requires filtering and scoring).
Global stability : avoid resource fragmentation and hotspots (needs long‑term balancing).
System throughput : maintain scheduling speed when many pods arrive (limits computation cost).
This creates an engineering "impossible triangle": finer scheduling reduces throughput, higher throughput makes decisions coarser, and aggressive resource utilization increases fragmentation risk.
3. Scheduler Position in the Control Plane
The scheduling flow is part of the control‑plane event loop:
User/Controller creates a Pod (nodeName empty).
API Server persists the Pod.
Scheduler watches unscheduled Pods.
Scheduling cycle: queue → filter → score → select.
Binding cycle: bind Pod to Node.
API Server updates spec.nodeName.
Kubelet on the target node starts the containers.
3.1 Scheduler Responsibilities
Watch unscheduled Pods.
Read cluster state from the local cache.
Select a target node for each Pod.
Bind the Pod by setting spec.nodeName.
It does not create containers, pull images, configure networking, or handle application start failures – those are performed by the kubelet and container runtime.
3.2 Cache‑Based Design
The scheduler never queries the API server for every decision; it maintains an informer‑driven cache of nodes, pods, PVCs, taints, etc. This cache enables high‑frequency decisions without a strong consistency guarantee.
3.3 No Global Optimum
In clusters of hundreds to thousands of nodes, the scheduler first finds a feasible node set, then scores a limited subset, and finally binds quickly to keep the queue moving.
4. Scheduling Cycle Decomposition
Since v1.19 the Scheduling Framework defines two main phases for a single pod:
Scheduling Cycle : decide the node.
Binding Cycle : perform the bind.
The configuration API version kubescheduler.config.k8s.io/v1 is stable; v1beta3 was removed in v1.29.
4.1 Full Scheduling Flow
PreEnqueue / QueueSort
-> PreFilter
-> Filter
-> PostFilter
-> PreScore
-> Score
-> NormalizeScore
-> Reserve
-> Permit
-> PreBind
-> Bind
-> PostBindThe flow can be viewed in three semantic layers:
Queue layer – decides who is scheduled first.
Feasibility layer – filters nodes that satisfy hard constraints.
Decision & execution layer – scores and binds the chosen node.
4.2 QueueSort
Pods are ordered by priority, backoff status, nominated node, and retry status. High‑priority bursts can starve low‑priority pods.
4.3 PreFilter / Filter
Hard‑constraint checks include CPU/memory, nodeSelector / nodeAffinity, taints/tolerations, podAffinity/anti‑affinity, topologySpreadConstraints, PVC binding, and port conflicts. Expensive checks are often podAntiAffinity, large topology spreads, and complex volume bindings.
4.4 Score
After filtering, the Score phase applies plugins such as NodeResourcesFit, NodeResourcesBalancedAllocation, ImageLocality, PodTopologySpread, InterPodAffinity, and TaintToleration. Over‑stacking plugins can dramatically increase per‑pod scheduling cost and cause throughput collapse during releases.
4.5 Reserve / Permit / Bind
These stages ensure that concurrent schedulers do not race for the same resource slot, providing a stateful reservation before the final bind.
5. Internal Queue Mechanics
The scheduler queue consists of three parts:
ActiveQ : pods currently eligible for scheduling.
BackoffQ : pods that recently failed and are waiting before retry.
UnschedulablePods : pods that cannot be scheduled under current constraints.
Backoff defaults are 1 s ( podInitialBackoffSeconds) and 10 s ( podMaxBackoffSeconds). Large batch releases (e.g., 800 pods) can quickly fill BackoffQ and Unschedulable sets, amplifying throughput consumption.
5.1 Common Failure Reasons
0/300 nodes are available:
60 Insufficient cpu,
45 node(s) had untolerated taint,
120 node(s) didn't match Pod anti-affinity rules,
75 node(s) didn't satisfy existing pods anti-affinity rulesUsually the dominant cause is rule conflict rather than pure CPU shortage.
6. Scheduler Framework Value
The framework makes the scheduler extensible via plugins instead of forking the whole codebase.
6.1 When to Use Plugins
Custom node scoring.
Business‑specific scheduling constraints.
Cost‑aware placement.
Fine‑grained bin‑packing or hotspot avoidance.
6.2 When Not to Use Plugins
Simple zone‑level spreading.
Basic affinity or taint‑toleration.
Batch‑task avoidance of online services.
In these cases native capabilities ( nodeAffinity, taints/tolerations, topologySpreadConstraints, priorityClass) are preferred.
6.3 Multi‑Scheduler Scenarios
Separate schedulers for online services, GPU workloads, and batch jobs avoid policy interference and improve isolation.
7. Native Capability First
Before writing plugins, leverage native features such as topologySpreadConstraints instead of strict podAntiAffinity, and use taints to separate high‑cost node pools.
7.1 Example: Replace Strict Anti‑Affinity
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: payment
topologyKey: kubernetes.io/hostnameSwitch to a topology spread constraint that allows flexible distribution while still preventing same‑node placement.
7.2 Example: Taint‑Based Isolation
apiVersion: v1
kind: Node
metadata:
name: gpu-node-01
spec:
taints:
- key: workload-type
value: gpu
effect: NoScheduleWorkloads that need GPUs add a matching toleration; mis‑labelled pods will never land on expensive nodes.
8. High‑Throughput Production Tuning
8.1 Limit Nodes Scanned per Pod
The percentageOfNodesToScore parameter controls how many nodes are examined after a feasible set is found. In clusters of 300+ nodes, tuning this value (e.g., 20 % for default, 100 % for GPU profiles) balances latency and fragmentation.
8.2 Reduce Expensive Constraint Coverage
Identify the three most costly rules: mandatory podAntiAffinity, many topologySpreadConstraints, and complex volume bindings. Apply them only to critical services; use softer constraints for the rest.
8.3 Separate Online and Offline Schedulers
Deploy default-scheduler for latency‑sensitive services and a dedicated batch-scheduler (or gpu-scheduler) for batch and AI workloads, each with its own node pool and taint‑based isolation.
8.4 Include Autoscaling in the Scheduling Loop
Pending pods often stem from slow node provisioning. Integrate Cluster Autoscaler or Karpenter, pre‑warm node images, and accelerate image pulls to avoid “still no place to put” loops.
9. Multi‑Scheduler Architecture
A typical production layout includes:
API Server → default-scheduler (online services)
API Server → gpu-scheduler API Server → batch-scheduler Node pools isolated by taints/labels.
Descheduler for secondary rebalancing.
9.1 Sample KubeSchedulerConfiguration
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
percentageOfNodesToScore: 25
- schedulerName: gpu-scheduler
percentageOfNodesToScore: 100
plugins:
score:
disabled:
- name: "*"
enabled:
- name: NodeResourcesFit
weight: 3
- name: ImageLocality
weight: 1
- name: TaintToleration
weight: 2Key points: default scheduler scans a fraction of nodes for throughput; GPU profile scans all nodes with higher scoring granularity.
9.2 Pod‑Specific Scheduler Selection
apiVersion: apps/v1
kind: Deployment
metadata:
name: recommendation-inference
spec:
replicas: 6
template:
spec:
schedulerName: gpu-scheduler
tolerations:
- key: workload-type
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: app
image: registry.example.com/reco-inference:4.2.1
resources:
requests:
cpu: "8"
memory: "24Gi"
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: "24Gi"
nvidia.com/gpu: "1"10. Production‑Ready Custom Plugin: Tenant‑Cost Score
The plugin reads node annotations (cost tier, tenant allow‑list) from the local cache and returns a score without remote calls.
10.1 Node Annotation Example
apiVersion: v1
kind: Node
metadata:
name: worker-az1-03
labels:
node-pool: online-general
topology.kubernetes.io/zone: az1
annotations:
scheduling.example.com/cost-tier: "gold"
scheduling.example.com/tenant-allowlist: "payment,reco,search"10.2 Scoring Logic (simplified)
If the pod's tenant is not in the node's allow‑list, return a low score (5).
For tenant payment, give 100 for gold, 80 for silver, otherwise 40.
For tenant analytics, give 100 for bronze, 60 for silver, otherwise 20.
Default case: 90 for silver, 70 for gold, else 60.
The plugin is registered with weight 5 in the scheduler configuration.
11. Observability of the Scheduler
11.1 Queue‑Level Metrics
ActiveQ length.
Unschedulable pod count.
BackoffQ size.
Failure‑reason distribution.
High ActiveQ indicates the scheduler cannot keep up; high BackoffQ shows repeated failures; high Unschedulable signals structural constraint issues.
11.2 Latency & Throughput Metrics
Pod‑level scheduling latency percentiles.
Filter and Score stage durations.
Successful pods per second.
Bind latency.
Example Prometheus query to get 99th‑percentile extension‑point latency:
histogram_quantile(
0.99,
sum(rate(scheduler_framework_extension_point_duration_seconds_bucket[5m])) by (extension_point, le)
)11.3 Business‑Facing Metrics
Time from Deployment creation to Ready.
Pending duration during bulk releases.
Queueing latency for high‑priority services.
Recovery time for preempted pods.
12. Real‑World Case Study: Large‑Scale Release Snow‑Avalanche
12.1 Background
Cluster: 420 nodes, ~16 000 pods.
Release: 38 services, ~1 100 new pods.
Core services used mandatory node‑level anti‑affinity and cross‑AZ topology spreads.
Batch jobs still running during the release window.
12.2 Symptoms
Many pods pending > 90 s.
High‑priority services preempted batch jobs.
Preempted pods re‑queued, creating a feedback loop.
Cluster Autoscaler added nodes, but they became Ready only after 3–5 min.
Scheduler 99th‑percentile latency kept rising.
12.3 Root‑Cause Decomposition
Strict anti‑affinity narrowed candidate nodes.
Release batch size too large, flooding the queue.
Online and batch workloads shared the same scheduler and node pool.
Autoscaling lagged behind the rapid retry cycle.
12.4 Remediation Actions
Replace mandatory anti‑affinity with topology spread constraints and softer distribution.
Separate online and batch workloads into different schedulers and node pools.
Throttle release to ≤ 80 pods per batch.
Keep high priority for critical services but make most background tasks non‑preempting.
Reduce percentageOfNodesToScore for the default profile.
Shrink batch jobs before the promotion window.
12.5 Outcome
Critical services' average pending time dropped from 46 s to < 4 s.
Scheduler 99th‑percentile latency improved > 60 %.
Preemption events decreased dramatically.
Release window stability increased noticeably.
13. Production Configuration Baseline
13.1 Strategy Layering
Online services: prioritize stability, cross‑AZ spread, moderate node dispersion.
Offline tasks: prioritize cost and packing, tolerate queuing.
GPU workloads: prioritize exact resource matching, allow finer scans.
13.2 Constraint Ordering
Use taints/tolerations to separate workload domains.
Apply nodeAffinity to select the appropriate node pool.
Apply topologySpreadConstraints for zone and node dispersion.
Use inter‑pod affinity/anti‑affinity only when necessary.
13.3 Release Governance
Rate‑limit large rolling updates.
Avoid queuing hundreds of pods simultaneously.
Clear low‑priority batch jobs before critical release windows.
Release system should monitor pending duration and auto‑throttle.
13.4 Capacity Management
Reserve buffer capacity for online services.
Distinguish "schedulable capacity" from total raw resources.
Include image warm‑up, PVC binding, and node‑Ready latency in scaling decisions.
14. Common Pitfalls
Assuming Pending always means insufficient resources – often rule conflicts.
Believing stricter anti‑affinity always improves availability – can hurt during failures or scaling.
Equating higher priority with higher SLA – leads to priority inflation.
Assuming custom plugins are always superior – native features are simpler and more maintainable.
Focusing only on scheduler parameters – real issues involve workload design, release pipelines, node‑pool planning, autoscaling, and observability.
15. Pre‑Production Checklist
15.1 Policy Correctness
Validate necessity of strict anti‑affinity.
Check for conflicting nodeAffinity and topologySpreadConstraints.
Ensure high‑priority is reserved for truly critical services.
15.2 Performance Validation
Benchmark batch creation of 100, 500, 1000 pods.
Identify Filter/Score hotspots.
Assess impact of node‑scan percentage on fragmentation.
15.3 Failure Scenarios
Verify business can recover from preemptions.
Test flexible distribution when a node pool fails.
Ensure Descheduler does not cause cascade evictions.
15.4 Operability
Integrate scheduler metrics into Prometheus/Alertmanager.
Provide a FailedScheduling reason dashboard.
Make release system aware of prolonged Pending states.
16. Conclusion
The Kubernetes scheduler is not just a component that places pods; it is the core decision engine that determines whether a platform can sustain high concurrency, complex constraints, and rapid resource fluctuations. Mastering its internals, leveraging native capabilities first, extending with lightweight plugins when needed, and closing the loop with observability, capacity planning, and release governance turns scheduling from a hidden bottleneck into a reliable platform service.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Cloud Architecture
Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
