Part I — The engine and the unit
The One Idea: Declare, Then Reconcile
This track derives Kubernetes from nothing. There is no prior lesson to repair — we start where every operator starts: you have a binary, you want it always running for the world, and your only tools are SSH, a startup command, and your own vigilance. That works for exactly one box on a calm afternoon. This lesson follows that manual baseline until it breaks under scale, node death, and rollouts, and shows that the only repair that survives all three is to stop typing commands and instead declare the state you want — then hand it to a loop that never stops checking. That loop is the whole machine. Every noun in the next thirteen lessons (container, Pod, controller, Service, scheduler) is just something this loop turns out to need.
./server &, and to keep it alive you add a restart cron and a runbook titled "what to do at 3am when it's down." That baseline has no model of desired state — only a human re-issuing commands. The moment the human is asleep, the box dies, traffic triples, or you need to ship v2, the gap between "what you wanted" and "what is actually running" goes unwatched, because nothing in the system is responsible for watching it.New capability: declarative desired state driven by a level-triggered, idempotent control loop (observe → diff → act → repeat forever) that continuously drives actual state toward desired. This loop is Kubernetes.
Forces next: the loop must reconcile "a running app," but a bare OS process is too slippery to declare and reconcile reproducibly — you need a portable, isolated, frozen unit. That is the container (lesson 01).
1 · Where the manual baseline breaks
Start with the smallest honest setup. One server, one binary, one command: you SSH in and run ./server. To survive a crash you wrap it in a restart loop or a cron job. To survive your own forgetfulness you write a runbook. This is real, it ships products, and it holds — until it meets the three forces every production system eventually meets.
The common root cause is not laziness — it is that the only place "I want 50 healthy copies of v2" exists is in your head. The system stores no desired state. It stores a pile of side effects from commands you happened to run. Nothing is continuously responsible for the gap between intent and reality, so the gap is only ever closed when a human notices and types. Humans sleep; reality does not. Every failure above is the same failure: intent is not represented anywhere a machine can act on.
2 · Two ways to ask: imperative vs declarative
There are exactly two ways to ask a computer for something, and the difference is the hinge of this entire track.
Imperative means you specify the steps: "start a process, then start another, then open this port." You are the planner; the machine just executes. The catch is that the steps only describe a transition from one specific assumed state to another. start the server assumes the server is not running. Run it twice and you may get two servers, or an error, or a port clash — the command does not know what already exists. The plan is only valid for the world you imagined when you typed it.
Declarative means you specify the result: "there should be 3 healthy copies of v2 serving on port 8080." You say nothing about how to get there, what already exists, or what just broke. You hand over a description of the end state and make it someone else's job to compute and apply whatever steps close the gap from wherever reality currently sits.
| Imperative ("do these steps") | Declarative ("reach this state") | |
|---|---|---|
| What you give | A sequence of commands | A description of the desired end state |
| Assumes | A specific starting state | Nothing — it reads actual state first |
| Run it twice | May double-apply or error (port clash, two copies) | Same result — already-correct means do nothing |
| A Pod dies after you ran it | Nothing reacts; your command is long gone | The gap reappears next check and is re-closed |
| Who holds the intent | You (in your head / your shell history) | The system (a stored, queryable object) |
Declarative wins for one structural reason: a declaration is durable and re-checkable. "Steps I ran" are spent the instant they finish; "the state I want" is a standing fact the system can compare against reality again and again, forever. The whole leap is: store the intent, then build something that never stops comparing.
3 · The control loop that makes a declaration true
A declaration on its own is inert — a wish written on a card. Something has to act on it, and it can't be a one-shot script (a script runs once, then the world drifts and no one is watching). It must be a loop that runs forever. This is the control loop, and it is borrowed directly from control engineering — the same idea as a thermostat, which is the cleanest mental model: you set 21°C (desired), it reads the room (actual), and if they differ it acts (heat or cool), then it checks again, forever. It never assumes the room stayed at 21°C just because it once was.
This is the entire engine. A program running this loop, watching one kind of desired state, is called a controller. Kubernetes is, at heart, a stored pile of declarations plus a swarm of controllers each running this loop. Strip away every other word you have heard about it and this is what is left. The loop is Kubernetes; every later noun is something the loop needs: it needs a durable place to keep the declarations and the observed truth (the control plane, lesson 03), a clean unit to count and create (the container and Pod, lessons 01–02), a way to find "its" copies among everyone else's (labels and selectors, lesson 04), and so on. None of those nouns is the point. The loop is the point.
desired state (stored) actual state (the world)
"3 healthy copies of v2" 2 running, 1 just died on a dead node
| |
v v
+---------------------- CONTROLLER ----------------------+
| observe actual -> diff vs desired -> act to close |
| ^ | |
| +--------------- repeat <---------------+ |
+--------------------------------------------------------+
(forever)
4 · Level-triggered and idempotent: why it self-heals
Two properties turn that loop from "a thing that runs" into "a thing that heals." Both are easy to state and easy to underrate.
Level-triggered vs edge-triggered. An edge-triggered system reacts to events — the transitions, the "edges": "a Pod died" fires a handler that creates one. That sounds fine until you ask what happens if the event is missed: the handler was busy, the controller had just restarted, three Pods died in the same instant and only one event got through. Then the system is permanently wrong and nothing ever re-fires to fix it, because the edge is in the past. A level-triggered system instead reacts to the current level — the standing value of the world right now: "desired is 3, I currently observe 1, therefore create 2." It does not care how the world got to 1, how many events it missed, or whether it has ever seen this situation before. It re-derives the correct action from scratch on every single pass. Miss every event you like; the next observe-diff-act pass still computes "+2" and fixes it. That is why Kubernetes self-heals: it is level-triggered, so it is structurally incapable of "missing" a failure — it only ever asks "what is true now, and what closes the gap," never "what just happened."
Idempotent. Each pass must be safe to run any number of times with no extra effect once the goal is met. "Ensure 3 copies exist" run on a world that already has 3 copies does nothing — not "start 3 more." Idempotence is what lets the loop run every few seconds forever without piling up duplicates; it is the property that makes "reach this state" repeatable where "run this step" was not (§2). Level-triggered tells the loop what to compute (the gap from the current level); idempotent guarantees that computing it repeatedly is harmless. Together they mean: it is always safe to ask "are we there yet?" and act on the answer.
5 · Drive the loop yourself
The widget below is the engine in miniature. The slider sets desired replicas (the declaration). The "kill random Pods" button injects failure (node death, crashes — pick any number at once). Each tick the loop does one observe → diff → act pass: it counts the live Pods, compares to desired, and creates or removes to close the gap. Watch two things. First, it always converges — kill 1 or kill all 8, the next passes still recompute the gap and rebuild exactly to desired, because it is level-triggered and never relies on "catching" the deaths. Second, when actual already equals desired, a tick does nothing — that is idempotence. There is no command you can issue here; you can only change the desired number and perturb reality. Closing the gap is the loop's job, forever.
Notice what the loop never asks: "how did we get to 1 running?" It does not know and does not care. It reads the level, computes the gap, takes one step, and will look again next tick. That indifference to history is the whole source of its robustness.
Failure modes & checklist
Failure modes
- Thinking imperatively inside a declarative system. Manually
kubectl delete-ing a Pod the controller owns and expecting it to stay gone. The loop observes the gap and recreates it. Signal: "I keep deleting this Pod and it keeps coming back" — you fought the loop instead of editing the desired state. - Treating a one-shot script as reconciliation. A deploy script that runs once at release time has no loop behind it, so drift after it finishes goes unwatched. Signal: state is correct right after a deploy and silently wrong an hour later, with nothing reacting.
- Assuming edge-triggered delivery. Designing logic that fires only on a "Pod died" event and breaks when several die at once or an event is dropped. Signal: after a multi-node outage some replicas never come back, because the missing events were never re-derived.
- Non-idempotent actions. An "act" step that adds a copy instead of ensuring N exist, so repeated passes pile up duplicates. Signal: replica count climbs past desired over time with no scale-up requested.
Checklist
- Write the result, not the steps. Describe the desired end state ("3 healthy v2 copies on :8080"); never script the transitions.
- Make every action idempotent. "Ensure N exist," not "create one" — so re-running a pass on a correct world is a no-op.
- React to the level, not the event. Compute the gap from current observed state every pass; never depend on catching a transition.
- Assume the loop runs forever. If you want a different reality, change the declaration — do not issue a command and walk away.
- Expect self-healing, and let it. Don't hand-patch what the loop owns; fix the desired state and let convergence do the rest.
Checkpoint exercise
Where this points next
The loop is now in place: declare desired state, observe actual, diff, act, repeat forever, level-triggered and idempotent — and it self-heals. But it has been counting and creating a vague thing we keep calling "a copy" or "a Pod." That hand-wave is the new crack. To reliably create and compare "a running app," the loop needs a unit that is identical everywhere it lands, can't trample its neighbors' libraries, CPU, or files, and can be frozen and shipped so that what you reconcile is exactly what you tested. A bare OS process is none of those things — it inherits the host's libraries (dependency hell, "works on my machine"), shares its CPU and memory (the noisy neighbor), and leaves no reproducible artifact. So the very first thing the loop forces us to build is that unit. That is lesson 01, The Container: Boxing a Process.
Interview prompts
- Why doesn't a restart cron plus a runbook survive production? (§1 — it has no model of desired state; intent lives only in a human's head, so node death, scale, and rollout each open a gap between wanted and actual that nothing is continuously responsible for closing.)
- Contrast imperative and declarative with a concrete example. (§2 — imperative gives steps that assume a starting state ("start the server" — run twice, get a clash); declarative gives the result ("3 healthy copies on :8080") and is re-checkable and durable, so the system re-closes the gap whenever reality drifts.)
- Describe the control loop and name its phases. (§3 — observe actual state, diff against desired, act with the smallest gap-closing step (or nothing), repeat forever; a program running this loop over one kind of desired state is a controller, and Kubernetes is stored declarations plus a swarm of such controllers.)
- What does level-triggered mean and why does it self-heal? (§4 — it reacts to the current standing level ("desired 3, observed 1 → +2"), re-deriving the right action from reality on every pass, so missed or simultaneous events are irrelevant; edge-triggered reacts to transitions and is permanently wrong if an event is dropped.)
- Why must each reconciliation pass be idempotent? (§4 — the loop runs every few seconds forever; "ensure N exist" run on a correct world must be a no-op, otherwise repeated passes pile up duplicates. Idempotence is what makes "reach this state" safely repeatable where "run this step" was not.)
- You delete a Pod a controller owns and it comes back — bug or feature? (§5, Failure modes — feature; you changed actual state, not desired, so the level-triggered loop observes the gap and recreates it. To remove it for good, edit the desired state.)
- Why is the next forced primitive a container, not anything else? (Where this points next — the loop must reliably create and compare "a running app," but a bare process inherits host libraries, shares CPU/memory, and leaves no reproducible artifact; the loop needs a portable, isolated, frozen unit, which is the container.)