CoursesKubernetes fundamentalsLabels and selectors

Labels and selectors

The stickers that connect everything.

Beginner8 min · lesson 12 of 24
In plain terms
Labels are sticky notes you put on things; a selector is “grab everything with a blue note.” It’s how the pieces find each other without memorizing names.

Think about a big conference hall. Everyone wears a name badge, and stuck onto each badge are little tags: "Speaker", "Team Blue", "Room 3". When an organizer needs the speakers on stage, they don't read out a list of names. They just say, "anyone with a Speaker tag, come up." Kubernetes wires your app together the exact same way. Once that clicks, a lot of the mystery falls away.

Kubernetes runs your app inside Pods. A container is one packaged, ready-to-run copy of your app. A Pod is the smallest thing Kubernetes runs: one or more of those containers bundled together on a single machine. Kubernetes calls those machines nodes. A label is a tiny tag you stick on a Pod. It's just a key and a value, like app=hello or env=prod. You can put as many labels on a Pod as you like, and you can add or change them later.

You get to pick the keys and values yourself. Most teams settle on a few they reuse everywhere: app for the application's name, env for the environment like prod or staging, tier for the layer such as frontend or backend, and version for the release. There's no fixed list you have to follow. A label is just text you agree on with your team, so anyone can find things later by the same tags.

A selector is the other half of the pair. It's a search: "give me everything wearing app=hello." That's it. Nothing memorizes names or network addresses. The pieces find each other by matching tags.

Start with a Pod that already wears some tags

Here's a complete Pod you can save and run. Look at the labels block under metadata. Those are the stickers. Everything below in spec just describes the container that runs.

pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hello
labels:
app: hello
env: prod
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80

Save that as pod.yaml and create it. The apply command reads the file and tells Kubernetes to make it real.

Terminal
kubectl apply -f pod.yaml
Output
pod/hello created

Add a sticker to something that's already running

You don't have to bake every label into the file up front. You can slap one on a live Pod with kubectl label:

Terminal
kubectl label pod hello tier=frontend
Output
pod/hello labeled

The Pod now wears three tags: app=hello, env=prod, and tier=frontend. None of that changed the running container. You only changed the stickers on it.

Want to check what's actually stuck on your Pods? Add --show-labels and Kubernetes prints every tag in a column:

Terminal
kubectl get pods --show-labels
Output
NAME READY STATUS RESTARTS AGE LABELS
hello 1/1 Running 0 3m app=hello,env=prod,tier=frontend

There they are, all three, exactly as you set them.

Search by sticker

The -l flag is how you search by label. Ask for every Pod wearing app=hello:

Terminal
kubectl get pods -l app=hello
Output
NAME READY STATUS RESTARTS AGE
hello 1/1 Running 0 2m

Need Pods that match two tags at once? Chain them with a comma: -l app=hello,env=prod returns only the Pods wearing both.

You can also match a set of values, not just one. This next search grabs any Pod whose env tag is either prod or staging. Our hello Pod is prod, so it shows up:

Terminal
kubectl get pods -l 'env in (prod,staging)'
Output
NAME READY STATUS RESTARTS AGE
hello 1/1 Running 0 2m

How the pieces actually connect

This is where labels earn their keep. A Service is a stable front door for your app. Traffic arrives at the Service, and the Service forwards it on to your Pods. But it never points at a Pod by name or IP (Internet Protocol) address. It carries a selector. Here's a full Service that routes to anything wearing app=hello:

service.yaml
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 80
Terminal
kubectl apply -f service.yaml
Output
service/hello created

Now ask Kubernetes which Pods the Service actually wired itself up to:

Terminal
kubectl get endpoints hello
Output
NAME ENDPOINTS AGE
hello 10.244.1.7:80 12s

That IP address is your Pod. You never typed it anywhere. The Service found the Pod purely by the label match. Endpoints is just Kubernetes' word for the live list of Pods a Service is currently sending traffic to. A Deployment (the part of Kubernetes that keeps a set of Pods running for you) works the same way: its selector.matchLabels field says "these are the Pods I'm responsible for."

A selector isn't limited to one Pod. Run ten copies of your app, give them all app=hello, and the Service spreads traffic across every one of them. Add more copies later and they join the moment they come up wearing the right tag. Remove some and the rest carry on. The Service never needed editing, because it was never told about any single Pod in the first place. This is the whole trick behind scaling an app up and down while it keeps serving traffic.

Working through labels instead of names is what lets Kubernetes heal itself. Pods come and go constantly. One dies, and a fresh one takes its place with a new name and a new IP address. As long as the replacement wears the same labels, every Service and Deployment keeps working and you touch nothing. Labels are also how you keep a busy cluster tidy: tag things by team, env, or version, then pick out and act on exactly the group you mean.

When the selector matches nothing

Here's the flip side of all that convenience, and it catches nearly everyone at least once. Because the Service finds Pods by a plain text match, a single wrong character means it finds none. Go back and change one letter in that Service, so it searches for app: helo instead of app: hello, then re-apply it:

service.yaml
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: helo
ports:
- port: 80
targetPort: 80

