Scheduling Controls: Node Selectors, Affinity, Taints, and Tolerations
The scheduler decides where Pods run. Scheduling controls let you influence placement without hardcoding infrastructure assumptions into application behavior.
What this lesson covers
The Kubernetes scheduler decides where Pods run. Scheduling controls let you influence that placement using node selectors, affinity, taints, tolerations, and topology spread.
- How scheduling works at a high level
- How node labels affect placement
- How nodeSelector works
- What node affinity adds
- What taints and tolerations do
- How to troubleshoot Pending Pods
The short version
nodeSelector: Place Pods only on nodes with matching labels. node affinity: More expressive node selection rules. taints: Repel Pods from nodes. tolerations: Allow Pods to tolerate matching taints. topology spread: Distribute Pods across failure domains.
Before you start
kubectl config current-context kubectl get nodes kubectl create namespace scheduling-lab
Step 1: Inspect node labels
kubectl get nodes --show-labels
Nodes have labels such as operating system, architecture, hostname, and Kubernetes role. These labels can be used for scheduling.
Step 2: Add a training label to your kind node
kubectl label node stealth-k8s-lab-control-plane workload-type=training
Confirm:
kubectl get nodes --show-labels
Step 3: Create a Pod with nodeSelector
Create a file named:
nodeselector-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nodeselector-pod
namespace: scheduling-lab
spec:
nodeSelector:
workload-type: training
containers:
- name: app
image: nginx:latest
kubectl apply -f nodeselector-pod.yaml kubectl get pods -n scheduling-lab -o wide
Step 4: Create an unschedulable nodeSelector Pod
Create a file named:
bad-nodeselector-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: bad-nodeselector-pod
namespace: scheduling-lab
spec:
nodeSelector:
workload-type: does-not-exist
containers:
- name: app
image: nginx:latest
kubectl apply -f bad-nodeselector-pod.yaml kubectl get pods -n scheduling-lab kubectl describe pod bad-nodeselector-pod -n scheduling-lab
The Pod should remain Pending because no node matches the selector.
Step 5: Create a Pod with node affinity
Create a file named:
node-affinity-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-pod
namespace: scheduling-lab
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload-type
operator: In
values:
- training
containers:
- name: app
image: nginx:latest
kubectl apply -f node-affinity-pod.yaml kubectl get pods -n scheduling-lab -o wide
Step 6: Understand taints and tolerations
A taint on a node says, “Do not schedule Pods here unless they tolerate this taint.”
In a kind cluster, the control-plane node may already have scheduling behavior suitable for labs. Be cautious adding taints to your only node because it can block new Pods.
Step 7: Add a temporary taint
kubectl taint node stealth-k8s-lab-control-plane dedicated=training:NoSchedule
Step 8: Create a Pod without a toleration
Create a file named:
no-toleration-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: no-toleration-pod
namespace: scheduling-lab
spec:
containers:
- name: app
image: nginx:latest
kubectl apply -f no-toleration-pod.yaml kubectl get pods -n scheduling-lab kubectl describe pod no-toleration-pod -n scheduling-lab
The Pod may remain Pending because it does not tolerate the taint.
Step 9: Create a Pod with a toleration
Create a file named:
toleration-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: toleration-pod
namespace: scheduling-lab
spec:
tolerations:
- key: "dedicated"
operator: "Equal"
value: "training"
effect: "NoSchedule"
containers:
- name: app
image: nginx:latest
kubectl apply -f toleration-pod.yaml kubectl get pods -n scheduling-lab -o wide
Step 10: Remove the taint
Remove the taint so your lab cluster returns to normal scheduling behavior:
kubectl taint node stealth-k8s-lab-control-plane dedicated=training:NoSchedule-
Step 11: Understand topology spread
Topology spread constraints help distribute Pods across failure domains such as zones, nodes, or racks. They matter more in multi-node and multi-zone clusters than in a one-node kind lab.
The mental model is simple: do not put all replicas in the same failure domain when you have better options.
Common scheduling troubleshooting commands
kubectl get pods -n <namespace> kubectl describe pod <pod-name> -n <namespace> kubectl get nodes --show-labels kubectl describe node <node-name> kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp
Common mistakes
- Using node names instead of stable labels.
- Adding constraints that no node satisfies.
- Forgetting to remove a lab taint.
- Assuming tolerations force placement. They only allow placement onto tainted nodes.
- Using single-node lab behavior to infer multi-node production scheduling.
Step 12: Clean up
kubectl delete namespace scheduling-lab kubectl label node stealth-k8s-lab-control-plane workload-type-
Check your understanding
- What does the scheduler decide?
- What does nodeSelector use?
- What does node affinity add beyond nodeSelector?
- What does a taint do?
- What does a toleration do?
- Does a toleration force a Pod onto a node?
- What command shows node labels?
- What command explains why a Pod is Pending?
- Why can over-constraining Pods be dangerous?
- What does topology spread try to improve?
Answer key
- Which node a Pod should run on.
- Node labels.
- More expressive matching rules and preferences.
- Repels Pods unless they tolerate it.
- Allows a Pod to be scheduled onto a node with a matching taint.
- No.
- kubectl get nodes --show-labels.
- kubectl describe pod <pod-name>.
- No node may satisfy all constraints, leaving Pods Pending.
- Distribution across failure domains.