Back to Blog

Labels, Selectors & Annotations for the CKAD Exam: matchLabels, Set-Based Selectors, Field Selectors & kubectl -l

A practitioner's guide to Kubernetes labels, selectors, and annotations for the CKAD exam — equality vs set-based selectors, how Services and Deployments find Pods with matchLabels and matchExpressions, the difference between labels and annotations, field selectors, the recommended label set, and the kubectl -l speed techniques that save you real minutes on exam day.

By Sailor Team , August 4, 2026

Labels look like the most trivial thing in Kubernetes — a few key/value pairs stapled to an object. But they are the connective tissue of the entire system. A Service finds its Pods through a label selector. A Deployment owns its Pods through a label selector. Jobs, ReplicaSets, NetworkPolicies, Pod affinity, kubectl filtering — all of it runs on labels. On the CKAD exam, a single wrong or missing label is one of the most common reasons a task silently fails: your Deployment rolls out, your Service exists, and yet kubectl get endpoints comes back empty because the selector doesn’t match the Pod template.

This guide covers labels, selectors, and annotations the way the CKAD actually tests them — not as trivia, but as the mechanism you use to wire objects together and to filter them quickly under time pressure. If you want the full exam picture first, start with the CKAD exam guide for 2026 and sequence your prep with the CKAD study plan. This piece assumes you can already create Pods and Deployments and focuses on the label mechanics that hold them together.

What Labels Actually Are

A label is a key/value pair attached to an object’s metadata that is intended to be identifying and queryable. “Identifying” is the important word: labels are meant to describe attributes you will later select on — the app name, the tier, the environment, the release. Kubernetes indexes labels so that selecting objects by them is fast, even in large clusters.

apiVersion: v1
kind: Pod
metadata:
  name: web-frontend
  labels:
    app: store
    tier: frontend
    environment: prod
    release: canary
spec:
  containers:
    - name: nginx
      image: nginx:1.27

Label keys can have an optional prefix (a DNS subdomain like app.kubernetes.io/) followed by a name, and values must be 63 characters or fewer, alphanumeric with -, _, and . allowed. Both keys and values are constrained — a value cannot contain spaces or arbitrary text. That constraint is deliberate: labels are for selection, not for storing free-form data. When you need free-form data, you use annotations (covered below).

You can add and change labels imperatively, which is by far the fastest path on the exam:

# Add or overwrite a label
kubectl label pod web-frontend team=payments

# Overwrite an existing label (must pass --overwrite)
kubectl label pod web-frontend environment=staging --overwrite

# Remove a label (trailing minus)
kubectl label pod web-frontend release-

# Label every Pod at once
kubectl label pods --all tier=frontend

Selectors: How Objects Find Each Other

A selector is a query over labels. Kubernetes supports two selector syntaxes, and knowing which controllers accept which is directly examable.

Equality-Based Selectors

Equality-based selectors match on exact key/value equality (or inequality). They are written as a comma-separated list, where every clause is ANDed together:

# All Pods where app=store AND tier=frontend
kubectl get pods -l 'app=store,tier=frontend'

# Inequality
kubectl get pods -l 'environment!=prod'

Older objects — a Service, a ReplicationController — only understand equality-based selectors, expressed as a simple map:

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

That selector map means “send traffic to every Pod carrying both app=store and tier=frontend.” If the Pods behind it are missing either label, the Service has no endpoints and returns connection failures. This is the single most common wiring bug on the exam.

Set-Based Selectors

Set-based selectors are richer: they support in, notin, and existence checks. Newer controllers — Deployment, ReplicaSet, Job, DaemonSet, StatefulSet — use them through the matchLabels / matchExpressions structure.

# Set-based on the command line
kubectl get pods -l 'environment in (prod,staging)'
kubectl get pods -l 'tier notin (cache)'
kubectl get pods -l 'release'          # key exists, any value
kubectl get pods -l '!release'         # key does NOT exist

In YAML, a Deployment’s selector looks like this:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: store-frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: store
    matchExpressions:
      - key: tier
        operator: In
        values: [frontend]
      - key: environment
        operator: NotIn
        values: [dev]
  template:
    metadata:
      labels:
        app: store
        tier: frontend
        environment: prod
    spec:
      containers:
        - name: nginx
          image: nginx:1.27

matchLabels and matchExpressions are ANDed together. The available operators are In, NotIn, Exists, and DoesNotExist (the last two take no values).

The Rule That Trips Everyone Up

For a Deployment (and ReplicaSet), the Pod template’s labels must satisfy the selector, and the selector is immutable after creation. If the template labels don’t match the selector, the API server rejects the Deployment outright:

selector does not match template labels

