09 · Namespaces & YAML Manifest Structure¶
Not run against a live cluster
Manifests and command output below follow documented Kubernetes behavior; not executed against a live cluster in this session.
What namespaces are for¶
A namespace partitions a single physical cluster into multiple virtual
clusters. Most namespaced resources (Pods, Deployments, Services,
ConfigMaps, Secrets — but not cluster-scoped resources like Nodes or
PersistentVolumes) live inside exactly one namespace, and names only need
to be unique within a namespace — you can have a web Deployment in
both staging and production namespaces without conflict.
Typical uses:
- Separating environments (
dev,staging,production) on a shared cluster. - Separating teams/projects sharing a cluster, often combined with RBAC (Level 3) and resource quotas to isolate them from each other.
- Kubernetes itself uses
kube-systemfor control-plane component Pods andkube-public/kube-node-leasefor other internal purposes.
Working with namespaces¶
kubectl get namespaces
# NAME STATUS AGE
# default Active 10d
# kube-system Active 10d
# kube-public Active 10d
# kube-node-lease Active 10d
Create one:
Deploy into it — either put namespace: in the manifest's metadata:
...or specify it on the command line:
If neither is set, resources land in whatever namespace your current
context defaults to (default, unless you changed it — Module 04).
What DNS and Service discovery look like across namespaces¶
Recall from Module 07: a Service's DNS name is
<name>.<namespace>.svc.cluster.local. Within the same namespace, the
short name (web) works; from a different namespace you need at least
web.staging:
# From a pod in the "default" namespace, reaching a service in "staging":
wget -qO- http://web.staging
wget -qO- http://web.staging.svc.cluster.local
Deleting a namespace deletes everything in it¶
This cascades — every Pod, Deployment, Service, ConfigMap, Secret, etc.
inside staging is deleted with it. This is powerful for tearing down a
whole environment in one command, and correspondingly dangerous if run
against the wrong namespace — always double-check
kubectl config current-context and -n <namespace> before deleting.
YAML manifest structure, formalized¶
You've been writing manifests since Module 05 — here's the structure made explicit, since Module 10's project asks you to compose several kinds together in one workflow.
The four top-level fields, every time¶
apiVersion: <group>/<version> # or just <version> for core resources
kind: <ResourceType>
metadata:
name: <string>
namespace: <string> # optional; defaults per context
labels: { key: value, ... } # optional; used by selectors
annotations: { key: value, ... } # optional; non-identifying metadata
spec:
# shape is entirely specific to `kind`
apiVersion cheat sheet for what you've used so far:
| Kind | apiVersion |
|---|---|
| Pod, Service, ConfigMap, Secret, Namespace | v1 |
| Deployment, ReplicaSet | apps/v1 |
kubectl explain <kind> gives you the authoritative field reference
straight from the cluster's API, without leaving the terminal:
Multiple objects in one file¶
YAML's --- document separator lets you define several resources in a
single manifest file — very common for "everything one small app needs":
# app.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
data:
LOG_LEVEL: "info"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27-alpine
envFrom:
- configMapRef:
name: web-config
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
kubectl apply -f app.yaml # creates/updates all three objects
kubectl delete -f app.yaml # deletes all three
Ordering inside the file mostly doesn't matter — Kubernetes will happily
accept a Deployment that references a ConfigMap defined later in the same
file, since apply first writes all objects to the API server and
reconciliation happens asynchronously. Convention still favors config
before consumers, top to bottom, for human readability.
Directory-based organization¶
For anything beyond a toy example, one file per resource (or per logical group) in a directory is standard:
kubectl apply -f <dir> applies every YAML file in the directory
(non-recursively by default; add -R to recurse into subdirectories).
How It Actually Works¶
Namespaces and manifests both boil down to how the API server keys and scopes objects internally:
- A namespace is not a network boundary — it's a key prefix. Every
namespaced object is stored in etcd under a path like
/registry/<resource>/<namespace>/<name>. This means uniqueness is only enforced within that prefix (webcan exist in bothdevandstagingsimultaneously with zero conflict), and it's also why cluster-scoped resources (Nodes, PersistentVolumes, Namespaces themselves, ClusterRoles) live at/registry/<resource>/<name>with no namespace segment at all — there's genuinely nowhere for a namespace to attach. Pod-to-Pod network reachability across namespaces is unaffected by any of this; that's governed separately by NetworkPolicy (Level 3), which is off (fully permeable) by default. - Deleting a namespace triggers cascading finalizer-driven cleanup,
not an instant wipe.
kubectl delete namespacefirst setsstatus.phase: Terminatingand stamps the object with akubernetesfinalizer; a namespace controller then lists every API resource type and deletes every object scoped to that namespace one by one (in dependency order, driven by the same owner-reference garbage collector from Module 06), and only removes the finalizer — letting the Namespace object itself finally disappear — once that sweep confirms nothing is left. A namespace stuck inTerminatingalmost always means one object in it has its own finalizer that nothing is running to satisfy. kubectl apply -f <dir>applies each file as an independent request, in filename-sorted order, not as one transaction. There is no atomicity across the batch — if file 3 of 5 fails validation, files 1 and 2 are already committed to etcd. This is exactly why manifests are conventionally ordered so dependencies (Namespace, then ConfigMap/Secret, then Deployment, then Service) sort correctly by filename: the API server processes whatever kubectl sends it in sequence, and kubectl sends them in the order it read the directory.- YAML's
apiVersion/kindare how the API server picks a schema and validator, not decoration. The API server maintains a discovery document mapping every(group, version, kind)to a specific REST endpoint and Go struct; submitting akindthat doesn't match the registered schema for thatapiVersionfails at admission before any business logic runs, which is the actual reason a typo'dapiVersionproduces "no matches for kind" rather than a more specific error.
Exercise¶
Create a staging namespace. Write a directory manifests/ containing
configmap.yaml, deployment.yaml, and service.yaml (reusing the
web-config/web names from Modules 06-08), each with namespace:
staging set, and apply the whole directory with one kubectl apply -f
manifests/. Confirm with kubectl get all -n staging, then delete the
entire environment with a single kubectl delete namespace staging and
confirm everything is gone.