Skip to main content
RunBook Academy

KubernetesV · Kubernetes Objects and MetadataKubernetes objects and metadata

Selectors — matching objects by labels

Intermediate⏱ ~16 minkubectl

What you'll learn

  • Distinguish equality-based from set-based label selectors
  • Identify where label selectors are used: services, deployments, network policies, jobs, HPA
  • Explain why Deployment selectors are immutable and what this means for production changes
  • Apply label selectors in kubectl commands and admission policies

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

Label selectors are the query language of Kubernetes. Every controller that targets a set of objects — Service, Deployment, NetworkPolicy, Job, HPA — does so via a selector. This lesson covers the selector grammar and the production patterns that arise from selector discipline.

Two forms of selector

Equality-based

app = web
env != dev
tier in (frontend, backend)

Match labels with the exact value or exclude them.

Set-based

env notin (dev, staging)
!legacy
partition

Match labels by set membership, absence, or presence.

Set-based is a superset of equality-based; app = web is the same as app in (web). Production deployments use set-based because it is more expressive.

Where selectors are used

Service.spec.selector

The Service routes traffic to Pods with matching labels:

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080

The Service’s selector is required. Without it, the Service has no Endpoints and routes no traffic.

Deployment.spec.selector

The Deployment owns Pods with matching labels:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.1

The Deployment’s selector must match its Pod template’s labels. If they don’t match, the API server rejects the manifest.

NetworkPolicy.spec.podSelector

The NetworkPolicy applies to Pods with matching labels:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
spec:
  podSelector:
    matchLabels:
      app: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend

Job.spec.template.metadata.labels + Job.spec.selector.matchLabels

Since Kubernetes 1.27, Jobs can use a selector to manage their Pods. This enables:

  • Pause and resume of a Job
  • Suspend and resume without losing track of Pods
  • Safer retries on Job updates
apiVersion: batch/v1
kind: Job
metadata:
  name: batch-job
spec:
  selector:
    matchLabels:
      app: batch
  template:
    metadata:
      labels:
        app: batch
    spec:
      restartPolicy: Never
      containers:
      - name: job
        image: job-runner:v1

HPA.spec.scaleTargetRef

The HPA scales a target Deployment/StatefulSet/ReplicaSet:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Note: HPA references by name, not by selector. But it scales the Deployment, which owns Pods via its own selector.

DaemonSet.spec.selector

Same as Deployment: the DaemonSet’s selector must match its Pod template’s labels.

Why Deployment selectors are immutable

The Deployment controller uses the selector to identify which Pods it owns. Changing the selector would orphan the existing Pods (they’re no longer owned by the Deployment) and the new Pods (the Deployment wouldn’t own them until they had the new labels).

To prevent this, the API server rejects changes to a Deployment’s spec.selector after creation:

The Deployment "web" is invalid: spec.selector: Invalid value: ...
field is immutable

To change a selector, the deployment must be deleted and recreated. This is destructive:

  • The old ReplicaSet’s Pods are deleted
  • A new ReplicaSet is created with the new selector
  • The new ReplicaSet’s Pods come up with the new template

In production, this is acceptable for major changes but expensive for routine changes. Convention: pick the selector once and never change it.

Selectors in kubectl commands

# Equality-based
kubectl get pods -l app=web
kubectl get pods -l 'app=web,env=prod'
kubectl get pods -l 'app!=web'
kubectl get pods -l 'app=web,env!=dev'

# Set-based
kubectl get pods -l 'app in (web,api)'
kubectl get pods -l 'env notin (dev,staging)'
kubectl get pods -l '!legacy'
kubectl get pods -l 'partition'

# Field selector (different from label selector)
kubectl get pods --field-selector=status.phase=Running
kubectl get pods --field-selector=spec.nodeName=worker-04

# Combined
kubectl get pods -l 'app=web' --field-selector=status.phase=Running

