Use nodeSelector for Pod Placement
Learn how to use nodeSelector for pod placement in Kubernetes. This lesson covers the core concept, step-by-step setup, hands-on exercise, troubleshooting, and what to study next.
Focus: use nodeSelector for pod placement
You've deployed a pod, but it lands on a node that's overloaded, running on spot instances, or even in a different region — causing latency, cost overruns, or outright failures. That's the problem nodeSelector solves: it's a simple, declarative way to tell Kubernetes "this pod must run only on nodes that have a specific label." Unlike complex affinity rules, nodeSelector is straightforward to understand and perfect for beginners who need predictable pod placement without diving into advanced scheduling logic.
The problem this lesson solves
Kubernetes schedules pods onto nodes based on resource requests and default algorithms — but it has no idea where you want the pod to go. Without nodeSelector, your database pod might end up on a regular worker node instead of a dedicated SSD-backed node, or your GPU-accelerated ML training job could land on a CPU-only machine. This unpredictability leads to:
- Performance issues: Pods needing fast storage or specialized hardware may fail or run slowly.
- Cost inefficiency: Expensive GPU nodes might sit idle while GPU workloads run on standard nodes.
- Compliance risks: Workloads requiring data residency or security isolation may be placed in the wrong cluster zone.
nodeSelector solves this by giving you simple label-based control — you tag nodes with descriptive labels (e.g., disk=ssd, tier=ml) and then tell each pod which label it requires.
Core concept / mental model
Think of nodeSelector as a bouncer at a club. Each node has a set of "passes" (labels), and each pod submits a "VIP list" (a nodeSelector map). The scheduler checks if the node has all the labels the pod requires; if not, the pod is rejected from that node and stays pending.
Key definitions:
- Node labels: Key-value pairs attached to a node object, e.g.,
kubernetes.io/os=linux,topology.kubernetes.io/region=us-east-1, or custom tags likesize=large. - nodeSelector: A
map[string]stringfield in the pod spec. Only nodes with matching labels (all key-value pairs) are considered eligible. - Scheduling: The scheduler's first filter is node predicate —
nodeSelectoris evaluated here. If no nodes match, the pod staysPendingwith aFailedSchedulingevent.
Pro tip:
nodeSelectoruses exact matching — both key and value must match. There's no partial or regex support. For advanced matching (likeIn,NotIn,Exists), use node affinity (future lesson).
How it works step by step
1. Label your nodes
First, add labels to the nodes that should host specific workloads. This is typically done by cluster admins or via infrastructure-as-code.
# See current node labels
kubectl get nodes --show-labels
# Add a label to a specific node
kubectl label nodes node1 disk=ssd
kubectl label nodes node2 gpu=true
2. Add nodeSelector to your pod spec
Edit your pod or deployment YAML to include nodeSelector under spec:
apiVersion: v1
kind: Pod
metadata:
name: my-ssd-pod
spec:
containers:
- name: app
image: nginx
nodeSelector:
disk: ssd
This pod will only run on nodes that have the label disk=ssd. If a node has disk=ssd and other labels, it's still eligible — the pod doesn't require those other labels.
3. Multiple selectors
You can require multiple labels; all must match:
nodeSelector:
disk: ssd
gpu: "true" # Note: YAML booleans should be strings in nodeSelector values
tier: ml
Only nodes that have all three labels will be selected.
Hands-on walkthrough
Let's create a multi-node cluster (if you only have Minikube or a single-node cluster, label the single node and observe scheduling).
Step 1: Label a node
kubectl label nodes minikube disk=hdd
kubectl label nodes minikube env=dev
Step 2: Create a pod targeting SSD
Create ssd-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: ssd-pod
spec:
containers:
- name: app
image: nginx
nodeSelector:
disk: ssd
Apply and check:
kubectl apply -f ssd-pod.yaml
kubectl get pod ssd-pod -o wide
Expected output (pending):
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ssd-pod 0/1 Pending 0 10s <none> <none> <none> <none>
The pod stays Pending because no node has disk=ssd. Let's fix that:
kubectl label nodes minikube disk=ssd
kubectl get pod ssd-pod -o wide
Expected output (scheduled):
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ssd-pod 1/1 Running 0 5s 10.244.0.5 minikube <none> <none>
What happened? The scheduler re-evaluated the pending pod (it's requeued periodically). Once the label existed, the node matched and the pod was scheduled.
Step 3: Verify in describe
kubectl describe pod ssd-pod | grep -A5 Events
Look for Successfully assigned or FailedScheduling.
Compare options / when to choose what
| Feature | nodeSelector | Node Affinity (requiredDuringScheduling) | Pod Anti-Affinity |
|---|---|---|---|
| Matching logic | Exact key=value | Operators: In, NotIn, Exists, Gt, Lt | Same as node affinity but for spreading |
| Multiple selectors | AND (all must match) | AND across terms, OR inside expressions | Complex topology constraints |
| Complexity | Simple string map | More YAML, but flexible | Advanced |
| Use case | Quick hardware pinning, environment segregation | Fine-grained scheduling, multi-condition | High availability, zone awareness |
When to choose nodeSelector:
- You're a beginner and need simple label-based rules.
- You only have a few distinct node types (e.g.,
disk=ssd,gpu=true). - You don't need
NotInorExistssemantics. - Your workload doesn't require dynamic re-evaluation (nodeSelector is evaluated once at scheduling time).
Pro tip: For production, prefer node affinity over
nodeSelectorbecause it's more expressive and avoids unneeded string casts. ButnodeSelectoris faster to write and understand.
Troubleshooting & edge cases
Pod stays Pending indefinitely
Check node labels:
kubectl get nodes --show-labels | grep <expected-label>
kubectl describe pod <pod-name> | grep -A10 Events
If you see FailedScheduling with 0/1 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, you have a label mismatch.
Case sensitivity
Node labels are case-sensitive. Disk=SSD is different from disk:ssd and disk:SSD. Always verify the exact key-value via kubectl describe node <name>.
Multiple labels not matching
If you specify:
nodeSelector:
disk: ssd
gpu: "true"
And the node has disk=ssd but no gpu=true, the pod won't schedule. Remove the unmatched label from the spec or add it to the node.
Common mistake: YAML parsing of values like
trueorfalse. Always quote them as strings ("true") becausenodeSelectorvalues are strings, not booleans.
NodeSelector and taints/tolerations
nodeSelector and taints/tolerations work independently. A pod with nodeSelector: {disk: ssd} on a node that also has a taint will not be scheduled unless the pod has a matching toleration. If you need both, combine them.
What you learned & what's next
You now know how to use nodeSelector for pod placement — from labeling nodes, writing the selector in YAML, to troubleshooting pending pods. You covered:
- Why nodeSelector is essential for hardware and environment constraints.
- The mental model of label-based scheduling.
- Step-by-step commands to label and schedule.
- A complete hands-on exercise with a pending-to-running example.
- How nodeSelector compares to node affinity (your next advanced topic).
Next lesson: Node Affinity — you'll learn how to use In, NotIn, Exists operators to make scheduling rules even more powerful. You'll also see how to set soft (preferred) scheduling hints, not just hard constraints.
Ready to master advanced pod placement? Let's go!
Practice recap
Try this mini-exercise: Create a new node label size=small and a pod with nodeSelector: {size: small}. Then, change the label to size=medium and observe the pod — does it move? (Hint: nodeSelector is required during scheduling, not preferred.) Finally, add a second label zone=us-east and update the pod spec to require both. Verify scheduling success.
Common mistakes
- Forgetting to label the target node before creating the pod — the scheduler can't match a label that doesn't exist, causing the pod to stay Pending until the label is added.
- Using unquoted boolean or numeric values in nodeSelector (e.g.,
gpu: trueinstead ofgpu: "true") — YAML interpretstrueas a boolean, but nodeSelector expects a string, leading to silent failures or errors. - Assuming nodeSelector supports OR logic across multiple keys — it only does AND (all must match). For OR, you need node affinity with multiple expressions.
- Overwriting existing labels when using
kubectl labelwithout--overwrite— if a label key already exists, the command fails unless--overwriteis used.
Variations
- Node affinity with
requiredDuringSchedulingIgnoredDuringExecution— offers operators likeIn,NotIn,Existsfor more flexible matching. - Node selector terms in multi-arch deployments — combine
beta.kubernetes.io/arch=amd64withbeta.kubernetes.io/os=linuxto pin pods to specific architectures. - Using custom labels for environment segregation (e.g.,
env=prod,env=staging) — ensures development pods never land on production nodes.
Real-world use cases
- Pin GPU-training pods to nodes labeled
gpu=trueto avoid wasting expensive GPU capacity on CPU-bound workloads. - Ensure database pods run only on nodes with SSD storage by using
nodeSelector: {disk: ssd}for consistent I/O performance. - Isolate tenant workloads in a shared cluster by labeling nodes with
tenant=team-aand using nodeSelector to prevent cross-tenant pod placement.
Key takeaways
- nodeSelector is a simple, exact-match filter that tells the scheduler which nodes a pod can run on.
- Node labels must be added manually (or via automation) before nodeSelector can work — always verify with
kubectl get nodes --show-labels. - Multiple nodeSelector keys act as AND conditions; all labels must match on the node.
- Pending pods due to missing labels are automatically scheduled when the label is added — no manual restart needed.
- For advanced matching (OR, NotIn, Exists), use node affinity instead of nodeSelector.
- Watch for YAML type coercion — always quote values that look like booleans or numbers.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.