Skip to content
Documentation Background

Object Organization

Kubernetes provides four built-in tools for organising and targeting objects in a cluster. They work as a layered system from coarse boundaries down to fine-grained metadata:

Kubernetes Namespaces, Labels, Selectors & Annotations
ConceptRoleWhat it does
NamespacesThe BoundaryPartitions a cluster into virtual segments, scoping names, RBAC rules, and resource quotas
LabelsThe Organisational GlueKey-value pairs attached to objects to identify attributes like app type or release stage
SelectorsThe Targeting EngineFilter and query objects by label; used by Services, Deployments, and kubectl commands
AnnotationsThe Metadata LayerNon-identifying key-value store for tooling config, Git hashes, and human-readable context

A Kubernetes Namespace divides a single physical cluster into multiple virtual clusters, each with its own scope for object names, RBAC rules, resource quotas, and policies.

Linux vs Kubernetes namespaces Kubernetes Namespaces isolation

Namespace isolation is soft, not hard:

Isolation typeMechanismLimitation
Namespace (soft)Scopes names, RBAC, and quotasPods share the same nodes and kernel. A compromised workload can still affect others. No network isolation by default - requires a NetworkPolicy
Separate cluster (hard)Separate control plane, separate nodesStrong isolation but higher cost and management overhead

Because of soft isolation, namespaces are suitable for internal teams or projects - not for separating external customers. For production-grade multi-tenancy between untrusted parties, use separate clusters.

Namespaced vs Cluster-Scoped resources

Not all Kubernetes objects belong to a namespace:

ScopeExamples
NamespacedPods, Deployments, Services, ConfigMaps, Secrets, PersistentVolumeClaims, Events, ServiceAccounts
Cluster-scopedNodes, PersistentVolumes, StorageClasses, Namespaces themselves
Terminal window
kubectl api-resources # NAME column shows shortnames, NAMESPACED column shows true/false
NAME SHORTNAMES APIVERSION NAMESPACED KIND
nodes no v1 false Node
persistentvolumeclaims pvc v1 true PersistentVolumeClaim
persistentvolumes pv v1 false PersistentVolume
pods po v1 true Pod
podtemplates v1 true PodTemplate
replicationcontrollers rc v1 true ReplicationController
resourcequotas quota v1 true ResourceQuota
secrets v1 true Secret
serviceaccounts sa v1 true ServiceAccount
services svc v1 true Service

Every cluster is initialised with four predefined namespaces:

NamespacePurpose
defaultWhere objects land if no namespace is specified
kube-systemControl plane components (CoreDNS, metrics-server, kube-proxy)
kube-publicObjects that must be readable by anyone, including unauthenticated users
kube-node-leaseNode lease objects used for node heartbeats and health tracking

Namespaces prefixed with kube- are reserved for Kubernetes internals and should not contain end-user workloads.

  • Team or project separation - a single cluster divided into finance, hr, and ops namespaces, each with its own RBAC and quotas
  • Preventing naming collisions - two teams can both deploy an object named api without conflict, as long as they use separate namespaces
  • Applying different policies - different resource quotas, network policies, or admission rules per namespace
  • Lowercase alphanumeric characters and hyphens only
  • Must start and end with an alphanumeric character
  • Cannot contain dots (unlike most DNS subdomains)

Imperatively:

Terminal window
kubectl create namespace <name>
kubectl create ns <name> # ns is the shorthand alias

Declaratively:

apiVersion: v1
kind: Namespace
metadata:
name: my-team
Terminal window
kubectl apply -f namespace.yaml

Objects not given a namespace go to default. To target a specific namespace:

Imperatively (flag):

Terminal window
kubectl run my-pod --image=nginx -n my-team
kubectl apply -f my-pod.yaml -n my-team # deploy a manifest into a specific namespace
kubectl get pods -n my-team
kubectl get pods -A # across all namespaces

Declaratively (in the manifest):

apiVersion: v1
kind: Pod
metadata:
name: my-pod
namespace: my-team

Appending -n <namespace> to every command is repetitive. Set a persistent default for the current kubeconfig context:

Terminal window
kubectl config set-context --current --namespace=my-team
# Verify
kubectl config view --minify | grep namespace:
# Revert to default
kubectl config set-context --current --namespace=default
Terminal window
kubectl delete ns <name>

The kubectl delete ns command blocks until the namespace and all its objects are fully removed. If you interrupt it and list namespaces, you will see the namespace in Terminating status:

Terminal window
kubectl get ns
# NAME STATUS AGE
# default Active 2h
# my-team Terminating 2h ← deletion in progress

Use --wait=false to return immediately without waiting for full deletion:

Terminal window
kubectl delete ns <name> --wait=false

If a namespace is stuck in Terminating:

The most common cause is a custom resource whose controller did not process the deletion and failed to remove the object’s finalizer. Kubernetes will not delete an object that still has finalizers. Diagnose it:

