Kubernetes Scheduler Decision Flow
Kubernetes Scheduler Decision Flow explains how the scheduler selects optimal nodes for pod deployment based on resource constraints, policies, and node affinity rules.
Kubernetes Scheduler Decision Flow is the sequence of internal stages the default kube-scheduler executes each time it processes a Pod awaiting node assignment, transforming a Pod's requirements and constraints into a single, final node-binding decision. The flow is organized around two major phases — scheduling and binding — each broken into a series of well-defined extension points, and understanding this flow is what makes it possible to reason precisely about why a given Pod landed where it did, or why it failed to be placed at all.
Rather than treating scheduling as a single opaque decision, the scheduler decision flow exposes discrete stages, each responsible for a specific narrower question, whose combined outcome determines the final placement.
The Scheduling Cycle
Filtering
The scheduler first evaluates every node in the cluster against the Pod's hard requirements — resource requests fitting within allocatable capacity, required node affinity rules, taint tolerations — eliminating any node that fails even one of these checks. The output of filtering is the set of "feasible" nodes: everywhere the Pod could run, without yet considering which is best.
Scoring
Every node that survives filtering is then scored according to a set of weighted scoring plugins — balancing resource utilization, spreading Pods across topology domains, honoring soft affinity/anti-affinity preferences, and other configurable priorities. Nodes receive a numeric score from each active plugin, which are combined (respecting each plugin's configured weight) into a single aggregate score per node.
Selecting
The node with the highest aggregate score is selected as the target for the Pod. Ties are broken through an internal, effectively randomized tie-breaking mechanism, ensuring that when multiple nodes are equally suitable, Pod placement does not deterministically pile onto the same node repeatedly.
Extension Points
PreFilter and Filter
PreFilter plugins perform any preprocessing needed before the per-node filtering pass (computing aggregate state once rather than per node), while Filter plugins implement the actual per-node hard-requirement checks described above.
PreScore and Score
Similarly, PreScore plugins prepare shared state ahead of the per-node scoring pass, and Score plugins implement the actual per-node scoring logic, each contributing its own weighted signal to the final aggregate.
Reserve and Permit
Once a node is selected, Reserve plugins update internal scheduler state to reflect the Pod's tentative placement (preventing a race where two Pods are simultaneously assigned resources that only one can actually have), while Permit plugins can delay, approve, or deny the binding — used by advanced scheduling features like gang scheduling, which needs to wait until all Pods in a group have a placement decision before committing any of them.
PreBind, Bind, and PostBind
PreBind plugins perform any final setup required before the actual binding operation (provisioning a volume that must exist before the Pod can start), Bind plugins perform the actual API call writing the Pod's nodeName, and PostBind plugins run informational logic after a successful bind, without being able to affect the outcome.
Preemption as a Fallback Path
When No Node Passes Filtering
If a Pod fails filtering on every node (no node has sufficient free resources, for instance), the scheduler does not immediately give up — for Pods with a sufficiently high PriorityClass, it evaluates whether evicting lower-priority Pods on some node would create enough room to satisfy the pending Pod's requirements, and if so, triggers preemption before retrying the scheduling cycle for that Pod.
Preemption Is Not Guaranteed
Preemption only proceeds if a viable eviction set actually exists and the resulting placement is expected to succeed; a Pod that still cannot be satisfied even after considering every possible preemption remains Pending, and the scheduler does not retry indefinitely in a tight loop but rather re-attempts scheduling as part of its normal event-driven reconciliation when cluster state changes (a node's resources free up, a new node joins).
Observing the Decision Flow
kubectl describe pod codartium-app-7d9f8
The Events section surfaces Scheduled events for successful placements and FailedScheduling events (with a human-readable summary of which filters rejected which nodes) for Pods stuck in the flow, which is the primary window into the otherwise internal scheduling cycle from outside the scheduler's own process.
kubectl get events --field-selector reason=Preempted
Example
apiVersion: v1
kind: Pod
metadata:
name: codartium-decision-flow-example
spec:
priorityClassName: high-priority
containers:
- name: app
image: codartium/app:latest
resources:
requests:
cpu: "1"
memory: "1Gi"