KubernetesV · Kubernetes Objects and MetadataKubernetes objects and metadata
Selectors — matching objects by labels
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
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-Authcovers identity and group concepts that map onto selector-based grouping. - The Observability course part
IX-Observability-Exporterscovers Prometheus label selectors — similar trade-offs. - The Linux course part
XXIX-Linux-Hardeningcovers classification schemes that map onto label-based enforcement. - The Docker course part
XXXVIII-Docker-Secretscovers secret handling; selectors are not for secrets.
Quiz
Knowledge check · 4 questions
Q1. Which selector matches Pods with label `app in (web, api)` but NOT with label `env=dev`?
Q2. A Deployment''s `spec.selector` can be changed after creation as long as the new selector still matches the Pod template''s labels.
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 ```
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.