Namespaces and Labels
As a cluster grows, it becomes home to many teams, many applications, and many environments. Without some way to organize all of that, everything lands in a single undifferentiated pile, and it becomes difficult to know which resources belong to which application, which are safe to delete, and which are critical. Kubernetes provides two complementary mechanisms for this: namespaces for hard boundaries between groups, and labels for flexible tagging within those groups.
Namespaces divide the cluster into isolated sections. Labels are key-value tags you attach to any resource and then use to filter, query, and connect resources together. Both are fundamental to working in a real cluster.
Namespaces
A namespace is a virtual partition inside the cluster. Resources in one namespace don’t interact with resources in another by default. You can have a Service named api in the staging namespace and a completely different Service also named api in the production namespace - they coexist without conflict because they’re in separate namespaces. This isolation also extends to resource quotas and access control: you can apply policies to an entire namespace, which makes it the natural boundary for separating teams or environments.
Every cluster starts with a few built-in namespaces. The default namespace is where resources land if you don’t specify one. This is convenient for learning, but in real systems you almost never put application workloads in default - you create a dedicated namespace for each application or team. The kube-system namespace is reserved for Kubernetes internal components: CoreDNS, kube-proxy, the scheduler, and the controller manager all run as Pods here. You should never deploy your own applications into kube-system. The kube-public namespace is readable by anyone and mostly used for cluster-level public bootstrapping information.
graph TB
CL["Kubernetes Cluster"]
NS1["Namespace: staging<br/>Service: api<br/>Deployment: web"]
NS2["Namespace: production<br/>Service: api<br/>Deployment: web"]
NS3["Namespace: kube-system<br/>CoreDNS · kube-proxy"]
CL --> NS1
CL --> NS2
CL --> NS3
Creating a namespace is as simple as:
kubectl create namespace my-teamOnce it exists, you target it with every command using -n:
kubectl apply -f app.yaml -n my-teamkubectl get pods -n my-teamkubectl delete deployment web -n my-teamTo see resources across all namespaces without specifying one at a time, use -A:
kubectl get pods -Akubectl delete namespace my-team deletes the namespace and every single resource inside it - Pods, Deployments, Services, ConfigMaps, Secrets. There is no undo and no confirmation prompt. Be careful with this command on shared clusters where others may be relying on what's inside.
Not every Kubernetes resource is namespaced. Nodes, PersistentVolumes, and ClusterRoles exist at the cluster level - they don’t belong to any namespace. If you’re ever unsure which category a resource falls into, you can ask:
kubectl api-resources --namespaced=truekubectl api-resources --namespaced=falseLabels
A label is a key-value pair you attach to a Kubernetes resource. Labels are completely arbitrary - Kubernetes doesn’t give them any inherent meaning. Their power comes from the fact that other resources can use label selectors to find and act on resources that have specific labels. This is the mechanism that connects a Service to its Pods, a ReplicaSet to the Pods it owns, and a Deployment to the ReplicaSet it manages.
metadata: labels: app: web env: production version: v2You add labels to resources in your manifest, or you can add or change them on a running resource imperatively:
kubectl label pod my-pod environment=stagingThe -l flag lets you filter any kubectl command by label. If all your backend Pods have app=backend, you can scope every command to them without knowing individual names:
kubectl get pods -l app=backendkubectl logs -l app=backendkubectl delete pods -l app=backendgraph LR
SVC["Service<br/>selector: app=web"]
P1["Pod<br/>app=web · env=prod"]
P2["Pod<br/>app=web · env=prod"]
P3["Pod<br/>app=api · env=prod"]
SVC -->|"matches"| P1
SVC -->|"matches"| P2
SVC -. "no match" .-> P3
Beyond kubectl filtering, labels are load-bearing. When you write a Service with a selector: app: web, Kubernetes finds every Pod in the same namespace that has app: web in its labels and routes traffic to them. If a Pod is missing that label, the Service never sends traffic to it - even if it’s running the right container image. If a Pod gains that label, it immediately starts receiving traffic. Understanding this selector mechanism is essential because it’s how the entire control plane wires resources together.
Annotations
Annotations are a related concept: they’re also key-value pairs attached to resources, but they are not used for selection. You can’t filter by annotations with -l. Annotations are for metadata that tools and operators need to read, but that shouldn’t influence how resources are grouped. Common uses include recording when something was deployed, who owns it, or attaching configuration consumed by third-party tools.
metadata: annotations: owner: 'platform-team' last-reviewed: '2025-01-15'Hands-On Practice
1. Create a namespace:
kubectl create namespace crash-lab2. Deploy a Pod into that namespace:
Paste the command in the terminal and open the visualizer before running it to see the Pod being created. It’s the telescope icon in the bottom right corner.
kubectl run web --image=nginx:1.28 -n crash-lab3. Confirm it’s isolated from the default namespace:
kubectl get podskubectl get pods -n crash-labThe first command shows nothing (or whatever was already in default). The second shows the Pod you just created. The two are completely separate.
4. Add labels to the running Pod:
kubectl label pod web tier=frontend -n crash-labkubectl label pod web env=learning -n crash-lab5. Check the labels:
kubectl get pod web -n crash-lab --show-labelsYou should see both labels listed in the LABELS column.
6. Filter by label:
kubectl get pods -n crash-lab -l tier=frontendkubectl get pods -n crash-lab -l tier=backendThe first command returns your Pod. The second returns nothing - no Pod in this namespace has tier=backend.
7. Remove a label:
kubectl label pod web env- -n crash-labThe trailing - after the key name removes that label. Run --show-labels again to confirm it’s gone.
8. Clean up:
kubectl delete namespace crash-labThe namespace, and the Pod inside it, are both removed.