Terminal window
kubectl get ns <name> -o yaml # check status.conditions for the blocking resource

A label is a key-value pair attached to a Kubernetes object that identifies it within the system. Labels enable the core Kubernetes mechanisms for object grouping, selection, and scheduling.

Both the key and value are strings. An object can have multiple labels; each key must be unique within that object.

In a microservices system, the number of running services can easily exceed 100. With replicas and simultaneous canary/stable releases, a single cluster can contain thousands of pods. Without a way to organise them, understanding what’s running becomes nearly impossible.

Kubernetes Labels

Labels solve this by letting you express identity on each object. For example, two labels are enough to classify any pod in a multi-service deployment:

LabelValue examplesPurpose
appatlas, beacon, nimbusWhich application this pod belongs to
relstable, canaryWhich release track it’s running

With these two labels, you can instantly answer questions like “show me all canary pods for the atlas service” using a selector — no manual tracking required.

Kubernetes does not enforce any specific label keys or values. You define the schema; Kubernetes provides the querying machinery.

Anatomy of Kubernetes Label

Keys have two parts: an optional prefix (must be a valid DNS subdomain) and a mandatory name:

  • Name: max 63 characters; alphanumeric, hyphens, underscores, dots; must start and end with alphanumeric

Values:

  • Max 63 characters; no whitespace; alphanumeric, hyphens, underscores, dots; must start with alphanumeric (or empty string)

If your data violates these rules (long text, special characters, URLs), use annotations instead.

System labels (applied automatically by Kubernetes to nodes and infrastructure objects):

KeyMeaning
kubernetes.io/archCPU architecture (amd64, arm64)
kubernetes.io/osOperating system (linux, windows)
kubernetes.io/hostnameNode hostname

Recommended application labels (app.kubernetes.io/ prefix):

KeyMeaningExample
app.kubernetes.io/nameApplication namemy-api
app.kubernetes.io/instanceUnique instance identifiermy-api-prod
app.kubernetes.io/componentArchitectural roledatabase, frontend
app.kubernetes.io/versionCurrent version1.4.2

Using the app.kubernetes.io/ prefix ensures compatibility with Kubernetes tooling and dashboards.

metadata:
name: my-pod
labels:
app.kubernetes.io/name: my-api
app.kubernetes.io/component: frontend
rel: stable
Terminal window
kubectl describe pod <name> # labels visible in metadata section
kubectl get pods --show-labels # adds a LABELS column to output
kubectl get pods -L app,rel # shows only specified keys as columns
Terminal window
# Add a label
# kubectl label pod <name> <key>=<value>
kubectl label pod my-pod env=prod
# Update an existing label (requires --overwrite as a safety check)
# kubectl label pod <name> <key>=<new-value> --overwrite
kubectl label pod my-pod rel=stable --overwrite
# Apply a label to all pods in the namespace
# kubectl label pods --all <key>=<value>
kubectl label pods --all tier=backend --overwrite
# Remove a label (append - to the key)
# kubectl label pod <name> <key>-
kubectl label pod my-pod env-

A label selector filters objects based on their labels. Selectors are used in two directions: by higher-level resources to target the objects they manage, and by Pods themselves to constrain which nodes they can be scheduled on.

Kubernetes Label Selectors

Filter on exact key-value matches:

ExpressionMatches
app=nginxObjects where app equals nginx
app!=nginxObjects where app is anything but nginx

More expressive filtering using sets:

ExpressionMatches
app in (atlas, nimbus)Objects where app is either atlas or nimbus
app notin (legacy)Objects where app is not legacy
appObjects that have the app key (any value)
!appObjects that do not have the app key

Multiple selectors are AND-ed - an object must match all of them:

Terminal window
kubectl get pods -l app=quote,rel=canary # must satisfy both
Terminal window
kubectl get pods -l app=web # equality
kubectl get pods -l 'rel in (stable, beta)' # set-based (quote in shell)
kubectl get pods -l '!deprecated' # key absence
# Delete all matching objects (no confirmation - use with care)
kubectl delete pods -l app=legacy

Selectors are the glue that holds Kubernetes resources together. Rather than hardcoding references by name, resources use label selectors to discover and target their dependants dynamically. This decoupling is intentional — it means you can replace, scale, or swap the underlying Pods without touching the resources that depend on them.

Common coupling patterns:

ResourceUses selector to findWhy it matters
ServicePods to route traffic toSwap pods underneath a Service with zero downtime
DeploymentReplicaSets it ownsEnables rolling updates and rollbacks without service interruption
NetworkPolicyPods the policy applies toEnforce network rules per workload, not per node

Production scenario — blue/green with a Service selector:
You run two Deployments: rel: blue and rel: green. The Service points to rel: blue. To cut over, you change the selector to rel: green — traffic shifts instantly with no pod restarts:

