Seeking Wisdom · AI/ML Engineering · Investing · Books · Projects · About ·

Kubernetes by Example · Part 3

Labels and Selectors

Nothing in Kubernetes finds anything by name. Once you know what it does instead, half the surprising behaviour stops being surprising.

assumes Parts 1–2~16 minutesone tuner becomes four

The symptom

You scale the tuner up to four, because four stations is more interesting than one. It works.

Then you make a second Deployment for a different kind of tuner, by copying the first manifest and changing the name. And now something is very wrong: pods are being deleted. Not by you. Your four tuners become three, then five, then two. Neither Deployment ever reaches the number it's asking for.

Nothing has errored. Both Deployments report themselves as perfectly healthy. The logs say nothing, because from each pod's point of view nothing unusual is happening — it's simply being killed, over and over, by something that won't explain itself.

What you've built is two owners for the same set of pods. To see why that's even possible, you need to know how anything in Kubernetes finds anything else.

Nothing here works by name

This is the part that feels wrong at first and then becomes the most useful thing you know.

A ReplicaSet does not hold a list of the pods it made. A Service does not hold a list of its backends. They hold a query, and they run it, constantly, and act on whatever it returns.

Which means the relationships you draw on a whiteboard — this Deployment owns those pods, that Service fronts these — don't exist as links anywhere. They're re-derived from scratch, several times a second, from labels you wrote by hand.

namespace: airwaves pods, each wearing whatever tags you gave it tuner-a1b2 app=tuner station=jazz tuner-c3d4 app=tuner station=jazz tuner-e5f6 app=tuner station=news tuner-g7h8 app=tuner station=news scribe-i9j0 app=scribe web-k1l2 app=web ReplicaSet · tuner selector: app=tuner “I own all four of those. Count = 4.” Service · jazz-feed selector: app=tuner, station=jazz “I send traffic to exactly two of them.” only station=jazz two owners, one pod — nobody wins selector: app=tuner — frozen annotations — the other metadata kubectl.kubernetes.io/last-applied-configuration: … arbitrary size, any content, and nothing can search by them

The two kinds of metadata

Labels are indexed and queryable. Short, restricted alphabet, 63 characters. Use them for anything that needs finding.

Annotations are not queryable at all. Any size, any content. Use them for things that are read after an object has been found — checksums, descriptions, config for other tools.

The commands

Selectors are the most reusable thing in kubectl. Learn them here and they work on every verb for the rest of the series.

Selecting things, every way there is

Click any piece of the command.

Changing labels on live objects

Click any piece of the command.

Working across namespaces

Click any piece of the command.

Build it

Four tuners, one per station, distinguished by labels rather than by name. This is the shape the rest of the series builds on: identical pods carrying different tags.

Apply, and show the station as a column

kubectl apply -f manifests/tuners.yaml
kubectl get pods -L station -l app=tuner

Ask it questions

# every tuner
kubectl get pods -l app=tuner

# only the jazz ones
kubectl get pods -l 'app=tuner,station=jazz'

# everything except the test station
kubectl get pods -l 'app=tuner,station notin (test)'

# anything that forgot to declare a station
kubectl get pods -l 'app=tuner,!station'

That last one is the habit worth forming. A selector for the absence of a label is how you find the thing someone forgot, and it costs nothing to run.

Break it

1 · Make a ReplicaSet adopt a stranger

Create a pod that has nothing to do with your Deployment, but happens to wear the right label.

kubectl run stray --image=busybox --restart=Never \
  --labels='app=tuner' -- sleep 3600

kubectl get pods -l app=tuner -w

Watch what happens. The ReplicaSet runs its query, finds one more pod than it asked for, and deletes one — possibly your stray, possibly one of its own. It has no way to tell the difference, because there is no difference. Ownership here is just a matching label.

2 · Try to change a selector

kubectl patch deployment tuner --type=merge \
  -p='{"spec":{"selector":{"matchLabels":{"app":"tuner","v":"2"}}}}'

Rejected, with field is immutable. Now you know why: changing the query would orphan every pod the Deployment currently owns, and Kubernetes has no mechanism to hand them over or clean them up. It refuses rather than leaving you with ghosts.

3 · Pull one pod out of service, live

# take it out of the Service's query without killing it
kubectl label pod  station=quarantine --overwrite

kubectl get pods -L station -l app=tuner

The pod keeps running. The ReplicaSet still owns it, because app=tuner is unchanged. But a Service selecting station=jazz drops it instantly, which means you can take a misbehaving pod out of rotation and then go poke at it at your leisure.

This is the single most useful operational trick in Kubernetes, and it falls straight out of “everything is a query”.

What follows from this

Because……this is what happens
Ownership is a queryA ReplicaSet adopts any unowned pod that matches. Hand-made test pods get eaten.
Selectors are immutablePick a minimal one — app=x — and put everything volatile in the template labels.
Overlaps aren't validatedTwo Deployments can fight over one pod forever, silently. Change the selector when you copy a manifest.
A typo matches nothingA wrong selector doesn't error, it returns zero. Empty results mean “check the query” before “check the pods”.
Labels move freelyRelabelling is instant rerouting. Canaries, quarantine and blue/green are all one kubectl label.
Namespaces scope names onlyDifferent namespaces can't collide on names and can't select each other — but they can still talk over the network.

Cheat sheet

Part 3 · labels, selectors, namespaces

• Labels are tags you invent. Kubernetes gives them no meaning; selectors do.

• Nothing finds anything by name. Owners and Services run queries, constantly, and act on the results.

• Equality (a=b, comma = AND) and set-based (in, notin, key, !key). No OR across keys, no wildcards.

• A ReplicaSet adopts any matching unowned pod — including ones you made by hand.

• A Deployment's selector is immutable. Keep it minimal; put the changing labels in the template.

• Overlapping selectors across two workloads cause silent, permanent pod churn.

• Labels are indexed and queryable; annotations are neither, but can be any size.

• -l '!key' finds everything missing a label — the cheapest audit there is.

• Relabel a pod to pull it out of a Service without killing it.

• Namespaces scope names. They are not a security or network boundary by themselves.