Labels and selectors
The stickers that connect everything.
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.
apiVersion: v1kind: Podmetadata:name: hellolabels:app: helloenv: prodspec:containers:- name: webimage: nginx:1.27ports:- containerPort: 80
Save that as pod.yaml and create it. The apply command reads the file and tells Kubernetes to make it real.
kubectl apply -f pod.yaml
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:
kubectl label pod hello tier=frontend
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:
kubectl get pods --show-labels
NAME READY STATUS RESTARTS AGE LABELShello 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:
kubectl get pods -l app=hello
NAME READY STATUS RESTARTS AGEhello 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:
kubectl get pods -l 'env in (prod,staging)'
NAME READY STATUS RESTARTS AGEhello 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:
apiVersion: v1kind: Servicemetadata:name: hellospec:selector:app: helloports:- port: 80targetPort: 80
kubectl apply -f service.yaml
service/hello created
Now ask Kubernetes which Pods the Service actually wired itself up to:
kubectl get endpoints hello
NAME ENDPOINTS AGEhello 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:
apiVersion: v1kind: Servicemetadata:name: hellospec:selector:app: heloports:- port: 80targetPort: 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.
kubectl apply -f service.yaml
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:
kubectl get endpoints hello
NAME ENDPOINTS AGEhello <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.
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.
$ kubectl run hello --image=nginx:1.27 --restart=Never --labels=app=hello,env=prodpod/hello created$ kubectl get pods -l app=hello --show-labelsNAME READY STATUS RESTARTS AGE LABELShello 1/1 Running 0 5s app=hello,env=prod$ kubectl expose pod hello --port=80 --name=hello --selector=app=helloservice/hello exposed$ kubectl get endpoints helloNAME ENDPOINTS AGEhello 10.244.1.7:80 3s$ kubectl patch service hello -p '{"spec":{"selector":{"app":"helo"}}}'service/hello patched$ kubectl get endpoints helloNAME ENDPOINTS AGEhello <none> 12s$ kubectl delete service hello; kubectl delete pod helloservice "hello" deletedpod "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.