Labels & Selectors
Summary
- Labels are key/value metadata attached to objects; selectors are queries that match objects by their labels.
- Insight: Kubernetes wires components together by label matching, not by name or IP - Services, Deployments, and NetworkPolicies all find their targets through selectors.
- Key Concepts: equality-based selectors, set-based selectors (
in,notin,exists), recommended labels, and the difference between labels and annotations. - When to Use: every workload, for grouping, routing, rollouts, and policy.
- Limitations: a mismatched or overlapping selector silently breaks routing or makes controllers fight over Pods.
- Design a consistent label scheme early; retrofitting it across a cluster is painful.
Recipe
Label Pods, then select them from a Service and from kubectl.
kubectl label pod web-abc app=web tier=frontend
kubectl get pods -l app=web,tier=frontend
kubectl get pods -l 'tier in (frontend,backend)'- Attach descriptive labels to workloads.
- Use equality selectors (
app=web) for the common case. - Use set-based selectors for ranges of values.
Selectors combined with commas are ANDed together.
Working Example
A Service finds its Pods purely by label selector - this is the glue.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
tier: frontend
spec:
containers:
- name: web
image: ghcr.io/acme/web:1.4.2
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 8080The Service has no idea which Pods exist or what their IPs are. It selects app: web, and the endpoints controller populates its backends with every matching Pod.
Verify the wiring by looking at the generated endpoints.
kubectl get endpointslices -l kubernetes.io/service-name=web
kubectl describe svc webIf that list is empty, your Service selector does not match any Pod labels, and traffic goes nowhere. This one mismatch is the most common "my Service returns nothing" bug.
Deep Dive
Labels vs annotations
Both are key/value maps in metadata, but they serve different purposes.
Labels are for identifying and selecting objects and are indexed for queries. Annotations hold non-identifying data (build info, checksums, tool config) and are never used by selectors.
Use a label when you will select on it; use an annotation for everything else.
Equality-based selectors
The simplest selectors match exact values.
kubectl get pods -l app=web
kubectl get pods -l app=web,tier=frontend # AND
kubectl get pods -l app!=web # not equalService spec.selector and older controllers use only equality-based matchLabels.
Set-based selectors
More expressive selectors match against sets of values.
kubectl get pods -l 'tier in (frontend,backend)'
kubectl get pods -l 'app,!deprecated' # has app, lacks deprecatedIn manifests, Deployments express these with matchExpressions.
selector:
matchExpressions:
- key: tier
operator: In
values: ["frontend", "backend"]An immutable selector caveat
A Deployment's spec.selector is immutable after creation, and it must match the Pod template labels. Choose a small, stable selector like app: web and add other labels (version, tier) on top.
Overly specific selectors that include a version label cause trouble at upgrade time because the selector can no longer match new Pods.
Recommended labels
Kubernetes defines a set of common labels under the app.kubernetes.io/ prefix for consistency across tools.
metadata:
labels:
app.kubernetes.io/name: web
app.kubernetes.io/instance: web-prod
app.kubernetes.io/version: "1.4.2"
app.kubernetes.io/component: frontend
app.kubernetes.io/part-of: storefront
app.kubernetes.io/managed-by: helmAdopting these makes dashboards, Helm, and Kustomize interoperate and lets you slice the cluster by app, component, or release.
Gotchas
A Service selector that does not exactly match Pod labels yields empty endpoints and silent request failures. Check with kubectl get endpointslices.
Two controllers whose selectors overlap will both try to own the same Pods, causing scale-up and scale-down fights.
Changing Pod template labels can orphan running Pods from their controller, since the ReplicaSet no longer selects them.
Putting version or hash in a Deployment selector breaks rollouts because the immutable selector cannot match new Pods.
Using labels for high-cardinality data (like a per-request ID) bloats etcd indexes. Keep label values low-cardinality; use annotations for the rest.
Forgetting that comma-separated selectors are ANDed leads to "no results" surprises when you meant OR (use a set-based in instead).
Alternatives
Field selectors filter by a few built-in fields (status.phase, metadata.namespace) rather than labels, useful for queries labels cannot express.
Namespaces group objects administratively for RBAC and quota, complementing labels which group them functionally.
Annotations carry metadata you never select on, so reach for them instead of labels for build info and tooling hints.
NetworkPolicy podSelector / namespaceSelector reuse the same label machinery to scope traffic rules, so a good label scheme pays off in security too.
FAQs
What is the difference between a label and an annotation? Labels identify and are selectable; annotations hold non-identifying metadata and cannot be selected on.
Why does my Service return nothing?
Its selector probably does not match any Pod labels. Inspect kubectl get endpointslices - an empty set confirms the mismatch.
Are comma-separated selectors AND or OR?
AND. For OR-style matching on one key, use a set-based selector like key in (a,b).
Can I change a Deployment's selector later?
No, spec.selector is immutable. Keep it minimal and stable, and add extra labels on the Pod template.
Should I put the version in the selector? No. Version belongs in a label for filtering, not in the selector, or rollouts will break.
How do I avoid controllers fighting over Pods? Give each workload a unique, non-overlapping selector so only one controller matches a given Pod.
Related
- kubectl Essentials
- API Objects & Reconciliation
- Kubernetes Basics
- Declarative vs Imperative
- Kubernetes Fundamentals Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).