Understanding Kubernetes Namespaces

Lesson 4 of 39 11 min read Updated

Picture an office building shared by several companies. Each company has its own floor, its own reception desk and its own staff, yet they all share the same lifts, power and street address. Namespaces give a Kubernetes cluster the same kind of floors.

A namespace is a virtual partition inside one cluster. Pods, Deployments, Services and ConfigMaps all live in a namespace, and their names only have to be unique inside it. That is why you can run a Deployment called web-api in both a development and a production namespace without a clash.

Namespaces are also the boundary where you attach access rules (RBAC), resource quotas and network policies. They are the main tool for separating teams and environments on a shared cluster.

What you will learn

  1. Why namespaces exist and what the four built-in namespaces are for
  2. Which resources live inside a namespace and which belong to the whole cluster
  3. How to create namespaces and deploy workloads into them
  4. How Services in different namespaces find each other through DNS
  5. How to give a namespace a budget with a ResourceQuota

Why use namespaces?

For a hobby project with one developer and one environment, a single namespace is fine. Real teams usually need to:

  1. Separate environments (development, staging, production) on the same cluster
  2. Give each team its own isolated workspace
  3. Limit how much CPU, memory and storage a team can consume
  4. Apply different security policies per environment
  5. Organise hundreds of microservices so their names do not collide

Namespaces cover all of these without running a separate cluster for each team or environment.

The built-in namespaces

Every cluster starts with four namespaces. Know what each one is for before you create your own.

NamespacePurposeDeploy your workloads here?
defaultWhere resources go when you do not name a namespaceOnly for quick demos, never in production
kube-systemComponents Kubernetes runs for itself, such as CoreDNS, kube-proxy and, on many clusters, the scheduler and controller managerNo, Kubernetes manages it
kube-publicData readable by everyone, even without authentication, such as the cluster-info ConfigMapNo
kube-node-leaseLease objects that act as node heartbeats, so the control plane notices failed nodes quicklyNo

List them on your own cluster:

kubectl get namespaces

Expected output (your ages will differ):

NAME STATUS AGE
default Active 5d
kube-node-lease Active 5d
kube-public Active 5d
kube-system Active 5d
Do not use default for real workloads. Everything you create without naming a namespace lands there, so it quickly turns into a mix of unrelated things. Give each app or environment its own namespace, and leave kube-system to Kubernetes.

Namespace-scoped and cluster-scoped resources

Not every resource lives in a namespace. Each kind is either namespace-scoped or cluster-scoped.

ScopeExamplesVisible from
Namespace-scopedPods, Deployments, ReplicaSets, Services, ConfigMaps, Secrets, Jobs, CronJobsOnly the namespace it belongs to
Cluster-scopedNodes, PersistentVolumes, ClusterRoles, ClusterRoleBindings, and Namespaces themselvesThe whole cluster, from every namespace

Not sure about a kind? Ask the API server:

# Kinds that live inside a namespace
kubectl api-resources --namespaced=true

# Kinds that belong to the whole cluster
kubectl api-resources --namespaced=false

Creating a namespace

For a quick experiment, one command is enough:

kubectl create namespace ecommerce-dev

In real projects, prefer a manifest that you keep in Git. Labels make the namespace easy to filter and to target with policies later. Save this as ecommerce-dev-namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: ecommerce-dev
labels:
environment: development
team: ecommerce

Save the production version as ecommerce-prod-namespace.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: ecommerce-prod
labels:
environment: production
team: ecommerce

You will apply both in the hands-on section. Namespace names must be valid DNS labels: lowercase letters, digits and -, starting and ending with a letter or digit, at most 63 characters.

Putting workloads into a namespace

There are two ways to choose the namespace of a resource:

  1. Set metadata.namespace in the manifest and run kubectl apply. The namespace is part of the definition and always explicit, so this is the recommended way for real workflows.
  2. Pass -n <namespace> (long form --namespace) to kubectl apply. This sets the namespace for manifests that do not declare one.

If you do neither, the resource goes to the default namespace of your current context, which is usually default.

The flag cannot override the manifest. If a manifest says namespace: ecommerce-dev and you run kubectl apply -n ecommerce-prod, kubectl refuses with an error saying the namespaces do not match. Use one or the other, not both with different values.

The Deployment below runs two replicas of an ASP.NET Core API in the ecommerce-dev namespace. It uses Microsoft's ready-made sample image, which listens on port 8080, so you can test it without building your own. Save it as catalog-api-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
name: catalog-api
namespace: ecommerce-dev
labels:
app: catalog-api
environment: development
spec:
replicas: 2
selector:
matchLabels:
app: catalog-api
template:
metadata:
labels:
app: catalog-api
environment: development
spec:
containers:
- name: catalog-api
image: mcr.microsoft.com/dotnet/samples:aspnetapp
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Development"
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"

Next, a ClusterIP Service exposes those Pods inside the cluster, in the same namespace. Save it as catalog-api-service.yaml:

apiVersion: v1
kind: Service
metadata:
name: catalog-api
namespace: ecommerce-dev
spec:
type: ClusterIP
selector:
app: catalog-api
ports:
- port: 80
targetPort: 8080

Talking across namespaces with DNS

Inside one namespace, a Service is reachable by its short name, for example http://catalog-api. From a different namespace, you add the namespace to the name.

Calling fromName to use
The same namespacecatalog-api
Another namespacecatalog-api.ecommerce-dev
Anywhere (fully qualified)catalog-api.ecommerce-dev.svc.cluster.local

The general form is <service-name>.<namespace>.svc.cluster.local. For example, the order-api Service in the production namespace is reachable at http://order-api.production.svc.cluster.local from any other namespace.

Namespaces separate names, not network traffic. By default any Pod can reach any Service in any namespace, as long as it uses the right DNS name. To block traffic between namespaces, add NetworkPolicies and use a network plugin that enforces them.

Giving a namespace a budget

Once each team has its own namespace, you can cap what it may use. A ResourceQuota limits the total CPU, memory and number of objects in a namespace. A LimitRange sets default, minimum and maximum requests and limits for each container, so one runaway container cannot starve the others. The next lesson covers both in depth. Here is a first quota.

Save it as ecommerce-dev-quota.yaml:

apiVersion: v1
kind: ResourceQuota
metadata:
name: ecommerce-dev-quota
namespace: ecommerce-dev
spec:
hard:
requests.cpu: "2"
requests.memory: "2Gi"
limits.cpu: "4"
limits.memory: "4Gi"
pods: "20"
services: "10"

All Pods in ecommerce-dev together may request at most 2 CPU cores and 2Gi of memory, and set limits up to 4 cores and 4Gi. The namespace may hold at most 20 Pods and 10 Services.

Because this quota covers CPU and memory, every new Pod in the namespace must declare those requests and limits, or it is rejected. The catalog-api Deployment already does.

Everyday namespace commands

I want to…Command
List all namespaceskubectl get namespaces (short: kubectl get ns)
Create a namespacekubectl create namespace <name>
See a namespace with its quotas and limit rangeskubectl describe namespace <name>
List resources in one namespacekubectl get pods -n <name>
List resources in every namespacekubectl get pods -A
Change the default namespace of my contextkubectl config set-context --current --namespace=<name>
Check how much of a quota is usedkubectl describe resourcequota <quota> -n <name>
Delete a namespace and everything in itkubectl delete namespace <name>

Work in one namespace or in all of them

Add -n to scope any kubectl get command to one namespace, or -A (short for --all-namespaces) to see every namespace at once:

# One namespace
kubectl get pods -n ecommerce-dev
kubectl get deployments -n ecommerce-dev
kubectl get services -n ecommerce-dev

# Every namespace
kubectl get pods -A
kubectl get deployments -A

Stop typing -n on every command

Typing -n ecommerce-dev again and again gets old fast. Make it the default for your current context instead:

kubectl config set-context --current --namespace=ecommerce-dev

Every following kubectl command now targets ecommerce-dev until you change it. To check which namespace your context uses (empty output means default):

kubectl config view --minify --output 'jsonpath={..namespace}'

Inspect a namespace and its quota

kubectl describe namespace ecommerce-dev
kubectl get resourcequota -n ecommerce-dev
kubectl describe resourcequota ecommerce-dev-quota -n ecommerce-dev

Delete a namespace

Deleting a namespace deletes all resources inside it:

kubectl delete namespace ecommerce-dev
There is no undo. Deleting a namespace removes every Pod, Service, Secret, ConfigMap and PersistentVolumeClaim in it. Run kubectl config current-context and kubectl get all -n <name> first, so you know exactly what you are deleting and on which cluster.

Hands-on: development and production on one cluster

Your team runs a small e-commerce platform. You want the same application in a development environment, where developers experiment freely, and a production environment, where real customers shop. Both run on one cluster, kept apart by namespaces.

You need the five files from the sections above: the two namespace manifests, the Deployment, the Service and the quota.

Step 1 — Create the two namespaces

kubectl apply -f ecommerce-dev-namespace.yaml
kubectl apply -f ecommerce-prod-namespace.yaml

Verify that they exist:

kubectl get namespaces

Expected output (abbreviated):

NAME STATUS AGE
default Active 5d
ecommerce-dev Active 5s
ecommerce-prod Active 4s
kube-system Active 5d

Step 2 — Deploy the catalog API into development

kubectl apply -f catalog-api-deployment.yaml
kubectl apply -f catalog-api-service.yaml

Check that the Pods run in the right namespace:

kubectl get pods -n ecommerce-dev

Expected output (the name suffixes will differ):

NAME READY STATUS RESTARTS AGE
catalog-api-6d4cf56db6-7xk2p 1/1 Running 0 20s
catalog-api-6d4cf56db6-q9m4z 1/1 Running 0 20s

Now confirm that the Pods are not visible from the production namespace:

kubectl get pods -n ecommerce-prod

Expected output:

No resources found in ecommerce-prod namespace.

Step 3 — Attach the quota to development

kubectl apply -f ecommerce-dev-quota.yaml

See the budget and how much of it is already used:

kubectl describe resourcequota ecommerce-dev-quota -n ecommerce-dev

Expected output. The Used column already counts the two catalog-api Pods and their Service:

Name: ecommerce-dev-quota
Namespace: ecommerce-dev
Resource Used Hard
-------- ---- ----
limits.cpu 1 4
limits.memory 512Mi 4Gi
pods 2 20
requests.cpu 200m 2
requests.memory 256Mi 2Gi
services 1 10

Step 4 — Make development your default namespace

kubectl config set-context --current --namespace=ecommerce-dev

You can now leave out -n ecommerce-dev. This lists the catalog-api Pods without any flag:

kubectl get pods

Step 5 — Call the API from another namespace

Start a short-lived busybox Pod in ecommerce-prod and fetch the catalog-api Service that lives in ecommerce-dev, using its full DNS name:

kubectl run debug --image=busybox:1.37 --restart=Never -n ecommerce-prod -- \
wget -qO- http://catalog-api.ecommerce-dev.svc.cluster.local

Read what it downloaded:

kubectl logs debug -n ecommerce-prod

You should see the HTML of the sample app's home page. The full DNS name works from any namespace, while the short name catalog-api resolves only inside ecommerce-dev.

Step 6 — Clean up

Delete the debug Pod:

kubectl delete pod debug -n ecommerce-prod

Point your context back at the default namespace:

kubectl config set-context --current --namespace=default

When you are finished with the exercise, delete both namespaces. The Deployment, Service and quota go with them:

kubectl delete namespace ecommerce-dev ecommerce-prod
Same names, two environments. You can now apply an identical catalog-api Deployment to ecommerce-prod without renaming anything. The namespace keeps the two copies apart, and each one can get its own quota and access rules.

Common mistakes

SymptomCauseFix
No resources found, but you know the Pod existsYou are looking in the wrong namespaceAdd -n <namespace>, or use -A to find it
kubectl apply -n fails with a namespace mismatchThe manifest's metadata.namespace differs from the flagRemove the flag or fix the manifest
A Service cannot be reached from another namespaceYou used the short nameUse <service>.<namespace> or the full .svc.cluster.local name
New Pods are rejected after you add a quotaThe quota covers CPU and memory, but the Pod declares no requests or limitsAdd requests and limits, or add a LimitRange with defaults (next lesson)

Summary

Namespaces are virtual partitions inside a Kubernetes cluster that keep resource names apart. They are the foundation for multi-tenancy, environment separation and access control.

  1. Every cluster has four built-in namespaces: default, kube-system, kube-public and kube-node-lease.
  2. Some resources are namespace-scoped (Pods, Services, ConfigMaps) and some are cluster-scoped (Nodes, PersistentVolumes, Namespaces).
  3. Declare the namespace in your manifests with metadata.namespace for predictable deployments.
  4. Use kubectl get <resource> -n <namespace> for one namespace, or -A for all of them.
  5. Set a default namespace per context with kubectl config set-context --current --namespace=<ns>.
  6. Reach Services in other namespaces with <service>.<namespace>.svc.cluster.local. Namespaces do not block network traffic on their own.
  7. Attach a ResourceQuota to cap CPU, memory and object counts per namespace.
  8. Deleting a namespace removes everything inside it, so double-check before you do it.

Next, you will give each namespace a proper budget with ResourceQuota and LimitRange.