Because the selector can’t be changed later, if you get it wrong you must delete and recreate the object. On the exam, the safe habit is to let kubectl create deployment generate a consistent app=<name> label on both the selector and the template, then only add extra labels to the template:

kubectl create deployment store-frontend --image=nginx:1.27 --replicas=3

This produces a Deployment whose selector is app=store-frontend and whose template carries the same label — matched by construction. You avoid the mismatch error entirely.

Equality vs Set-Based: Quick Reference

AspectEquality-basedSet-based
Operators=, ==, !=In, NotIn, Exists, DoesNotExist
Used byService, ReplicationControllerDeployment, ReplicaSet, Job, DaemonSet, StatefulSet
YAML fieldplain selector mapselector.matchLabels + selector.matchExpressions
CLI example-l app=store-l 'app in (store,cart)'
Combiningcomma = ANDmultiple expressions = AND

The exam-relevant takeaway: a Service uses a plain map; a Deployment uses matchLabels/matchExpressions. Mixing them up produces schema errors.

Verifying the Wiring

The fastest way to confirm a selector actually matches Pods is to check endpoints, not to eyeball YAML:

# Does the Service have any backends?
kubectl get endpoints store-frontend

# Which Pods match this exact selector?
kubectl get pods -l app=store,tier=frontend

# See labels on everything, as columns
kubectl get pods --show-labels

# Pivot a label into its own column
kubectl get pods -L tier,environment

If kubectl get endpoints shows <none>, the Service selector and the Pod labels disagree. Compare the two with kubectl get svc store-frontend -o yaml and kubectl get pods --show-labels, fix the labels with kubectl label ... --overwrite, and re-check. This loop — check endpoints, fix labels, re-check — is one you should be able to run in under a minute.

Annotations: The Other Metadata

Labels and annotations look identical in YAML — both are key/value maps under metadata — but they exist for opposite reasons.

  • Labels are for selecting and grouping. They are constrained, indexed, and queried by selectors.
  • Annotations are for attaching arbitrary non-identifying data. They are not indexed, cannot be selected on, and can hold large, structured, or free-form values.
apiVersion: v1
kind: Pod
metadata:
  name: web-frontend
  labels:
    app: store          # you select on this
  annotations:
    kubernetes.io/change-cause: "Roll out nginx 1.27"
    contact: "[email protected]"
    config-hash: "9f2b1c8e-build-4471"
    description: "Front door for the storefront; owns TLS termination."

Use annotations for things like build metadata, contact info, tooling hints, checksums that trigger rollouts when they change, and human-readable descriptions. Ingress controllers, cert managers, and other tools read their configuration from annotations. You cannot do kubectl get pods -l contact=... — annotations are invisible to selectors by design.

LabelsAnnotations
PurposeIdentify & selectAttach arbitrary metadata
Selectable?YesNo
Value limits63 chars, restricted charsetLarge, free-form
Typical useapp, tier, env, releasebuild info, contacts, tool config, change-cause
Set imperativelykubectl labelkubectl annotate
kubectl annotate pod web-frontend description="Storefront front door"
kubectl annotate pod web-frontend description- # remove

One annotation worth memorising is kubernetes.io/change-cause, which kubectl rollout history displays. Setting it (via kubectl ... --record in older versions, or by annotating) gives your rollouts a readable audit trail.

Field Selectors: Selecting on Non-Label Fields

Label selectors query the labels map. Field selectors query built-in object fields instead — things that aren’t labels at all, like a Pod’s phase or the node it landed on. They are a separate mechanism and a favourite for filtering quickly:

# Only Pods that are actually running
kubectl get pods --field-selector status.phase=Running

# Everything except succeeded/failed pods
kubectl get pods --field-selector status.phase!=Succeeded

# Pods on a specific node
kubectl get pods --field-selector spec.nodeName=worker-2 -A

# Combine with a label selector
kubectl get pods -l app=store --field-selector status.phase=Running

The supported fields vary by resource — metadata.name, metadata.namespace, and status.phase are widely available; spec.nodeName works for Pods. Field selectors and label selectors combine freely, which is a genuinely useful exam trick when you need “the running Pods of app X.”

Kubernetes defines a set of recommended common labels under the app.kubernetes.io/ prefix. You don’t have to use them, but the exam may reference them and real tooling (Helm, dashboards) understands them:

metadata:
  labels:
    app.kubernetes.io/name: store
    app.kubernetes.io/instance: store-prod
    app.kubernetes.io/version: "1.27"
    app.kubernetes.io/component: frontend
    app.kubernetes.io/part-of: storefront
    app.kubernetes.io/managed-by: helm

