Learning Kubernetes - Episode 16 - Introduction and Explanation of Node Selector

Learning Kubernetes - Episode 16 - Introduction and Explanation of Node Selector

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.

Arman Dwi Pangestu
Arman Dwi PangestuMarch 19, 2026
0 views
9 min read

Introduction

Note

If you want to read the previous episode, you can click the Episode 15 thumbnail below

Episode 15Episode 15

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.

What Is Node Selector?

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:

  • Label-based selection - Uses key-value labels to match nodes
  • Simple syntax - Easy to understand and implement
  • Equality matching - Only supports exact label matches
  • Multiple labels - Can specify multiple labels (AND logic)
  • Scheduling constraint - Pods won't schedule if no matching nodes exist
  • Node labeling - Requires manual node labeling

Why Do We Need Node Selector?

Node Selector is useful for various scenarios where you need control over Pod placement:

  • Hardware requirements - Schedule GPU workloads on GPU nodes
  • Storage types - Place Pods on nodes with SSD or NVMe storage
  • Environment separation - Keep production and development Pods separate
  • Geographic location - Schedule Pods in specific regions or zones
  • Node capabilities - Use nodes with specific CPU architectures
  • Cost optimization - Use cheaper nodes for non-critical workloads
  • Compliance - Keep sensitive workloads on specific nodes
  • Performance - Schedule high-performance apps on powerful nodes

Without Node Selector, you would need to:

  • Manually schedule Pods on specific nodes
  • Use more complex affinity rules
  • Accept random Pod placement by scheduler

Node Labels

Before using Node Selector, you need to understand node labels. Labels are key-value pairs attached to nodes.

Viewing Node Labels

Check existing node labels:

Kubernetesbash
sudo kubectl get nodes --show-labels

View labels for a specific node:

Kubernetesbash
sudo kubectl describe node <node-name>

Built-in Node Labels

Kubernetes automatically adds several labels to nodes:

  • kubernetes.io/hostname - Node's hostname
  • kubernetes.io/os - Operating system (linux, windows)
  • kubernetes.io/arch - CPU architecture (amd64, arm64)
  • node.kubernetes.io/instance-type - Cloud instance type
  • topology.kubernetes.io/region - Cloud region
  • topology.kubernetes.io/zone - Cloud availability zone

Adding Custom Labels

Add a label to a node:

Kubernetesbash
sudo kubectl label nodes <node-name> <key>=<value>

Example - label a node with SSD storage:

Kubernetesbash
sudo kubectl label nodes node1 disktype=ssd

Example - label a node as production:

Kubernetesbash
sudo kubectl label nodes node2 environment=production

Example - label a node with GPU:

Kubernetesbash
sudo kubectl label nodes node3 gpu=nvidia-tesla-v100

Removing Labels

Remove a label from a node:

Kubernetesbash
sudo kubectl label nodes <node-name> <key>-

Example:

Kubernetesbash
sudo kubectl label nodes node1 disktype-

Updating Labels

Update an existing label:

Kubernetesbash
sudo kubectl label nodes <node-name> <key>=<new-value> --overwrite

Example:

Kubernetesbash
sudo kubectl label nodes node1 disktype=nvme --overwrite

Using Node Selector

Once nodes are labeled, you can use nodeSelector in Pod specifications.

Example 1: Basic Node Selector

First, label a node:

Kubernetesbash
sudo kubectl label nodes node1 disktype=ssd

Create a Pod with node selector:

Kubernetespod-node-selector.yml
apiVersion: v1
kind: Pod
metadata:
    name: nginx-ssd
spec:
    containers:
        - name: nginx
          image: nginx:1.25
    nodeSelector:
        disktype: ssd

Apply the configuration:

Kubernetesbash
sudo kubectl apply -f pod-node-selector.yml

Verify Pod placement:

Kubernetesbash
sudo kubectl get pod nginx-ssd -o wide

The Pod will only schedule on nodes with disktype=ssd label.

Example 2: Multiple Label Selectors

You can specify multiple labels (all must match):

First, label a node with multiple labels:

Kubernetesbash
sudo kubectl label nodes node1 disktype=ssd
sudo kubectl label nodes node1 environment=production

Create a Pod requiring both labels:

Kubernetespod-multiple-selectors.yml
apiVersion: v1
kind: Pod
metadata:
    name: app-production
