So far in this series you have a local cluster and you know how to run Pods on it. Everything you created landed in the same big shared room. This part teaches you how to put up walls: Kubernetes namespaces let you split one physical cluster into many virtual ones, aim kubectl at the room you care about, and stop any single team from eating the whole buffet.

What you need

  • A working local cluster from Part 1 (kind or minikube) and kubectl pointed at it.
  • Comfort with creating Pods from Part 2.
  • About 20 minutes and nothing to install.

Recipe 1: Meet the built-in namespaces

Problem: You keep hearing about "namespaces" and want to know what already exists in a fresh cluster before you create your own.

Ask the cluster to list them:

kubectl get namespaces 
NAME STATUS AGE default Active 4d kube-node-lease Active 4d kube-public Active 4d kube-system Active 4d 

(kubectl get ns is the short form and does the same thing.)

How it works: A namespace is a scope for names and for API objects. Two Pods can both be called clinerds-demo as long as they live in different namespaces. The four you see above come with every cluster:

  • default is where your objects go when you don't say otherwise. Everything you made in Parts 1 and 2 lives here.
  • kube-system holds the cluster's own machinery: the DNS server, the network plugin, and so on. Look, but don't deploy your apps here.
  • kube-public is readable by everyone, even unauthenticated clients. It's mostly used for cluster discovery info and you can ignore it.
  • kube-node-lease stores small "lease" heartbeat objects that each node renews so the control plane can tell quickly when a node has died.

One caveat worth burning into memory: namespaces are not a hard security boundary by themselves. By default, a Pod in one namespace can still open network connections to Pods in every other namespace, and anyone with cluster-wide kubectl access sees everything. Namespaces give you organization and a place to hang policies. The actual locks — network policies and role-based access control (RBAC) — come later in this series, especially in Part 10.

Recipe 2: Create dev and prod lanes and run the same app in both

Problem: You want to test changes without touching the "production" copy of your app, but you only have one cluster.

Create two namespaces, one for each lane:

kubectl create namespace cookbook-dev kubectl create namespace cookbook-prod 

Now save the series demo app as clinerds-demo-pod.yaml:

apiVersion: v1 kind: Pod metadata: name: clinerds-demo spec: containers: - name: clinerds-demo image: hashicorp/http-echo args: - "-text=hello from the cookbook" ports: - containerPort: 5678 

Apply the exact same file into both namespaces:

kubectl apply -f clinerds-demo-pod.yaml -n cookbook-dev kubectl apply -f clinerds-demo-pod.yaml -n cookbook-prod 

The -n flag tells kubectl which namespace to work in. Check one lane:

kubectl get all -n cookbook-dev 
NAME READY STATUS RESTARTS AGE pod/clinerds-demo 1/1 Running 0 25s 

Now sweep the whole cluster at once:

kubectl get pods --all-namespaces 
NAMESPACE NAME READY STATUS AGE cookbook-dev clinerds-demo 1/1 Running 40s cookbook-prod clinerds-demo 1/1 Running 40s kube-system coredns-76f75df574-abcde 1/1 Running 4d 

How it works: Both Pods are named clinerds-demo and nothing collides, because object names only have to be unique within a namespace. Think of namespaces as folders: two folders can each contain a file with the same name. --all-namespaces (short form -A) lifts the folder filter and shows you everything, which is great for "where did that thing go?" moments.

Peeking ahead: Namespaces also show up in DNS. Once an app has a Service, other Pods can reach it at service-name.namespace-name.svc.cluster.local, so dev and prod copies are addressable side by side. Part 5 serves the full story on Services and DNS.

Recipe 3: Stop typing -n on every command

Problem: You're doing all your work in cookbook-dev today and adding -n cookbook-dev to every command is getting old.

Your kubectl configuration (usually ~/.kube/config) holds a context: the bundle of "which cluster, which user, which namespace" that every command uses. Set the namespace on your current context:

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

Verify it stuck:

kubectl config view --minify | grep namespace 
 namespace: cookbook-dev 

Now plain commands default to that lane:

kubectl get pods 
NAME READY STATUS RESTARTS AGE clinerds-demo 1/1 Running 0 5m 

No -n needed. When you're done, switch back to the cluster-wide default:

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

How it works: --current edits the context you're actively using instead of creating a new one. Every later command reads that namespace field unless you override it with an explicit -n. The setting lives in your local kubeconfig file, so it only affects your machine — it changes nothing on the cluster itself.

Variations:

  • The community tool kubens (installed alongside kubectx) makes this a one-word command: kubens cookbook-dev. Handy if you switch lanes fifty times a day.
  • You can also see the active namespace quickly with kubectl config view --minify -o jsonpath='{..namespace}'.

Recipe 4: Put a ResourceQuota on the dev buffet

Problem: The dev lane is for experiments, but one forgotten experiment shouldn't be allowed to eat the whole cluster's CPU and memory.

A ResourceQuota is a per-namespace budget. Save this as cookbook-dev-quota.yaml:

apiVersion: v1 kind: ResourceQuota metadata: name: cookbook-dev-quota namespace: cookbook-dev spec: hard: pods: "3" requests.cpu: "500m" requests.memory: "512Mi" 
kubectl apply -f cookbook-dev-quota.yaml 

Make sure you're still targeting cookbook-dev from Recipe 3, then create a couple of Pods that each request 100 millicores (a millicore is one-thousandth of a CPU) and 64 mebibytes:

kubectl run demo-2 --image=hashicorp/http-echo --requests="cpu=100m,memory=64Mi" -- -text=two kubectl run demo-3 --image=hashicorp/http-echo --requests="cpu=100m,memory=64Mi" -- -text=three 

That's three Pods — the budget is full. Try a fourth:

kubectl run demo-4 --image=hashicorp/http-echo --requests="cpu=100m,memory=64Mi" -- -text=four 
Error from server (Forbidden): pods "demo-4" is forbidden: exceeded quota: cookbook-dev-quota, requested: pods=1, used: pods=3, limited: pods=3 

Check the running tab any time:

kubectl describe quota cookbook-dev-quota -n cookbook-dev 
Name: cookbook-dev-quota Namespace: cookbook-dev Resource Used Hard -------- ---- ---- pods 3 3 requests.cpu 200m 500m requests.memory 128Mi 512Mi 

How it works: A quota is enforced by an admission controller — a gatekeeper inside the API server that inspects every create request before it's stored. When you ask for a new Pod, the controller adds up what the namespace already uses plus what your Pod requests, and rejects the request if any hard limit would be exceeded. Nothing was "scheduled and then killed"; demo-4 never existed.

When it goes wrong:

  • The moment a quota mentions requests.cpu or requests.memory, every new Pod in that namespace must declare those requests, or it's rejected outright. Pods already running are grandfathered in. Recipe 5 shows how to supply those values automatically.
  • Quotas count requests (what a Pod reserves), not actual usage. A Pod sleeping at 1% CPU still spends its full request against the budget.

Recipe 5: Give every container defaults with a LimitRange

Problem: Recipe 4's quota now rejects any Pod that forgets to declare CPU and memory requests. You don't want every developer hand-writing the same numbers.

A LimitRange can inject sensible defaults. First free up some room in the quota, then save cookbook-dev-limits.yaml:

kubectl delete pod demo-2 demo-3 
apiVersion: v1 kind: LimitRange metadata: name: cookbook-dev-defaults namespace: cookbook-dev spec: limits: - type: Container default: cpu: 250m memory: 128Mi defaultRequest: cpu: 100m memory: 64Mi 
kubectl apply -f cookbook-dev-limits.yaml 

Now create a Pod with no resource settings at all:

kubectl run demo-defaults --image=hashicorp/http-echo -- -text=defaults 

Inspect what the cluster actually stored:

kubectl describe pod demo-defaults 
 Limits: cpu: 250m memory: 128Mi Requests: cpu: 100m memory: 64Mi 

How it works: LimitRange is another admission controller, but instead of rejecting Pods it edits them on the way in. When a container arrives with no resources block, the controller copies in defaultRequest as the requests and default as the limits before the Pod is saved. Your YAML stayed clean, and the quota is satisfied because the requests now exist. Note this only applies to Pods created after the LimitRange — it doesn't retroactively fix old ones. A container that sets its own values keeps them; defaults only fill in blanks.

Cleaning up

When you're done experimenting, one command removes the whole lane and everything in it:

kubectl delete ns cookbook-dev 

Treat this with respect: deleting a namespace deletes every object inside it — Pods, quotas, LimitRanges, ConfigMaps, Secrets, all of it. There is no trash can and no undo. Keep cookbook-prod around if you want a running demo for the next part.

Where to go next

You can now slice one cluster into tidy lanes, point kubectl where you want, and keep resource appetites in check. The loose end is that our demo app is still a single hand-made Pod — one kubectl delete and it's gone forever. Part 4: Deployments fixes that with self-healing, replica-managed applications. For the official reference, see the Kubernetes namespaces documentation, and browse the whole series at Kubernetes Cookbook.