# Service selecting pods by label
spec:
selector:
app.kubernetes.io/name: my-api # routes traffic only to pods with this label
rel: blue # change to 'green' to cut over

By default, the Kubernetes scheduler places a Pod on any node with sufficient resources. Node scheduling lets you add placement constraints — restricting or preferring specific nodes based on their labels. This is essential in production clusters that have heterogeneous hardware.

When to use it:

ScenarioNode labelWhat it achieves
Mixed storage tiersdisk-type: ssd / disk-type: hddPin databases to SSD nodes; batch jobs to cheaper HDD nodes
Multi-arch clusterskubernetes.io/arch: amd64Run legacy x86-only workloads only on amd64 nodes in an arm64 cluster
GPU workloadsaccelerator: nvidia-v100Ensure ML training pods land only on GPU-equipped nodes
Compliance/regulatoryregion: eu-westKeep GDPR-sensitive workloads on nodes in a specific data-centre zone
Spot vs on-demandnode.kubernetes.io/lifecycle: spotPin fault-tolerant batch jobs to cheaper spot instances

nodeSelector (equality-based, simple — use when a single label match is enough):

spec:
nodeSelector:
kubernetes.io/arch: amd64 # only schedule on amd64 nodes
disk-type: ssd

nodeAffinity (set-based, expressive — use when you need In, NotIn, or soft preferences):

spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: [amd64, arm64]
- key: disk-type
operator: Exists

Available operators for matchExpressions: In, NotIn, Exists, DoesNotExist, Lt, Gt.

Labelling nodeskubectl label works on any object type, including nodes. You must label a node before a nodeSelector or nodeAffinity rule referencing that label can match:

Terminal window
# Add a custom label to a node
kubectl label node kind-worker node-role=front-end
# Overwrite an existing node label
kubectl label node kind-worker node-role=back-end --overwrite
# Remove a label from a node
kubectl label node kind-worker node-role-
# Verify node labels
kubectl get nodes --show-labels
kubectl get nodes -L node-role,disk-type # show only specific label columns

Field selectors filter objects based on their built-in manifest fields rather than labels. They cannot be used with annotations.

Labels answer “what is this object?” — but they only contain metadata you deliberately attach. Many useful filtering questions are about Kubernetes-managed state that you never define as a label: which node is a pod currently running on? What phase is it in? What is its exact name? Field selectors were introduced to fill that gap — letting you query the structural and runtime fields that Kubernetes itself writes into every object.

Kubernetes Field Selectors

metadata.name and metadata.namespace work across all object types. Other fields depend on the resource kind - use kubectl explain to inspect available fields, then test; kubectl returns an error if a field does not support selection.

Terminal window
# All pods running on a specific node
kubectl get pods --field-selector spec.nodeName=node-1
# Pods not in the Running phase (across all namespaces)
kubectl get pods --field-selector status.phase!=Running -A
# Combine with label selector
kubectl get pods -l app=web --field-selector spec.nodeName=node-1

Field selectors used for node affinity require matchFields instead of matchExpressions:

spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchFields:
- key: metadata.name
operator: NotIn
values: [node-a] # never schedule on node-a

Kubernetes Annotations

Annotations are also key-value pairs, but unlike labels they are not for identification or selection. They store larger amounts of arbitrary metadata attached to an object.

LabelsAnnotations
Used forIdentification and groupingDescriptive metadata and tooling
Can filter with selectorYesNo
Value size limit63 charactersUp to 256 KB
Value character restrictionsStrict (no whitespace, limited chars)Almost none
  • Kubernetes feature rollout - the Kubernetes project uses annotations to trial new fields before they graduate into the formal API schema; once proven, a proper field is added and the annotation is deprecated
  • Human context - creator name, team contact, Jira ticket, architecture notes
  • CI/CD metadata - Git commit SHA, build timestamp, image tag, registry URL
  • Tool configuration - configuration instructions for external tools (Prometheus scrape settings, Argo CD sync policies, cert-manager directives)

Annotation keys follow the same rules as label keys (optional DNS subdomain prefix + name component). Annotation values have no character restrictions.

metadata:
name: my-pod
annotations:
commit-sha: "abc123def456"
build-timestamp: "2026-07-24T08:00:00Z"
owner: "platform-team"

Annotations are not shown in kubectl get output. Use:

Terminal window
kubectl describe pod <name> # annotations section in output
kubectl get pod <name> -o json | jq .metadata.annotations
Terminal window
# Add an annotation
kubectl annotate pod <name> my-tool/owner='platform-team'
# Update an existing annotation (requires --overwrite)
kubectl annotate pod <name> my-tool/owner='infra-team' --overwrite
# Remove an annotation (append - to the key)
kubectl annotate pod <name> my-tool/owner-