spec:
    containers:
        - name: app
          image: nginx:1.25
    nodeSelector:
        disktype: ssd
        environment: production

This Pod only schedules on nodes with BOTH disktype=ssd AND environment=production.

Example 3: Using Built-in Labels

Use Kubernetes built-in labels:

Kubernetespod-builtin-labels.yml
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: amd64

This Pod only runs on Linux nodes with AMD64 architecture.

Node Selector with Deployments

Node Selector works with all Pod controllers:

Example 1: Deployment with Node Selector

Kubernetesdeployment-node-selector.yml
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: ssd

All 3 replicas will only schedule on nodes with both labels.

Example 2: DaemonSet with Node Selector

Kubernetesdaemonset-node-selector.yml
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: enabled

This DaemonSet only runs on nodes labeled with monitoring=enabled.

Example 3: Job with Node Selector

Kubernetesjob-node-selector.yml
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: Never

This Job runs on nodes suitable for batch processing with high memory.

Practical Examples

Example 1: GPU Workload

Label GPU nodes:

Kubernetesbash
sudo kubectl label nodes gpu-node-1 gpu=nvidia-tesla-v100
sudo kubectl label nodes gpu-node-2 gpu=nvidia-tesla-v100

Create GPU workload:

Kubernetesgpu-workload.yml
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-v100

Example 2: Environment Separation

Label nodes by environment:

Kubernetesbash
sudo kubectl label nodes node1 node2 environment=production
sudo kubectl label nodes node3 node4 environment=development

Production deployment:

Kubernetesproduction-deployment.yml
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: production

Development deployment:

Kubernetesdevelopment-deployment.yml
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: development

Example 3: Storage Type Selection

Label nodes by storage type:

Kubernetesbash
sudo kubectl label nodes node1 node2 disktype=ssd
sudo kubectl label nodes node3 node4 disktype=hdd

Database on SSD:

Kubernetesdatabase-ssd.yml
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: ssd

Log storage on HDD:

Kuberneteslog-storage-hdd.yml
apiVersion: v1
kind: Pod
metadata:
    name: log-aggregator
spec:
    containers:
        - name: aggregator
          image: fluentd:latest
    nodeSelector:
        disktype: hdd

Example 4: Geographic Placement

Label nodes by region:

Kubernetesbash
sudo kubectl label nodes node1 node2 region=us-east
sudo kubectl label nodes node3 node4 region=us-west

Deploy to specific region:

Kubernetesregional-deployment.yml
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-east

Example 5: Cost Optimization

Label nodes by cost tier:

Kubernetesbash
sudo kubectl label nodes node1 node2 cost=high-performance
sudo kubectl label nodes node3 node4 node5 cost=standard

Critical workload on high-performance nodes:

Kubernetescritical-workload.yml
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-performance

Non-critical workload on standard nodes:

Kubernetesnon-critical-workload.yml
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: standard

Troubleshooting Node Selector

Pod Stuck in Pending State

If a Pod is pending, check if matching nodes exist:

Kubernetesbash
sudo kubectl describe pod <pod-name>

Look for events like:

plaintext
Warning  FailedScheduling  Pod didn't match node selector

Check available nodes with required labels:

Kubernetesbash
sudo kubectl get nodes -l disktype=ssd

If no nodes match, either:

  1. Add the label to a node
  2. Remove/modify the nodeSelector

Checking Pod Placement

Verify where Pods are running:

Kubernetesbash
sudo kubectl get pods -o wide

Check if Pods are on expected nodes:

Kubernetesbash
sudo kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,NODE_SELECTOR:.spec.nodeSelector

Listing Pods by Node

See all Pods on a specific node:

Kubernetesbash
sudo kubectl get pods --all-namespaces -o wide --field-selector spec.nodeName=<node-name>

Common Mistakes and Pitfalls

Mistake 1: Typo in Label Names

Problem: Label name mismatch between node and Pod.

Solution: Double-check label names:

Kubernetesbash
# Check node labels
sudo kubectl get nodes --show-labels
 
# Verify Pod nodeSelector
sudo kubectl get pod <pod-name> -o yaml | grep -A 5 nodeSelector

Mistake 2: No Matching Nodes

Problem: No nodes have the required labels.

Solution: Verify nodes with required labels exist:

Kubernetesbash
sudo kubectl get nodes -l <key>=<value>

