In this episode, we'll discuss Kubernetes Node Selector, a simple mechanism for controlling Pod placement on specific nodes. We'll learn how to use labels and node selectors to schedule Pods on nodes with specific characteristics.

Note
If you want to read the previous episode, you can click the Episode 15 thumbnail below
In the previous episode, we learned about CronJob, which creates Jobs on a time-based schedule. In episode 16, we'll discuss Node Selector, a fundamental concept for controlling where Pods run in your cluster.
Note: Here I'll be using a Kubernetes Cluster installed through K3s.
By default, Kubernetes scheduler automatically places Pods on available nodes. But sometimes you need control over Pod placement - maybe you want GPU workloads on GPU nodes, or production Pods on high-performance nodes. Node Selector provides a simple way to achieve this.
Node Selector is the simplest way to constrain Pods to run on specific nodes. It uses label matching to select nodes where Pods should be scheduled. You add labels to nodes, then specify those labels in Pod specifications using nodeSelector.
Think of Node Selector like filtering - you label nodes with characteristics (GPU, SSD, high-memory), then tell Pods to only run on nodes with specific labels. The scheduler only considers nodes that match all specified labels.
Key characteristics of Node Selector:
Node Selector is useful for various scenarios where you need control over Pod placement:
Without Node Selector, you would need to:
Before using Node Selector, you need to understand node labels. Labels are key-value pairs attached to nodes.
Check existing node labels:
sudo kubectl get nodes --show-labelsView labels for a specific node:
sudo kubectl describe node <node-name>Kubernetes automatically adds several labels to nodes:
kubernetes.io/hostname - Node's hostnamekubernetes.io/os - Operating system (linux, windows)kubernetes.io/arch - CPU architecture (amd64, arm64)node.kubernetes.io/instance-type - Cloud instance typetopology.kubernetes.io/region - Cloud regiontopology.kubernetes.io/zone - Cloud availability zoneAdd a label to a node:
sudo kubectl label nodes <node-name> <key>=<value>Example - label a node with SSD storage:
sudo kubectl label nodes node1 disktype=ssdExample - label a node as production:
sudo kubectl label nodes node2 environment=productionExample - label a node with GPU:
sudo kubectl label nodes node3 gpu=nvidia-tesla-v100Remove a label from a node:
sudo kubectl label nodes <node-name> <key>-Example:
sudo kubectl label nodes node1 disktype-Update an existing label:
sudo kubectl label nodes <node-name> <key>=<new-value> --overwriteExample:
sudo kubectl label nodes node1 disktype=nvme --overwriteOnce nodes are labeled, you can use nodeSelector in Pod specifications.
First, label a node:
sudo kubectl label nodes node1 disktype=ssdCreate a Pod with node selector:
apiVersion: v1
kind: Pod
metadata:
name: nginx-ssd
spec:
containers:
- name: nginx
image: nginx:1.25
nodeSelector:
disktype: ssdApply the configuration:
sudo kubectl apply -f pod-node-selector.ymlVerify Pod placement:
sudo kubectl get pod nginx-ssd -o wideThe Pod will only schedule on nodes with disktype=ssd label.
You can specify multiple labels (all must match):
First, label a node with multiple labels:
sudo kubectl label nodes node1 disktype=ssd
sudo kubectl label nodes node1 environment=productionCreate a Pod requiring both labels:
apiVersion: v1
kind: Pod
metadata:
name: app-production
spec:
containers:
- name: app
image: nginx:1.25
nodeSelector:
disktype: ssd
environment: productionThis Pod only schedules on nodes with BOTH disktype=ssd AND environment=production.
Use Kubernetes built-in labels:
apiVersion: v1
kind: Pod
metadata:
name: linux-amd64-pod
spec:
containers:
- name: app
image: nginx:1.25
nodeSelector:
kubernetes.io/os: linux
kubernetes.io/arch: amd64This Pod only runs on Linux nodes with AMD64 architecture.
Node Selector works with all Pod controllers:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
nodeSelector:
environment: production
disktype: ssdAll 3 replicas will only schedule on nodes with both labels.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: monitoring-agent
spec:
selector:
matchLabels:
app: monitoring
template:
metadata:
labels:
app: monitoring
spec:
containers:
- name: agent
image: monitoring-agent:latest
nodeSelector:
monitoring: enabledThis DaemonSet only runs on nodes labeled with monitoring=enabled.
apiVersion: batch/v1
kind: Job
metadata:
name: data-processor
spec:
template:
spec:
containers:
- name: processor
image: data-processor:latest
resources:
requests:
memory: "2Gi"
cpu: "2000m"
nodeSelector:
workload: batch-processing
memory: high
restartPolicy: NeverThis Job runs on nodes suitable for batch processing with high memory.
Label GPU nodes:
sudo kubectl label nodes gpu-node-1 gpu=nvidia-tesla-v100
sudo kubectl label nodes gpu-node-2 gpu=nvidia-tesla-v100Create GPU workload:
apiVersion: v1
kind: Pod
metadata:
name: ml-training
spec:
containers:
- name: trainer
image: tensorflow/tensorflow:latest-gpu
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
gpu: nvidia-tesla-v100Label nodes by environment:
sudo kubectl label nodes node1 node2 environment=production
sudo kubectl label nodes node3 node4 environment=developmentProduction deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-production
spec:
replicas: 5
selector:
matchLabels:
app: api
env: prod
template:
metadata:
labels:
app: api
env: prod
spec:
containers:
- name: api
image: api:v2.0
nodeSelector:
environment: productionDevelopment deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-development
spec:
replicas: 2
selector:
matchLabels:
app: api
env: dev
template:
metadata:
labels:
app: api
env: dev
spec:
containers:
- name: api
image: api:dev
nodeSelector:
environment: developmentLabel nodes by storage type:
sudo kubectl label nodes node1 node2 disktype=ssd
sudo kubectl label nodes node3 node4 disktype=hddDatabase on SSD:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
nodeSelector:
disktype: ssdLog storage on HDD:
apiVersion: v1
kind: Pod
metadata:
name: log-aggregator
spec:
containers:
- name: aggregator
image: fluentd:latest
nodeSelector:
disktype: hddLabel nodes by region:
sudo kubectl label nodes node1 node2 region=us-east
sudo kubectl label nodes node3 node4 region=us-westDeploy to specific region:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-us-east
spec:
replicas: 3
selector:
matchLabels:
app: web
region: us-east
template:
metadata:
labels:
app: web
region: us-east
spec:
containers:
- name: web
image: nginx:1.25
nodeSelector:
region: us-eastLabel nodes by cost tier:
sudo kubectl label nodes node1 node2 cost=high-performance
sudo kubectl label nodes node3 node4 node5 cost=standardCritical workload on high-performance nodes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
replicas: 3
selector:
matchLabels:
app: payment
template:
metadata:
labels:
app: payment
spec:
containers:
- name: payment
image: payment-service:latest
nodeSelector:
cost: high-performanceNon-critical workload on standard nodes:
apiVersion: apps/v1
kind: Deployment
metadata:
name: background-jobs
spec:
replicas: 5
selector:
matchLabels:
app: jobs
template:
metadata:
labels:
app: jobs
spec:
containers:
- name: worker
image: job-worker:latest
nodeSelector:
cost: standardIf a Pod is pending, check if matching nodes exist:
sudo kubectl describe pod <pod-name>Look for events like:
Warning FailedScheduling Pod didn't match node selectorCheck available nodes with required labels:
sudo kubectl get nodes -l disktype=ssdIf no nodes match, either:
Verify where Pods are running:
sudo kubectl get pods -o wideCheck if Pods are on expected nodes:
sudo kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,NODE_SELECTOR:.spec.nodeSelectorSee all Pods on a specific node:
sudo kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=<node-name>Problem: Label name mismatch between node and Pod.
Solution: Double-check label names:
# Check node labels
sudo kubectl get nodes --show-labels
# Verify Pod nodeSelector
sudo kubectl get pod <pod-name> -o yaml | grep -A 5 nodeSelectorProblem: No nodes have the required labels.
Solution: Verify nodes with required labels exist:
sudo kubectl get nodes -l <key>=<value>Problem: New nodes added without required labels.
Solution: Create a checklist or automation for labeling new nodes:
# Label new node immediately after adding
sudo kubectl label nodes <new-node> environment=production disktype=ssdProblem: Expecting OR logic, but nodeSelector uses AND.
Solution: Node Selector only supports AND logic. For OR logic, use Node Affinity (covered in next episode).
# This requires BOTH labels (AND logic)
nodeSelector:
disktype: ssd
environment: productionProblem: Too many nodeSelector constraints prevent scheduling.
Solution: Use only necessary constraints:
# Too constrained
nodeSelector:
disktype: ssd
environment: production
region: us-east
zone: us-east-1a
instance-type: m5.xlarge
# Better - only essential constraints
nodeSelector:
environment: production
disktype: ssdProblem: All matching nodes are full.
Solution: Ensure enough capacity on labeled nodes:
# Check node capacity
sudo kubectl describe nodes -l environment=productionChoose clear, descriptive label names:
# Good
sudo kubectl label nodes node1 disktype=ssd
sudo kubectl label nodes node1 environment=production
# Avoid
sudo kubectl label nodes node1 type=1
sudo kubectl label nodes node1 env=prodMaintain documentation of your label schema:
# Label Schema Documentation
# disktype: ssd | hdd | nvme
# environment: production | staging | development
# region: us-east | us-west | eu-central
# gpu: nvidia-tesla-v100 | nvidia-a100 | noneStandardize label values across your cluster:
# Consistent
environment=production # Always lowercase
environment=staging
environment=development
# Inconsistent (avoid)
environment=Production
environment=STAGING
environment=devAutomate node labeling during cluster setup:
# In node provisioning script
kubectl label nodes $NODE_NAME \
environment=production \
disktype=ssd \
region=us-eastAlways set resource requests with nodeSelector:
spec:
containers:
- name: app
image: app:latest
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
nodeSelector:
environment: productionReserve nodeSelector for workloads with specific requirements:
# Good use case - GPU workload
nodeSelector:
gpu: nvidia-tesla-v100
# Good use case - High-performance database
nodeSelector:
disktype: nvme
memory: high
# Unnecessary - generic web app
# nodeSelector:
# kubernetes.io/os: linux # Usually not neededTrack label changes for audit purposes:
# View recent events
sudo kubectl get events --sort-by='.lastTimestamp' | grep -i labelTest nodeSelector in development first:
# Test in dev namespace
kubectl apply -f pod-node-selector.yml -n development
# Verify placement
kubectl get pod -n development -o wide
# Then deploy to production
kubectl apply -f pod-node-selector.yml -n productionNode Selector is simple but has limitations:
Cannot use operators like "not equal" or "in":
# Node Selector - only supports equality
nodeSelector:
disktype: ssd # Must equal "ssd"
# Cannot do:
# disktype != hdd
# disktype in (ssd, nvme)
# disktype existsFor advanced matching, use Node Affinity (next episode).
Cannot express OR logic:
# Cannot say: disktype=ssd OR disktype=nvme
# All labels must match (AND logic)
nodeSelector:
disktype: ssd
environment: productionNode Selector is a hard requirement. Pod won't schedule if no matching nodes exist.
For soft preferences, use Node Affinity (next episode).
Use Node Selector when:
Consider Node Affinity when:
In episode 16, we've explored Node Selector in Kubernetes in depth. We've learned what Node Selector is, how to label nodes, and how to use nodeSelector to control Pod placement.
Key takeaways:
Node Selector is essential for controlling Pod placement in Kubernetes. By understanding Node Selector, you can ensure workloads run on appropriate nodes, optimize resource usage, and maintain environment separation.
Are you getting a clearer understanding of Node Selector in Kubernetes? In the next episode 17, we'll discuss working with the all keyword, which provides a convenient way to manage multiple Kubernetes resources at once using kubectl get all and kubectl delete all. Keep your learning momentum going and look forward to the next episode!