Labels are short key/value pairs that identify; selectors query them, in equality form (tier=api, tier!=db) or set form (tier in (api,web), !canary). Annotations hold what is never queried: change causes, build data, tool settings:
kubectl get pods -l 'app=booknest,tier in (api,web)' -L tier --no-headers
kubectl get pods -l 'app=booknest,tier notin (api,web)' -o name
kubectl annotate deployment api example.com/owner=storefront-team >/dev/null
kubectl get deployment api -o jsonpath='{.metadata.annotations}' \
| jq -r 'del(.["kubectl.kubernetes.io/last-applied-configuration"]) | to_entries[]
| "\(.key)=\(.value)"'Output
api-6cbf65d77f-7ndq4 1/1 Running 0 10m api api-6cbf65d77f-f7c6h 1/1 Running 0 10m api web-ccd64df5f-78cx9 1/1 Running 0 10m web web-ccd64df5f-gwkxn 1/1 Running 0 10m web pod/postgres-0 deployment.kubernetes.io/revision=2 example.com/owner=storefront-team kubernetes.io/change-cause=BookNest API 1.4, settings from api-config
The recommended shared labels (app.kubernetes.io/name, instance, version, component, part-of, managed-by) are applied for you by Helm 29,435 charts (Packaging BookNest with Helm). A Deployment's spec.selector is immutable, so choose its labels once, and a Service selector with a typo is no error, just a Service without endpoints.