Field selectors are limited to specific fields (metadata.name, metadata.namespace, status.phase, spec.nodeName, spec.restartPolicy, etc.). Label selectors are the workhorse for application-defined selection.

Selector evaluation in the API server

The API server evaluates selectors efficiently. For Pods:

flowchart LR
    Q[Query: app=web] --> I[Indexer<br/>in-memory map]
    I -->|lookup| P[Matching Pods]

The API server maintains an index on labels (in etcd for storage; in the cache for queries). A selector query is a constant-time lookup, not a linear scan.

High-cardinality labels still cost index size and watch event volume, but the read path is fast.

How selectors interact with namespace

A selector does not cross namespaces by default. A Service in namespace prod routes only to Pods in prod:

# All web Pods in prod
kubectl get pods -n prod -l app=web

# All web Pods cluster-wide
kubectl get pods -A -l app=web

The namespace scope is implicit from where the selector is applied.

Production patterns

Pattern 1: stable deployment selector

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app.kubernetes.io/name: web
    app.kubernetes.io/instance: web-prod
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: web    # stable selector
  template:
    metadata:
      labels:
        app.kubernetes.io/name: web
        app.kubernetes.io/version: "1.27.2"  # additional label, not in selector
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.2

The selector uses a stable label (app.kubernetes.io/name). Changing the version label does not affect the selector; the Deployment continues to own the Pods.

Pattern 2: tier-based network policies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
spec:
  podSelector:
    matchLabels:
      tier: backend
  ingress:
  - from:
    - podSelector:
        matchLabels:
          tier: frontend

Pattern 3: service mesh with selectors

Service meshes (Istio, Linkerd) use selectors to choose which Pods to inject sidecars into. The convention: a label like app: web or istio.io/inject: true selects the Pods.

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    istio-injection: enabled

Pattern 4: HPA by deployment name

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Cross-course references

  • The Linux course part XXVII-Linux-Auth covers identity and group concepts that map onto selector-based grouping.
  • The Observability course part IX-Observability-Exporters covers Prometheus label selectors — similar trade-offs.
  • The Linux course part XXIX-Linux-Hardening covers classification schemes that map onto label-based enforcement.
  • The Docker course part XXXVIII-Docker-Secrets covers secret handling; selectors are not for secrets.

Quiz

Knowledge check · 4 questions

  1. Q1. Which selector matches Pods with label `app in (web, api)` but NOT with label `env=dev`?

  2. Q2. A Deployment''s `spec.selector` can be changed after creation as long as the new selector still matches the Pod template''s labels.

  3. Q3. An operator changes the Service's `spec.selector` from `app: web` to `app: web-v2`. The Service was previously routing to 3 Pods labeled `app: web`. After the change, the Endpoints are empty. Diagnose and remediate.

    Before: ```yaml apiVersion: v1 kind: Service metadata: name: web spec: selector: app: web ``` After: ```yaml apiVersion: v1 kind: Service metadata: name: web spec: selector: app: web-v2 # changed ``` Endpoints: ``` $ kubectl get endpoints web -n prod NAME ENDPOINTS AGE web <none> 30s ``` Pods: ``` $ kubectl get pods -n prod -l app=web NAME READY STATUS web-abc-1 1/1 Running web-abc-2 1/1 Running web-abc-3 1/1 Running $ kubectl get pods -n prod -l app=web-v2 No resources found ```

  4. Q4. Why are Deployment selectors immutable? What production pattern allows you to add labels to Pods without affecting the Deployment''s ownership?

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Pick a stable Deployment selector label (typically app.kubernetes.io/name) and never change it.
  • Document the Service / Deployment label contract: the Service’s selector must match a label the Deployment’s Pod template has.
  • Use set-based selectors (in, notin, !) when multiple values or exclusions are needed.
  • Treat selectors as part of the manifest’s contract: changes to selectors require coordinated updates to Pods and other controllers.
  • Monitor empty Endpoints (kube_endpoint_address_ available == 0) as an early signal of selector mismatches.