feat(root): author Kubernetes foundations curriculum

root-05-m00-kubernetes-mental-model — a new, full gold-standard-pattern
front-door module for ROOT 05: what Kubernetes solves, the desired-state
model, the control-plane/worker split, and Pods/Namespaces/labels
through kubectl. Lesson, demonstration (watching a Pod get replaced
after manual deletion), knowledge check, guided exercise (planning a
Namespace layout), a real Lab (namespaces-basics), and a reflection —
sequenced ahead of the existing bare kubectl-fluency lab bank.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-08-26 09:05:40 -05:00
co-authored by Claude Sonnet 5
parent 046a0fba18
commit 25409d3bd7
6 changed files with 377 additions and 0 deletions
@@ -0,0 +1,108 @@
# What Kubernetes Solves & the Desired-State Model
## Before Kubernetes: the problem
Imagine you're running a web application on three servers by hand. One server crashes at 2am —
someone has to notice, log in, and restart the application. Traffic grows, so you need a fourth
server — someone has to provision it, install dependencies, and configure it to match the other
three. You ship a new version — someone has to update all four servers, one at a time, without
taking the whole application down.
None of this is hard in isolation. It becomes unmanageable at scale, and it depends entirely on
a human noticing and reacting fast enough. Kubernetes exists to make a computer do that
noticing and reacting instead — continuously, automatically, and the same way every time.
## The core idea: desired state
You don't tell Kubernetes *how* to run your application step by step. You tell it *what you
want to be true* — "3 replicas of this container should be running" — and Kubernetes
continuously works to make that true, correcting drift whenever reality doesn't match.
This is the single most important mental shift for someone new to Kubernetes: you're not
writing a script that runs once. You're declaring a **desired state**, and a background process
never stops comparing the real world against it.
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3 # <- the desired state: "3 of these, always"
...
```
If a Pod crashes, Kubernetes doesn't "restart the crashed one" as a special case — it just
notices reality (2 running) no longer matches desired state (3 wanted) and creates a
replacement. Same mechanism, no special-casing.
## The cluster mental model
A Kubernetes cluster has two conceptually different kinds of machines:
- **The control plane** — the "brain." It stores the desired state (in `etcd`), watches actual
state, and makes decisions (the API server, scheduler, and controllers all live here). You
talk to the control plane through `kubectl`; you never SSH into it to manage workloads.
- **Worker nodes** — where your containers actually run. Each node runs a `kubelet` (an agent
that starts/stops containers as the control plane instructs) and a container runtime.
You describe desired state to the control plane. The control plane decides which nodes should
run what. The nodes' kubelets make it actually happen. You never manually place a Pod on a
specific node in normal operation — that placement decision belongs to the scheduler.
## Declarative, not imperative
"Imperative" means giving step-by-step instructions ("start a container, then another, then
configure networking between them"). "Declarative" means describing an end state and letting
the system figure out the steps. Kubernetes' API objects (Pods, Deployments, Services, and
everything else you'll learn in ROOT_) are declarative — you write YAML describing what should
exist, `kubectl apply` submits it, and the control plane does the rest.
This is also why Kubernetes YAML looks the way it does: it's not a script, it's a specification.
## Pods, Namespaces, and labels — the vocabulary you need first
- **Pod** — the smallest deployable unit. Usually one container, sometimes a small group of
tightly-coupled containers that must run together on the same node and share networking.
You'll rarely create bare Pods directly in real use (a Deployment manages them for you, see
ROOT 06) but understanding the Pod itself is foundational to everything built on top of it.
- **Namespace** — a way to partition one cluster into logically separate areas (e.g. `dev`,
`staging`, teams, or projects), so names don't collide and access/quotas can be scoped.
- **Labels** — arbitrary key/value tags attached to objects (`app: web`, `env: prod`). Labels
are how almost everything in Kubernetes finds related objects — a Deployment finds "its" Pods
by label selector, not by name pattern matching.
## kubectl: your one tool for all of this
`kubectl` is the command-line client that talks to the control plane's API server. Nearly
everything you'll do in ROOT_ Labs goes through it: `kubectl get` (list/inspect),
`kubectl describe` (detailed status + recent events — your primary troubleshooting tool),
`kubectl apply -f` (submit a desired-state YAML file), `kubectl logs` (see what a container
printed). You'll build real fluency with these across this module and the ones that follow.
## Instructor Notes
**Approximate timing:** 15 minutes read, 10-12 minutes for a live walkthrough with discussion.
**Demo plan:** Before the demonstration activity, it's worth drawing the control-plane/worker
split on a whiteboard (or sharing a simple diagram) and tracing one request: `kubectl apply` ->
API server -> etcd -> scheduler -> kubelet -> container runtime. Students retain the mental
model much better when they've seen the path, not just read about it.
**Likely misconceptions to watch for:**
- Students often assume `kubectl` talks directly to nodes/containers. It doesn't — it only ever
talks to the API server. Worth stating explicitly.
- "Declarative" is sometimes read as "automatic" in a vague sense. Be concrete: it means "you
describe the end state; a controller loop makes it true," not "Kubernetes guesses what you
want."
**Discussion question:** "If desired state says 3 replicas and you manually run `kubectl delete
pod` on one of them, what happens next, and why?" (Answer: a new one is created almost
immediately — deleting a Pod doesn't change the Deployment's desired replica count, so the
control loop restores it.)
**Optional extension:** Ask students to predict what would happen if they edited a running Pod's
container image directly with `kubectl edit pod` instead of updating the Deployment that manages
it (answer: it works until that specific Pod is replaced for any reason, at which point the new
Pod reverts to whatever the Deployment's template says — a good early illustration of "the
Deployment is the source of truth, not the running Pod").
@@ -0,0 +1,76 @@
# Demonstration: Desired State Reconciliation
Watch what actually happens when reality drifts from desired state — this is the mechanism the
whole lesson describes, made concrete.
## Setup
A Namespace called `demo` exists with a Deployment `web` declaring `replicas: 2`. Right now,
reality matches desired state:
```
$ kubectl get pods -n demo
NAME READY STATUS RESTARTS AGE
web-7d8f9c6b5d-abcde 1/1 Running 0 3m
web-7d8f9c6b5d-fghij 1/1 Running 0 3m
```
## Someone (or something) deletes one Pod
```
$ kubectl delete pod web-7d8f9c6b5d-abcde -n demo
pod "web-7d8f9c6b5d-abcde" deleted
```
## What happens in the next second or two, without anyone doing anything else
```
$ kubectl get pods -n demo
NAME READY STATUS RESTARTS AGE
web-7d8f9c6b5d-fghij 1/1 Running 0 3m
web-7d8f9c6b5d-klmno 0/1 ContainerCreating 0 1s
```
A brand new Pod (`...-klmno`, a new random suffix — this is NOT the deleted Pod coming back)
appeared on its own. Nobody ran `kubectl create` again. The Deployment's controller noticed
actual state (1 running Pod matching its label selector) no longer matched desired state
(`replicas: 2`), and created exactly one replacement to close the gap — not two, not zero.
A few seconds later:
```
$ kubectl get pods -n demo
NAME READY STATUS RESTARTS AGE
web-7d8f9c6b5d-fghij 1/1 Running 0 3m
web-7d8f9c6b5d-klmno 1/1 Running 0 8s
```
Back to 2/2 — desired state restored, entirely without human intervention.
## Why this matters beyond the demo
This exact mechanism is what makes a node crash, a Pod eviction, or a container being
OOM-killed something Kubernetes recovers from automatically instead of something that pages a
human at 2am. The controller loop doesn't know or care WHY a Pod disappeared — crash, manual
delete, node failure — it only ever asks "does reality match desired state right now?" and acts
if not. Understanding this one loop explains a huge amount of Kubernetes' behavior that would
otherwise look like unrelated special cases.
## Instructor Notes
**Demo script:** Run this live rather than only showing the captured output above — have
students watch `kubectl get pods -n demo --watch` in one terminal while you delete a Pod from
another. Watching the new Pod name/age appear in real time lands the "this is a NEW Pod, not a
restart" point far better than static text.
**Approximate timing:** 8-10 minutes live; 5 minutes as a self-paced read.
**Common misconception to correct:** students frequently describe this as "Kubernetes restarted
the Pod." Correct this explicitly — a deleted Pod is gone permanently; a *new* Pod object was
created to satisfy the replica count. This distinction matters later when debugging (the new
Pod has zero history from the old one — no old logs, no old restart count).
**Optional extension:** ask what would happen if you deleted the Deployment itself instead of
one Pod (answer: all of its Pods are deleted and NOT replaced, because the thing declaring
desired state is now gone — a good way to reinforce that the Deployment, not any individual
Pod, is the actual source of truth).
@@ -0,0 +1,48 @@
# Knowledge Check: The Kubernetes Mental Model
Answer each question yourself before reading the answer that follows it.
---
**1. You run `kubectl delete pod` on one Pod belonging to a Deployment with `replicas: 3`. A
few seconds later, `kubectl get pods` shows 3 Pods again, but one has a different name than any
of the original three. What actually happened?**
*Answer:* The deleted Pod is gone permanently. The Deployment's controller noticed actual state
(2 Pods) no longer matched desired state (3 Pods) and created ONE new Pod to close the gap. It
is not the old Pod restarting — it's a brand new object.
---
**2. A Deployment has `replicas: 3`, and there are 5 nodes in the cluster. Which of these
decides WHICH node each of the 3 Pods actually lands on?**
*Answer:* The scheduler (part of the control plane), not the person who wrote the Deployment
YAML and not the nodes themselves. You declare desired state; the scheduler makes the placement
decision based on available resources and any placement rules configured.
---
**3. True or false: `kubectl` communicates directly with the kubelet on whichever node your
Pod happens to be running on.**
*Answer:* False. `kubectl` only ever talks to the API server (part of the control plane). The
API server is the single entry point for every read and write — it never bypasses the control
plane to reach a node directly for a normal `get`/`apply`/`describe` call.
---
**4. Why is Kubernetes YAML described as "declarative" rather than a script?**
*Answer:* A script describes a sequence of steps to execute once. Kubernetes YAML describes an
END STATE you want to exist ("3 replicas of this container should be running") — a controller
loop then continuously works to keep reality matching that description, correcting drift
whenever it appears, indefinitely, not just once at apply time.
## Instructor Notes
**Facilitation:** Ask these aloud in a live classroom before revealing answers — question 1 in
particular tends to surface the "it restarted" misconception the demonstration activity should
have already addressed; if it hasn't landed, this is the moment to circle back.
**Approximate timing:** 5 minutes.
@@ -0,0 +1,57 @@
# Guided Exercise: Planning a Namespace Layout
This is a paper-and-thinking exercise — work through it before the Lab, not during it.
## The scenario
You're setting up a small Kubernetes cluster for a class project with three teams (`frontend`,
`backend`, `data`), each running their own set of Pods, and you also need a shared area for
tools everyone uses (a shared logging dashboard, for example).
## Your task
Sketch out, on paper or in a scratch file:
1. What Namespaces would you create, and why? (Hint: think about who needs to see what, and
whether any team's mistake — e.g. deleting the wrong Pod by accident — should be able to
affect another team.)
2. If `frontend` team members should be able to manage their own Pods but NOT touch `backend`'s
or `data`'s, what does that suggest about how Namespaces relate to access control? (You'll
learn the actual mechanism, RBAC, in ROOT 09 — for now just reason about why Namespace
boundaries make that kind of restriction possible at all.)
3. Would you put the shared logging dashboard in one of the three team Namespaces, or somewhere
else? Justify your answer.
4. If two teams both need to run something they each call `web`, what would happen if they were
in the SAME Namespace? What changes if they're in separate Namespaces?
## A reasonable answer to check yourself against
1. Three Namespaces (`frontend`, `backend`, `data`) — one per team, so each team's objects are
isolated from the others and a mistake in one Namespace (deleting the wrong Pod, applying a
bad manifest) can't accidentally affect a different team's workloads.
2. Namespace boundaries are what RBAC rules can be scoped TO — "this person can manage Pods in
the `frontend` Namespace" is a real, common permission model. Without separate Namespaces,
you'd have to distinguish access by resource name pattern instead of by boundary, which is
far more error-prone.
3. A separate Namespace (e.g. `shared-tools` or `platform`) — it isn't owned by any one team,
and mixing it into a team's Namespace would make it unclear who's responsible for it or
accidentally couple its lifecycle to that team's.
4. In the SAME Namespace, object names must be unique — a second `web` Deployment would either
fail to create or overwrite the first, depending on how it's applied. In SEPARATE
Namespaces, both `frontend/web` and `backend/web` can coexist without conflict — the
Namespace is part of what makes a name unique, not just the name itself.
If your answer differed meaningfully from this, re-read the lesson's Namespace section before
continuing to the Lab.
## Instructor Notes
**Facilitation:** Good as a 2-3 person small-group discussion in a live classroom before
revealing the reasonable answer — question 4 in particular often produces a genuine "oh" moment
once students realize name uniqueness is Namespace-scoped, not cluster-wide.
**Approximate timing:** 10 minutes live with discussion; 10 minutes self-paced.
**Optional extension:** ask what would need to be true for the `data` team to be ALLOWED to read
(but not modify) something in `shared-tools` — a good bridge to RBAC's read vs. write verbs,
covered properly in ROOT 09.
@@ -0,0 +1,44 @@
# Launch Lab: Working with Namespaces
Time to apply the mental model for real — you'll create an actual isolated Namespace and deploy
into it, in your own disposable Lab environment.
## What you're doing
You'll create a Namespace (`team-alpha`) and deploy an application into it with a specific
replica count — directly applying the "isolated area within one cluster" idea from the guided
exercise, and the desired-state model from the lesson (once you declare `replicas: 2`, that
becomes the standing desired state for that Deployment, enforced continuously).
## Before you start
Think back to the demonstration: once your Deployment exists with its desired replica count,
Kubernetes will keep it there on its own. You don't need to "check back" and manually fix
anything — if you want to prove this to yourself, try deleting one of your Pods after you've
finished the task and watch what happens.
## Your task in this Lab
Create the Namespace, then deploy the application inside it with the exact replica count
specified. Use `kubectl get namespace` and `kubectl get deployment -n <namespace>` to confirm
your work — the same verification pattern you'll use constantly throughout ROOT_.
This Lab has its own in-context hints if you get stuck. Try working from the lesson and guided
exercise first — the commands you need are exactly the ones described there.
When you're done, return to this module to close out with a reflection on why this isolation
matters beyond just "keeping things organized."
## Instructor Notes
**Classroom flow:** This is most students' first real `kubectl apply`/`create` in ROOT_ beyond
whatever pilot exposure they've had — a good moment to circulate and check that basic
`kubectl` command syntax isn't the blocker (as opposed to the Namespace concept itself, which
the guided exercise should have already addressed).
**Approximate timing:** 10-15 minutes.
**Common mistake to watch for:** creating the Deployment in the DEFAULT namespace instead of
`team-alpha` (forgetting `-n team-alpha` or the namespace field in the manifest) — this is a
very common first mistake and a good real-world illustration of why Namespace isolation only
works if you're actually specifying it every time.
@@ -0,0 +1,44 @@
# Reflection: Why Desired State Matters in Production
You just created an isolated Namespace and a Deployment with a declared replica count. Take a
few minutes to connect this to why it matters beyond passing this Lab's validation.
## Reflection questions
1. If your `team-alpha` Namespace were a real team's production environment, what's one mistake
in a DIFFERENT namespace that Namespace isolation would have protected you from?
2. You declared `replicas: 2` rather than manually creating two Pods. What would you have had
to do manually, over and over, if Kubernetes only supported creating individual Pods with no
concept of desired state?
3. Think of one non-Kubernetes system you've used (a thermostat, a spell-checker, an
auto-save feature) that also works by continuously comparing "what is" to "what should be."
What made you think of it?
## Real Ops takeaway
**Namespaces are your first real production safety boundary, and they're free.** A
misconfigured `kubectl delete` scoped to the wrong Namespace is a common, entirely preventable
class of incident — teams that consistently namespace their environments (dev/staging/prod, or
per-team) limit the blast radius of almost any operator mistake to just that Namespace. This
costs nothing to set up and prevents a real, common category of outage.
**Desired state is what makes self-healing possible at all.** Every "the platform recovered on
its own" story you'll hear about Kubernetes in production traces back to exactly the mechanism
you just watched in the demonstration: a controller comparing actual state to desired state and
correcting the gap. Understanding this loop is what lets you predict Kubernetes' behavior
instead of being surprised by it.
This module is complete. Continue to the next module.
## Instructor Notes
**Discussion questions for a live classroom:** use all three reflection questions as discussion
prompts. Question 3 in particular tends to produce good analogies that make the abstract
"controller loop" concept concrete and memorable.
**Approximate timing:** 8-10 minutes live discussion; 5 minutes self-paced writing.
**Optional extension:** ask students to name one place in their own life (not computing) where
"declare the goal and let a system enforce it" would work better than "manually do the thing
every time" — a genuinely reflective prompt that reinforces the mental model beyond Kubernetes
specifically.