The idea is a shared vocabulary so that unrelated tools can reason about your objects. For exam speed you’ll usually use short labels (app, tier), but knowing the recommended set exists — and that it’s a convention, not enforced — is enough.

Exam-Day Speed Techniques

Labels are where you win or lose time on the CKAD. A few reflexes:

# Generate YAML with the right labels already wired, then edit
kubectl create deployment api --image=nginx --dry-run=client -o yaml > api.yaml

# Expose a Deployment — kubectl copies the label selector for you
kubectl expose deployment api --port=80 --target-port=8080
# The Service selector is auto-set to match the Deployment's pod labels

# Bulk-select for deletion or inspection
kubectl delete pods -l 'environment=dev'
kubectl get all -l app=store

# Scale/patch everything matching a label
kubectl get deploy -l tier=frontend

kubectl expose is the highest-leverage of these: it reads the target’s Pod template labels and builds a matching Service selector automatically, so you never hand-write a mismatched Service. When a task says “create a Service for this Deployment,” reach for kubectl expose before you reach for YAML.

Two more habits that prevent silent failures:

  1. Always verify with kubectl get endpoints after wiring a Service. Empty endpoints = label mismatch, every time.
  2. Never try to edit a Deployment’s selector — it’s immutable. If it’s wrong, kubectl delete and recreate. Trying to kubectl edit the selector wastes time on an error you can’t resolve in place.

Common Mistakes

MistakeSymptomFix
Service selector doesn’t match Pod labelsendpoints empty; connection refusedAlign labels with kubectl label --overwrite
Deployment template labels don’t satisfy selectorAPI rejects: “selector does not match template labels”Make template labels a superset of the selector
Trying to change a Deployment selectorImmutable field errorDelete and recreate the Deployment
Using set-based syntax in a Service selectorSchema/validation errorServices take a plain equality map only
Storing free-form data in a label63-char / charset validation errorUse an annotation instead
Forgetting --overwrite on kubectl label”already has a value” errorAdd --overwrite

Practising Until It’s Automatic

Labels and selectors are muscle memory. The way to internalise them is to build the wiring, break it on purpose, and fix it under a clock: create a Deployment, expose it, confirm endpoints, then deliberately mislabel a Pod and watch the Service drop it. Doing that a dozen times makes the “empty endpoints → check labels” reflex instant — which is exactly what the CKAD rewards.

That timed, hands-on loop is what the CKAD Certification Mock Exam Bundle is built for. It runs on a real cluster with real kubectl in a browser-based terminal that mirrors the proctored PSI environment, so you practise the exact wiring-and-verifying loop above against graded scenarios instead of reading about it. Pair it with the CKAD Services & Networking guide to see selectors in their most common context, and the SecurityContext & ServiceAccounts guide for the identity side of Pod configuration.

Frequently Asked Questions

What is the difference between a label and an annotation in Kubernetes?

Labels are identifying key/value pairs meant to be selected and grouped on — they are indexed, size-constrained, and queried by selectors used by Services, Deployments, and more. Annotations attach arbitrary, non-identifying metadata (build info, contacts, tool config) that cannot be selected on and can hold large free-form values. Rule of thumb: if you’ll ever query by it, it’s a label; otherwise it’s an annotation.

What is the difference between matchLabels and matchExpressions?

Both live under a controller’s selector. matchLabels is a simple equality map (app: store). matchExpressions is a list of set-based rules using operators In, NotIn, Exists, and DoesNotExist. They are ANDed together, so an object must satisfy every clause of both to be selected.

Why does my Service have no endpoints?

Almost always a label mismatch: the Service’s selector doesn’t exactly match the labels on the target Pods. Run kubectl get pods --show-labels and compare against kubectl get svc <name> -o yaml. Fix the Pod labels with kubectl label --overwrite or correct the Service selector, then re-check kubectl get endpoints <name>.

Can I change a Deployment’s selector after creating it?

No. The selector field of a Deployment and ReplicaSet is immutable. If it’s wrong, you must delete the object and recreate it with the correct selector. This is why the safest approach is to let kubectl create deployment set matching labels on both the selector and the Pod template.

What are field selectors and how do they differ from label selectors?

Label selectors query the labels map. Field selectors query built-in object fields such as status.phase, metadata.name, metadata.namespace, and (for Pods) spec.nodeName. Use --field-selector on the command line, and combine it with -l to filter on both labels and fields at once, e.g. kubectl get pods -l app=store --field-selector status.phase=Running.

Are the app.kubernetes.io/ labels required?

No. The app.kubernetes.io/* set is a recommended convention so that different tools share a common vocabulary, but nothing enforces it. On the exam you’ll usually use short labels like app and tier for speed; just know the recommended set exists and what it’s for.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now