Kubernetes takes it happily. There's no such thing as a misspelled label to Kubernetes: helo is a perfectly valid value that just happens to be stuck on no Pod at all.

Terminal
kubectl apply -f service.yaml
Output
service/hello configured

Now everything looks healthy. The Service is there, the Pod is still Running, nothing prints an error. But traffic sent to the Service just hangs or comes back refused. The one command that shows you why is the same endpoints check from a moment ago:

Terminal
kubectl get endpoints hello
Output
NAME ENDPOINTS AGE
hello <none> 20s

That <none> in the ENDPOINTS column is the whole story: the Service matched zero Pods and is forwarding traffic to nobody. So whenever a Service seems dead while the Pods behind it are perfectly fine, check its endpoints first. An empty list nearly always means the selector and the Pod labels disagree. Put the two side by side, kubectl get pods --show-labels for the tags the Pods really wear and kubectl describe service hello for the selector it's searching on, and the mismatched character usually jumps straight out.

A connection is a live label search
1Stick a label on aPodapp=hello goes on the Pod2A Service declaresa selectorselector: app=hello3Kubernetes matchesthemwhich Pods wear app=hello…4Traffic reachesevery matchno names or IPs are hard-coded5A Pod is replacedthe new one wears app=hello,…

Labels are the stickers that connect Services, Deployments, NetworkPolicies, and your own kubectl filters. Selectors are searches over those stickers. Prefer a small, documented vocabulary (app, tier, env, version) rather than inventing a new key per PR.

A typo in a Service selector silently yields empty endpoints. That is the classic "pods are Running but nothing answers" incident. Always diff selector versus pod labels with get endpoints and --show-labels.

In production, changing labels on live pods can orphan them from a Deployment or Service. Prefer changing the pod template and rolling, instead of kubectl label on a single survivor.

Try this

Label a pod, select it with -l, attach a Service selector, then break the selector on purpose and watch endpoints go empty.

terminal
$ kubectl run hello --image=nginx:1.27 --restart=Never --labels=app=hello,env=prod
pod/hello created
$ kubectl get pods -l app=hello --show-labels
NAME READY STATUS RESTARTS AGE LABELS
hello 1/1 Running 0 5s app=hello,env=prod
$ kubectl expose pod hello --port=80 --name=hello --selector=app=hello
service/hello exposed
$ kubectl get endpoints hello
NAME ENDPOINTS AGE
hello 10.244.1.7:80 3s
$ kubectl patch service hello -p '{"spec":{"selector":{"app":"helo"}}}'
service/hello patched
$ kubectl get endpoints hello
NAME ENDPOINTS AGE
hello <none> 12s
$ kubectl delete service hello; kubectl delete pod hello
service "hello" deleted
pod "hello" deleted

Takeaway

Labels glue the cluster together; selectors are live searches. Empty endpoints almost always mean a label mismatch — fix the selector, and avoid hand-editing labels off of managed pods.

Changing a live Pod's labels can quietly cut it off
Services and Deployments find Pods by label, not by name. So if you edit the labels on a running Pod, you can accidentally drop it out of a Service's traffic, or orphan it from its Deployment. When a Deployment loses sight of a Pod, it assumes one went missing and starts a replacement to hit its target count. Nothing errors out and no warning pops up. The Pod just quietly stops matching. Change labels on purpose, not by reflex.
Quick check
01Every Pod shows Running and nothing logs an error, but requests to your Service just hang. You run kubectl get endpoints hello and the ENDPOINTS column prints <none>. What's the most likely cause?
Incorrect — The Pods are Running and healthy. get endpoints reports which Pods the Service matched, not whether they're alive, so <none> is about the match, not Pod health.
Correct — An empty endpoints list means the label search found nothing. Line the selector up against the Pod labels, kubectl describe service against kubectl get pods --show-labels, and a single typo is usually the culprit.
Incorrect — Endpoints populate in a second or two when labels match. A persistent <none> means the match is broken, not slow, so waiting won't help.
Incorrect — A missing targetPort defaults to the port value and would still list matching endpoints. <none> points specifically at the selector finding no Pods.
02What does the selector -l app=hello,env=prod match?
Incorrect — The comma is AND, not OR; for either-value matching you'd use a set search like 'env in (prod,staging)'.
Correct — chaining labels with a comma returns only Pods that carry every one of them.
Incorrect — A selector matches whole label keys and values, not a substring of raw text.
Incorrect — Two labels combine fine in a single search, joined by the comma as AND.
03A Pod is managed by a Deployment (replicas: 3, selector app=hello) and also served by a Service selecting app=hello. You use kubectl label to overwrite that Pod's label to app=hello-old. What happens?
Incorrect — Labels aren't just notes here — Services and Deployments find Pods by them, so a change has real effects.
Incorrect — Kubernetes doesn't block a label change just because a Service uses it; it applies the change silently.
Correct — the relabeled Pod stops matching, so it leaves the Service and the Deployment quietly spins up a replacement.
Incorrect — Selectors don't chase a Pod's new label; the Pod simply falls out of the match.

Related