Mistake 3: Forgetting to Label New Nodes

Problem: New nodes added without required labels.

Solution: Create a checklist or automation for labeling new nodes:

Kubernetesbash
# Label new node immediately after adding
sudo kubectl label nodes <new-node> environment=production disktype=ssd

Mistake 4: Using OR Logic

Problem: Expecting OR logic, but nodeSelector uses AND.

Solution: Node Selector only supports AND logic. For OR logic, use Node Affinity (covered in next episode).

Kubernetesyml
# This requires BOTH labels (AND logic)
nodeSelector:
    disktype: ssd
    environment: production

Mistake 5: Overconstraining Pods

Problem: Too many nodeSelector constraints prevent scheduling.

Solution: Use only necessary constraints:

Kubernetesyml
# 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: ssd

Mistake 6: Not Considering Node Capacity

Problem: All matching nodes are full.

Solution: Ensure enough capacity on labeled nodes:

Kubernetesbash
# Check node capacity
sudo kubectl describe nodes -l environment=production

Best Practices

Use Meaningful Label Names

Choose clear, descriptive label names:

Kubernetesbash
# 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=prod

Document Your Labeling Strategy

Maintain documentation of your label schema:

yaml
# Label Schema Documentation
# disktype: ssd | hdd | nvme
# environment: production | staging | development
# region: us-east | us-west | eu-central
# gpu: nvidia-tesla-v100 | nvidia-a100 | none

Use Consistent Label Values

Standardize label values across your cluster:

Kubernetesbash
# Consistent
environment=production  # Always lowercase
environment=staging
environment=development
 
# Inconsistent (avoid)
environment=Production
environment=STAGING
environment=dev

Label Nodes During Provisioning

Automate node labeling during cluster setup:

Kubernetesbash
# In node provisioning script
kubectl label nodes $NODE_NAME \
    environment=production \
    disktype=ssd \
    region=us-east

Combine with Resource Requests

Always set resource requests with nodeSelector:

Kubernetesyml
spec:
    containers:
        - name: app
          image: app:latest
          resources:
              requests:
                  memory: "512Mi"
                  cpu: "500m"
              limits:
                  memory: "1Gi"
                  cpu: "1000m"
    nodeSelector:
        environment: production

Use for Critical Workloads

Reserve nodeSelector for workloads with specific requirements:

Kubernetesyml
# 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 needed

Monitor Node Label Changes

Track label changes for audit purposes:

Kubernetesbash
# View recent events
sudo kubectl get events --sort-by='.lastTimestamp' | grep -i label

Test Before Production

Test nodeSelector in development first:

Kubernetesbash
# 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 production

Node Selector Limitations

Node Selector is simple but has limitations:

Only Equality Matching

Cannot use operators like "not equal" or "in":

Kubernetesyml
# Node Selector - only supports equality
nodeSelector:
    disktype: ssd  # Must equal "ssd"
 
# Cannot do:
# disktype != hdd
# disktype in (ssd, nvme)
# disktype exists

For advanced matching, use Node Affinity (next episode).

AND Logic Only

Cannot express OR logic:

Kubernetesyml
# Cannot say: disktype=ssd OR disktype=nvme
# All labels must match (AND logic)
nodeSelector:
    disktype: ssd
    environment: production

No Soft Preferences

Node Selector is a hard requirement. Pod won't schedule if no matching nodes exist.

For soft preferences, use Node Affinity (next episode).

When to Use Node Selector

Use Node Selector when:

  • You need simple, straightforward node selection
  • You have clear, specific node requirements
  • You want easy-to-understand Pod specifications
  • You're using equality-based label matching

Consider Node Affinity when:

  • You need complex selection logic (OR, NOT, IN)
  • You want soft preferences (preferred but not required)
  • You need more flexible matching operators
  • You're implementing advanced scheduling strategies

Conclusion

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 the simplest way to constrain Pod placement
  • Uses label matching to select nodes
  • Requires manual node labeling before use
  • Supports multiple labels with AND logic
  • Works with all Pod controllers (Deployment, DaemonSet, Job, etc.)
  • Only supports equality matching (key=value)
  • Pod won't schedule if no matching nodes exist
  • Perfect for hardware requirements, environment separation, and cost optimization
  • Use meaningful, consistent label names
  • Document your labeling strategy

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!


Related Posts