Kubernetes nodeSelector and Taints: Pin a Workload to a Node Pool
nodeSelector attracts a pod to a node pool; a toleration lets it past the pool's taint. You need both to pin a workload to dedicated hardware and keep everything else off. Here's how the pattern works and what the manifest looks like.
Why you need both nodeSelector and a toleration
Taints and node selectors solve opposite halves of the same problem.
A taint on a node repels pods - dedicated=batch:NoSchedule means "nothing
schedules here unless it explicitly opts in." It keeps general workloads off your
batch pool. Without a taint, other pods will happily land on your dedicated hardware.
A nodeSelector attracts a pod to nodes that carry a matching label -
workload: batch tells the scheduler "only place me on nodes with this label."
Without the selector, your batch pod could land on any untainted node.
Together: the taint keeps the crowd out, the selector steers your pod in.
Label and taint the node pool
Before the Deployment can use them, the nodes need to be labeled and tainted:
kubectl label node <node-name> workload=batch
kubectl taint node <node-name> dedicated=batch:NoSchedule
In managed clusters (GKE, EKS, AKS) this is done through the node pool configuration - the same logic, just applied at provisioning time.
The Deployment manifest
Add nodeSelector and tolerations under spec.template.spec:
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch
spec:
replicas: 1
selector:
matchLabels:
app: batch
template:
metadata:
labels:
app: batch
spec:
nodeSelector:
workload: batch # must match the node label
tolerations:
- key: dedicated
operator: Equal
value: batch
effect: NoSchedule # must match the taint effect
containers:
- name: batch
image: busybox:1.36
command: ["sleep", "86400"]
Apply it and confirm the fields landed in the live spec:
kubectl apply -f deployment.yaml
kubectl get deploy batch -o yaml | grep -A4 'nodeSelector\|tolerations'
Toleration fields explained
| Field | What it matches |
|---|---|
key |
The taint key (dedicated) |
operator |
Equal (exact match) or Exists (any value) |
value |
The taint value (batch) |
effect |
NoSchedule, PreferNoSchedule, or NoExecute |
A toleration with operator: Exists and no key is a wildcard - it tolerates
every taint on the node. Useful for DaemonSets, but too broad for workload
pinning.
NoSchedule vs NoExecute
NoSchedule prevents new pods from landing but leaves existing pods running.
NoExecute evicts pods that are already on the node and lack a matching
toleration (add tolerationSeconds to delay eviction). Use NoSchedule for
node pools; reserve NoExecute for drain-style operations.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Adding nodeSelector and tolerations to a Deployment manifest
- Matching toleration key/value/effect to the node pool's taint
- Verifying the scheduling constraints with kubectl get deploy -o yaml
FAQ
What is the difference between nodeSelector and taints in Kubernetes?
nodeSelector steers a pod toward nodes that have a specific label - it's a pod-side rule. Taints repel pods from a node unless the pod carries a matching toleration - it's a node-side rule. You need both to pin a workload to a dedicated pool and keep everything else off.
How do I pin a pod to a specific node pool in Kubernetes?
Label the nodes in the pool (e.g. workload=batch), taint them (e.g. dedicated=batch:NoSchedule), then add a matching nodeSelector and toleration to the Deployment's pod spec. The scheduler only places the pod on nodes that satisfy both.
What does NoSchedule mean on a Kubernetes taint?
NoSchedule prevents new pods without a matching toleration from being scheduled onto that node. Pods already running on the node are not evicted. Use NoExecute instead if you want to evict existing pods that don't tolerate the taint.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →