Why this matters
What you need to know already: objects and manifests (15.1, 15.3), pods (15.14), ReplicaSets and Deployments (15.16), DaemonSets (15.22), shell quoting (6.6), DNS names (8.16).
A Deployment has three pods. How does it know which three, out of hundreds in the cluster? Not by name, not by a list - by labels. One wrong letter in a label and a pod silently stops belonging to its Deployment, or a test pod gets swallowed by the wrong one. This lesson is the wiring of the whole cluster.
Labels are the wiring
A label is a key/value pair in an object's metadata.labels, like app: web. A selector is a query over labels: "every pod with app=web". Kubernetes has no other way to say "these pods belong to that thing": a ReplicaSet finds its pods by label selector, a DaemonSet too, and so do the objects of the next chapters.
Later (Ch 16-17): Services (a stable address in front of pods), NetworkPolicies and PodDisruptionBudgets all find their pods with the same selectors.
Get a label wrong and things silently stop being connected.
metadata:
labels:
app.kubernetes.io/name: checkout
app.kubernetes.io/instance: checkout-prod
app.kubernetes.io/version: "2.4.1"
app.kubernetes.io/component: api
app.kubernetes.io/part-of: shop
app.kubernetes.io/managed-by: kubectl
tier: backend
env: prod
The app.kubernetes.io/* keys are the recommended labels - a naming convention most tools and dashboards understand (managed-by names the tool that deployed it). Keys can have a DNS-prefix (app.kubernetes.io/) and a name up to 63 characters; values up to 63 characters of [A-Za-z0-9._-], starting and ending alphanumeric (so no spaces, no slashes, and "2.4.1" quoted in YAML so it stays a string).
Selecting
-l (--selector) on get, delete, logs and most other commands filters by label (k is the kubectl alias from 15.3):
k get pods -l app=web # equality
k get pods -l app=web,tier=frontend # AND
k get pods -l 'env!=prod' # not equal (or key missing)
k get pods -l 'tier in (frontend,backend)' # set-based
k get pods -l 'env notin (prod)'
k get pods -l tier # key exists
k get pods -l '!tier' # key does not exist
Quote anything with spaces, parentheses or ! - the shell would eat them.
# shop = the labelled namespace of the next mission
k get pods -n shop -l 'tier in (frontend,backend)' -L tier,env
NAME READY STATUS RESTARTS AGE TIER ENV
api-5d9c7b8f6d-2kx8p 1/1 Running 0 2m backend prod
web-6b8d9c7f5d-8xk2p 1/1 Running 0 2m frontend prod
web-6b8d9c7f5d-n4v7q 1/1 Running 0 2m frontend staging
-L key adds a column per label (here TIER and ENV); --show-labels adds one LABELS column with all of them. The other columns are the usual pod columns (15.14).
In manifests the same two flavours appear as matchLabels and matchExpressions:
selector:
matchLabels:
app: web
matchExpressions:
- key: tier
operator: In # In, NotIn, Exists, DoesNotExist
values: [frontend, edge]
matchLabels is the equality form (every listed label must match); matchExpressions is the set-based form. When both are given, both must match. Some older objects only support the simple map form (selector: {app: web}).
Changing labels on the fly
k run NAME --image=IMG -l k=v,... starts a single pod with labels; k label OBJECT key=value sets a label on an existing object:
$ k run demo --image=nginx:1.27 -l app=demo,env=prod
pod/demo created
$ k label pod demo env=staging
error: 'env' already has a value (prod), and --overwrite is false
$ k label pod demo env=staging --overwrite
pod/demo labeled
$ k label pod demo env-
pod/demo unlabeled
KEY- removes. --overwrite is required to change an existing value - a guard against typos in automation.
The quarantine trick
Because a ReplicaSet owns pods that match its selector, changing a pod's label takes it out of the ReplicaSet:
$ k create deployment web --image=nginx:1.27 --replicas=3 # AlreadyExists is fine
deployment.apps/web created
$ k label $(k get pods -l app=web -o name | head -1) app=web-debug --overwrite
pod/web-6b8d9c7f5d-8xk2p labeled
$ k get pods -L app
NAME READY STATUS RESTARTS AGE APP
web-6b8d9c7f5d-8xk2p 1/1 Running 0 5m web-debug
web-6b8d9c7f5d-n4v7q 1/1 Running 0 5m web
web-6b8d9c7f5d-zq9hm 1/1 Running 0 5m web
web-6b8d9c7f5d-q2l8c 0/1 ContainerCreating 0 1s web
($(k get pods -l app=web -o name | head -1) picks the first pod's name, pod/web-..., with command substitution from Ch 6.)
The ReplicaSet released it (removed its ownerReference, the field in metadata that says which controller owns an object) and created a replacement to get back to 3. The misbehaving pod keeps running, no longer counted and no longer sent traffic, for you to exec into and debug at leisure. Delete it when you are done - nothing else will.
Adoption, and why a Deployment's selector has a hash in it
The reverse also happens. A controller adopts any pod matching its selector that has no other controller. A Deployment protects itself: the ReplicaSets it creates select on app=web,pod-template-hash=6b8d9c7f5d, so a stray pod with just app=web is not adopted by them. A DaemonSet or a bare ReplicaSet has no such hash in its selector - run a test pod with the same labels as a DaemonSet's pods and the DaemonSet adopts it, sees two pods on that node, and deletes one. Usually yours.
Annotations
Also key/value metadata, but not selectable and much larger (up to 256KB total). They are for tools and humans, not for wiring:
kubernetes.io/change-cause rollout history
kubectl.kubernetes.io/last-applied-configuration kubectl apply's memory
kubectl.kubernetes.io/restartedAt kubectl rollout restart
deployment.kubernetes.io/revision the deployment controller
checksum/config: 3f7a... a hash of the config, to force rollouts (15.29)
k annotate deploy web owner="[email protected]" contact=@payments-oncall
k annotate deploy web owner- # remove
Rule of thumb: if something needs to select by it, label; otherwise annotate.
Namespaces
A namespace is a folder for objects: a scope for names, and later for permissions and limits (who may do what, how much CPU a team gets - Ch 17). Four exist on every cluster built with kubeadm (the standard installer, Ch 18):
$ k get ns
NAME STATUS AGE
default Active 12d
kube-node-lease Active 12d node heartbeats (Lease objects)
kube-public Active 12d readable by everyone, e.g. cluster-info
kube-system Active 12d the cluster's own components
Columns: NAME, STATUS (Active, or Terminating while being deleted), AGE.
Not everything is namespaced - nodes and namespaces themselves are cluster-scoped (one per cluster, no namespace), as are a few kinds you meet later. k api-resources --namespaced=false lists them.
Namespaces are not a network boundary: by default any pod can reach any pod in any namespace (Ch 16 adds the rules that change that). And names only need to be unique within one: shop/web and payments/web are different objects, and DNS tells them apart as web.shop.svc.cluster.local and web.payments.svc.cluster.local.
Stop typing -n all the time. k config set-context --current --namespace=shop changes the default namespace of your current kubeconfig context (15.1); k config get-contexts shows it in the NAMESPACE column, with * marking the current context:
$ k config set-context --current --namespace=shop
Context "kubernetes-admin@kubernetes" modified.
$ k config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kubernetes-admin@kubernetes kubernetes kubernetes-admin shop
Now k get pods means shop. The trap is forgetting you did it - k get pods returning "No resources found in shop namespace." is the reminder. Switch back before you go on:
$ k config set-context --current --namespace=default
Context "kubernetes-admin@kubernetes" modified.
Deleting a namespace deletes everything in it and waits for it to be empty (the namespace shows Terminating meanwhile). There is no confirmation prompt.
What you can now do:
- Select pods with equality and set-based selectors, and show labels as columns.
- Relabel a pod to take it out of its ReplicaSet for debugging.
- Choose label vs annotation, and set your default namespace.