In Kubernetes, what is the difference between a label and an annotation, and how do you decide which one to use?
answer
- identify versus describe
- which one can a selector match
- 63-character value limit on one side
- 256 KiB total on the other
- kubectl apply stores its manifest there
basics
~20 sLabels are short identifying key/value pairs that selectors match, so Services, Deployments and kubectl -l find objects by them. Annotations carry non-identifying data of any shape, capped at 256 KiB in total, that tools read by key but nothing can select on.
solid answer
~50 sBoth are string maps in `metadata` and share the same key syntax: an optional DNS-subdomain prefix plus a name of up to 63 characters. **Labels identify**: values are capped at 63 characters from a restricted character set, and they are what selection works on — a Service's selector, a Deployment's `.spec.selector` and `kubectl get -l` all match labels. **Annotations describe**: a value can be any string, including multi-line JSON, limited only by 262144 bytes for all keys and values together, and no selector can query them. Tools and controllers read them by key — `kubectl apply` stores `kubectl.kubernetes.io/last-applied-configuration`, and the Deployment controller writes `deployment.kubernetes.io/revision`. The fast check: if anything will ever need to *find or group* objects by the value, it is a label; if it is information *about* the object, such as a build commit, an on-call contact or a tool setting, it is an annotation.
code
yaml · 27 linesapiVersion: apps/v1
kind: Deployment
metadata:
name: game-session
labels:
app.kubernetes.io/name: game-session
games.example.com/store-tier: flagship
annotations:
games.example.com/build-commit: "9f2c41e"
games.example.com/oncall: "store-edge-team"
games.example.com/build-log: "https://ci.example.com/builds/5827"
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: game-session
template:
metadata:
labels:
app.kubernetes.io/name: game-session
spec:
containers:
- name: server
image: registry.example.com/game-session:2.14.3
resources:
requests:
cpu: 350mgo deeper
Recall the split: labels identify and can be selected, annotations describe and cannot. Name one example of each, such as app.kubernetes.io/name and a build commit.
Explain the constraints behind the split: 63-character constrained label values versus unrestricted annotation values under a 262144-byte total, and name built-in annotations like last-applied-configuration and deployment.kubernetes.io/revision.
Show you have seen annotations fail in production: the size limit hit through client-side apply, and typo'd controller annotations silently ignored because nothing validates them.
Frame labels as a shared, governed vocabulary the whole platform selects on, and annotations as per-tool configuration channels that need naming prefixes and ownership conventions to stay manageable.
## Two maps in every object's metadata Every persisted Kubernetes object has a `metadata` block (the **ObjectMeta** type). Two of its fields are free-form string-to-string maps that you, rather than the API server, fill in: - `metadata.labels` — **identifying** attributes. - `metadata.annotations` — **non-identifying** attributes. They look almost identical in YAML, which is why interviewers ask how they differ. The difference is not the syntax; it is what the API server lets you do with them. ## Labels: small, identifying, selectable A **label** says what an object *is* in terms other objects can query. The API server supports **label selectors** on list and watch requests, so labels are the glue that lets objects find each other: - a Service picks its backend Pods by labels; - a Deployment's `.spec.selector` decides which Pods and ReplicaSets it owns; - `kubectl get pods -l app.kubernetes.io/name=game-session` filters by them. Because they are matched constantly, labels are deliberately **small and constrained**. A value is at most 63 characters, may be empty, and must start and end with an alphanumeric character with only `-`, `_` and `.` in between. Spaces, slashes and JSON are rejected. ## Annotations: arbitrary, non-identifying, never selected An **annotation** records information *about* an object that no selector will ever match on. The key follows the same rules as a label key, but the **value is unrestricted**: free text, a URL, a multi-line JSON document. The only size rule is a **total limit of 262144 bytes (256 KiB)** across all annotation keys and values on one object; a write over that is rejected with `metadata.annotations: Too long: must have at most 262144 bytes`. No selection is possible on annotations: there is no annotation-selector parameter on list requests, and no Service or workload can target Pods by one. Annotations are read **by key** — a component fetches the object and looks up the entry it cares about. ## Side by side | Aspect | Labels | Annotations | |---|---|---| | Purpose | Identify and group objects | Attach descriptive or tool data | | Value size | At most 63 characters | Any length, 262144-byte total per object | | Value characters | Alphanumerics plus `-`, `_`, `.` | Any string, including JSON | | Selectable | Yes — Services, workload selectors, `kubectl get -l` | No | | Typical readers | Controllers, the scheduler (via selectors), humans filtering | kubectl, controllers, CD tools, humans reading | | Key syntax | Optional DNS-subdomain prefix + `/` + name of at most 63 chars | Same as labels | Prefixes `kubernetes.io/` and `k8s.io/` are reserved for Kubernetes' own components; your keys should use a domain your team owns, such as `games.example.com/build-commit`. ## Annotations Kubernetes writes itself Seeing the built-in ones makes the purpose concrete: 1. `kubectl.kubernetes.io/last-applied-configuration` — client-side `kubectl apply` stores the full manifest you last applied here, as JSON. It is far too large and too unstructured to be a label. 2. `deployment.kubernetes.io/revision` — the Deployment controller numbers each rollout on the ReplicaSet and mirrors the current number onto the Deployment. 3. `kubectl.kubernetes.io/restartedAt` — `kubectl rollout restart` stamps a timestamp into the pod template. 4. `kubernetes.io/change-cause` — a human-written note that `kubectl rollout history` prints per revision. Ingress controllers, CD tools and operators use the same channel for their own settings. Because annotations are **not validated against any schema**, a mistyped key is simply ignored by whatever was meant to read it. ## Choosing in practice Take a **multiplayer game-session backend** running on a **5-node edge cluster in a retail store**, each pod requesting **0.35 core** (`cpu: 350m`). The team wants to record several facts on its Deployment: - the component name, `app.kubernetes.io/name: game-session` — a **label**, because the Service and the Deployment selector must find these pods; - the store tier, `games.example.com/store-tier: flagship` — a **label**, because dashboards and `kubectl get -l` will group by it; - the Git commit `9f2c41e` and a link to the build log — **annotations**, because nobody selects by a commit hash and a URL is not a legal label value; - the on-call contact and a 300-character JSON feature-flag summary — **annotations**, far past the 63-character label limit. A useful rule: labels answer *which ones?*, annotations answer *what about this one?*. Putting descriptive data into labels bloats every selector-indexed object and breaks on the character rules; putting identity into annotations means nothing can ever select on it.
- What happens when an object's annotations grow past the size limit, and where does that usually bite?The API server rejects the write with `metadata.annotations: Too long: must have at most 262144 bytes`. The common trigger is client-side `kubectl apply` on a large ConfigMap: apply copies the whole manifest into `kubectl.kubernetes.io/last-applied-configuration`, so a ConfigMap with a few hundred KiB of data fails even though the ConfigMap itself is within its own limit. Creating or replacing it instead, or switching to server-side apply, avoids storing that copy.
- Why does `kubectl annotate` refuse to change an annotation that already exists?`kubectl annotate` protects existing keys: setting a key that is already present fails unless you pass `--overwrite`, so a script cannot silently clobber a value another tool wrote. Appending a dash to the key, as in `games.example.com/build-commit-`, removes the annotation.
- Who may use the `kubernetes.io/` and `k8s.io/` key prefixes?Those prefixes are reserved for Kubernetes core components and documented well-known keys. Your own labels and annotations should carry a prefix from a DNS domain your team controls, such as `games.example.com/`, so they cannot collide with keys the project or other tools define.
Labels are the colour-coded tags on warehouse shelves that pickers scan to find items; annotations are the packing slip taped inside the box, read only by whoever opens that particular box.
saying these in an interview costs you the question
- A Service selector can match annotations just like labels.
- Labels are the right place for long descriptions or JSON blobs.
- Annotations are only comments that no Kubernetes component ever reads.
- Label values can be any length as long as the object fits in etcd.
- Every single annotation value has its own 63-character limit.