Scheduling Basics
This page is a hands-on intro to how the kube-scheduler places Pods: the two-phase filter-then-score model, the fields that steer it, and the commands to observe it.
Prerequisites
- Kubernetes 1.36.2 cluster access with
kubectlconfigured. - containerd as the node runtime (the default CRI runtime; Docker Engine is not the in-cluster runtime).
- A multi-node cluster is ideal so scheduling actually has choices. A local
kindorminikubecluster works for the read-only examples. - Familiarity with Pod resource
requestsandlimits.
# Confirm you can see nodes and their capacity
kubectl get nodes -o wide
kubectl describe node <node-name> | grep -A6 AllocatableBasic Examples
1. See where a Pod landed
Every scheduled Pod carries the node the scheduler chose.
kubectl get pod my-app -o wide
kubectl get pod my-app -o jsonpath='{.spec.nodeName}{"\n"}'spec.nodeNameis written by the scheduler at bind time.- An empty
nodeNameplus aPendingstatus means scheduling has not succeeded yet. -o widealso shows the node's IP, useful for correlating with node problems.- The scheduler never changes
nodeNameafter binding.
2. Diagnose a Pending Pod
When no node is feasible, the reason is in the Pod events.
kubectl describe pod my-app | sed -n '/Events/,$p'- Look for
FailedSchedulingwith a per-node breakdown likeInsufficient cpuornode(s) had untolerated taint. - The message aggregates why each node was filtered out.
0/5 nodes are availabletells you the total node count considered.- Fixing usually means lowering requests, adding a toleration, or relaxing affinity.
3. Set resource requests so the scheduler can fit the Pod
Requests are the numbers the scheduler uses to check node capacity.
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: ghcr.io/acme/app:1.4.2
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "256Mi"- Only
requestsaffect the scheduling fit;limitsgovern runtime throttling and OOM behavior. - A Pod with no requests is treated as needing near-zero resources, which harms packing quality.
- Sum of all requests on a node cannot exceed the node's allocatable capacity.
- Matching memory request and limit gives the Pod the
GuaranteedQoS class.
4. Pin a Pod to a node type with nodeSelector
The simplest steering mechanism matches node labels.
apiVersion: v1
kind: Pod
metadata:
name: gpu-job
spec:
nodeSelector:
kubernetes.io/os: linux
node.acme.io/pool: gpu
containers:
- name: trainer
image: ghcr.io/acme/trainer:0.9.0nodeSelectoris a hard requirement evaluated during filtering.- Every listed label must match, or the node is not feasible.
- Label nodes with
kubectl label node <name> node.acme.io/pool=gpu. - For anything richer than exact matches, use node affinity instead.
5. Inspect node labels the scheduler sees
Steering only works if you know the labels available.
kubectl get nodes --show-labels
kubectl get nodes -L topology.kubernetes.io/zone,kubernetes.io/arch- Well-known labels like
topology.kubernetes.io/zoneandkubernetes.io/archare set automatically. -Lprints selected labels as columns for quick scanning.- Cloud providers add instance-type and zone labels you can select on.
- Avoid selecting on labels that are not guaranteed to exist on every intended node.
6. Assign a Pod directly with nodeName
You can bypass the scheduler for debugging.
apiVersion: v1
kind: Pod
metadata:
name: debug-here
spec:
nodeName: worker-3
containers:
- name: shell
image: busybox:1.37
command: ["sleep", "3600"]- Setting
spec.nodeNameskips filtering and scoring entirely. - The kubelet on that node runs the Pod even if it does not actually fit, risking eviction.
- Use this only for debugging, never for production placement.
- If the node is unreachable, the Pod stays stuck with no rescheduling.
7. Watch scheduling events live
Events show scheduling decisions as they happen.
kubectl get events --field-selector reason=Scheduled --watch- The
Scheduledevent names the Pod and the node it was bound to. FailedSchedulingevents indicate no feasible node was found.- Events expire (default one hour), so watch or capture them promptly.
- Correlate
Scheduledwith the kubeletPullingandStartedevents to trace startup.
Intermediate Examples
8. Steer with node affinity (required plus preferred)
Node affinity is the expressive successor to nodeSelector.
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: node.acme.io/pool
operator: In
values: ["general"]
containers:
- name: web
image: ghcr.io/acme/web:2.1.0required...is a hard filter;preferred...adds score without blocking.IgnoredDuringExecutionmeans the rule is not re-checked after the Pod is running.weight(1 to 100) ranks preferred terms against each other.- Operators include
In,NotIn,Exists,DoesNotExist,Gt, andLt.
9. Keep a Pod off tainted nodes, or let it tolerate them
Taints repel Pods unless the Pod tolerates the taint.
apiVersion: v1
kind: Pod
metadata:
name: gpu-consumer
spec:
tolerations:
- key: "nvidia.com/gpu"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: infer
image: ghcr.io/acme/infer:1.0.0- Without a matching toleration, a
NoScheduletaint makes the node infeasible. - A toleration permits scheduling but does not force it; you still need affinity to attract the Pod.
- Node pools for GPU or spot capacity are commonly tainted so only opted-in workloads land there.
- The three effects are
NoSchedule,PreferNoSchedule, andNoExecute.
10. Spread replicas across zones with topology spread
Even spread reduces the blast radius of a zone failure.
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: webmaxSkewcaps the imbalance in matching Pods between any two zones.whenUnsatisfiable: DoNotSchedulemakes it a hard rule;ScheduleAnywaymakes it a soft preference.- The
labelSelectordefines which Pods count toward the spread. - Combine with a Deployment of several replicas to see the effect across zones.
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).