A virtual partition of the cluster — scope names, quotas, and access by environment or team.
Use the default namespace as your starting point, then this chapter creates a separate dev namespace:
kubectl config set-context --current --namespace=default
kubectl delete namespace dev --ignore-not-foundA Namespace is a virtual partition of one cluster. It scopes object names, and gives you a unit to attach quotas and access control to. Think folders: two teams can each have a web Deployment without clashing, as long as they're in different namespaces.
Your cluster came with a few:
kubectl get namespacesNAME STATUS AGE
default Active 1h ← where your objects go if you don't say otherwise
kube-system Active 1h ← the control plane + add-ons (don't touch)
kube-public Active 1h
kube-node-lease Active 1h
Everything you've created so far landed in default. The control plane you saw in Set Up lives in kube-system.
kubectl create namespace dev
kubectl apply -f manifests/core-objects/web-deployment.yaml -n dev # create in dev
kubectl apply -f manifests/core-objects/web-service.yaml -n dev # create a same-named Service in dev
kubectl get pods -n dev # view dev only
kubectl get svc -n dev # view Services in dev
kubectl get deploy web -n default
kubectl get deploy web -n dev # same name, different namespace
kubectl get pods -A # every namespaceTired of typing -n dev? Pin it to your context:
kubectl config set-context --current --namespace=devThat default sticks until you change it again. When a later command seems to "lose" an object, check your current namespace with kubectl config get-contexts.
For namespaced objects, kubectl -n dev decides where an object goes when the manifest does not set metadata.namespace. If the manifest already says metadata.namespace: dev, kubectl apply -f file.yaml uses that namespace. Cluster-scoped objects ignore -n entirely.
In k9s, press :ns to switch, or 0 to see all namespaces at once.
A namespace by itself doesn't stop one team from eating all the cluster's CPU/memory. A ResourceQuota sets hard caps on a namespace — total Pod count, total requested/limited CPU and memory. A LimitRange can set default requests/limits for Pods that forget to declare them.
Short version: ResourceQuota is the ceiling for the namespace; LimitRange is the default or per-object rule inside that namespace.
▶ Runnable manifest: manifests/core-objects/dev-resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: dev-quota
namespace: dev
spec:
hard:
pods: "4"
requests.cpu: "300m"
requests.memory: 320Mi
limits.cpu: "1"
limits.memory: 640MiEvery key under hard is a cap on the sum across the whole namespace, not per Pod:
| Key | Caps |
|---|---|
pods |
Total number of Pods allowed in dev, regardless of their size. |
requests.cpu |
Sum of every container's resources.requests.cpu in the namespace. 300m = 0.3 CPU core. |
requests.memory |
Sum of every container's resources.requests.memory. 320Mi = 320 mebibytes. |
limits.cpu |
Sum of every container's resources.limits.cpu. "1" = 1 full CPU core. |
limits.memory |
Sum of every container's resources.limits.memory. 640Mi = 640 mebibytes. |
Numeric values are quoted ("4", "300m", "1") because Kubernetes' quantity format (300m, 1, 640Mi) isn't a plain YAML number — quoting keeps it from being misparsed. So with this quota: at most 4 Pods total in dev, and across all of them combined, requests can't exceed 0.3 CPU / 320Mi memory, nor limits exceed 1 CPU / 640Mi memory.
▶ Runnable manifest: manifests/core-objects/dev-limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: dev-defaults
namespace: dev
spec:
limits:
- type: Container
defaultRequest:
cpu: 50m
memory: 64Mi
default:
cpu: 200m
memory: 128MiUnlike ResourceQuota, this applies per container, not as a namespace-wide sum:
| Key | Meaning |
|---|---|
type: Container |
These defaults apply to each container individually (LimitRange can also target Pod or PersistentVolumeClaim). |
defaultRequest |
If a container omits resources.requests, it gets cpu: 50m (0.05 core) / memory: 64Mi here. |
default |
If a container omits resources.limits, it gets cpu: 200m (0.2 core) / memory: 128Mi here. |
So a Pod that declares no resources at all isn't actually "unbounded" — the apiserver silently fills in these values at creation time, and those filled-in values are what count against the ResourceQuota above. This is also why a quota-tracked namespace doesn't reject bare Pods outright: as long as a LimitRange default exists, there's something to count.
kubectl apply -f manifests/core-objects/dev-resourcequota.yaml
kubectl apply -f manifests/core-objects/dev-limitrange.yaml
kubectl describe resourcequota dev-quota -n dev # USED vs HARD for each resource
kubectl describe limitrange dev-defaults -n dev # default requests/limitsOnce a quota tracks CPU/memory, every Pod in that namespace needs resources.requests/limits. The Deployment example declares them directly; the LimitRange provides defaults for simpler Pods that do not. Without either one, the apiserver rejects the Pod because it cannot count it against the quota.
Try pushing past the cap:
kubectl scale deployment/web -n dev --replicas=5
kubectl get pods -n dev # only 4 Running — quota caps at pods: "4"
kubectl get events -n dev --sort-by=.lastTimestamp # look for "exceeded quota: dev-quota …"
kubectl describe deployment web -n dev # also shows the failed scale-up eventThe 5th Pod never gets created — the apiserver rejects it at admission time, before the scheduler even sees it. Scale back down to clean up:
kubectl scale deployment/web -n dev --replicas=3To see namespaced vs cluster-scoped resources directly:
kubectl api-resources --namespaced=true
kubectl api-resources --namespaced=false- ✅ Names —
webindevandwebinprodare different objects. - ✅ A scope for
ResourceQuota,LimitRange, and RBAC — cap resources or restrict access per namespace. - ✅ DNS — the
webService you created indevis reachable cross-namespace asweb.dev.svc.cluster.local(see Service Discovery & DNS). - ❌ Not a security boundary by default — without a NetworkPolicy, Pods in one namespace can still reach Pods in another over the network. Namespaces organize; they don't firewall.
- ❌ Not for every little thing — cluster-wide objects (Nodes, PersistentVolumes, Namespaces themselves) aren't namespaced.
- Separate environments/teams with namespaces (
dev,staging,prod), not separate clusters, when isolation needs are modest. - Never deploy your apps into
kube-system— it's reserved for cluster components. - Add
ResourceQuota/LimitRangeper namespace in shared clusters so one team can't starve others. - For real isolation between namespaces, add NetworkPolicies and RBAC.
If you set your default namespace to dev, switch back to default before continuing:
kubectl config set-context --current --namespace=defaultThen delete the lab namespace. This removes the web Deployment, Service, ResourceQuota, and LimitRange inside it:
kubectl delete namespace devTo fully reset after Part 1, also remove the web Service and Deployment left in default:
kubectl delete -f manifests/core-objects/web-service.yaml --ignore-not-found
kubectl delete -f manifests/core-objects/web-deployment.yaml --ignore-not-found