§ 01 Year 3: the same 3 a.m.
Cast
- Sam
- Founding engineer. On call for everything until this year.
- Jordan
- Co-founder, now managing the engineers: that’s you, in this story.
- Ari
- Platform lead, hired this year to move Fennel onto Kubernetes.
I didn’t read the last chapter. What happened?
- In Chapter 1, Fennel ran on one server that Sam had built by hand. A full disk took the site down for two hours, and rebuilding the server from Sam’s notes failed.
- In Chapter 2, Fennel started packing each piece of its software, with everything it needs to run, into an image. A running copy of an image is a container.
- In Chapter 3, a year later, Fennel’s app was four services (
storefront,orders,paymentsanddispatch) running as containers on four machines, with Sam’s spreadsheet saying what ran where. One Saturday at 03:04, machine 03 died withordersand adispatchcopy on it. Customers couldn’t order until Sam, woken by a page, started both on other machines and toldstorefrontwhereordershad gone. - Software that does that kind of work all the time is an orchestrator. Kubernetes is one.
A year ago, at 03:04, machine 03 died with orders and a dispatch copy on it. Which jobs did Sam have to do before customers could order again?
It’s now Year 3, and Fennel runs on managed Kubernetes from its cloud provider. The provider runs Kubernetes’ own machinery, while Fennel’s services run on four machines, numbered 01 to 04 as before. Kubernetes calls each machine in a cluster a node. Ari, the platform lead Fennel hired this year, led the move.
One Saturday at 03:04, machine 03 dies again. Kubernetes knows it as node 03 now, and just as a year ago, it was running orders and a dispatch copy. This time nobody’s phone rings. Sam sleeps through it and reads about it over breakfast.
On Monday, Jordan asks Ari how Kubernetes knew what to do.
“Nobody told it what to do. We told it what we want.”
Ari
The rest of this chapter explains what Ari means, and what that sentence doesn’t promise.
§ 02 Say what you want, not what to do
When a machine dies, there are two ways to write down how Fennel should recover. Fig. 01 compares both for the same incident. On the left is the step-by-step task list Sam worked through after machine 03 died last year. On the right is a goal that says how many copies of each service must keep running.
says what to do, step by step
Sam’s task list · after 03:04, year 2
- Start
orderson machine 04. - Start the lost
dispatchcopy on machine 02. - Change
storefront’s settings:ordersis on 04.
says what should be running
Fennel’s goal · written for the cluster
- storefront
- 3 copies
- orders
- 1 copy
- payments
- 2 copies
- dispatch
- 3 copies
This time it’s node 04 that dies at 3 a.m., not node 03. Which of the two documents in Fig. 01 still says the right thing to do, unchanged?
Sam’s list spells out what to do, step by step, and engineers call that style imperative. Fennel’s goal describes what should be running and leaves the steps to the system, which makes it declarative. What a declarative goal describes is called the cluster’s desired state.
New term · 01 / 04
desired state
A written description of what you want running, such as “storefront: 3 copies”. Kubernetes keeps working to make the cluster match it.
declarative: say what you want · imperative: say what to do
You met it in Fig. 01: Fennel’s goal.
The difference matters most on a night nobody planned for. A task list is only right for the failure its author imagined. A goal doesn’t mention machines, so it stays true whichever one fails. It still shows what’s missing, and whatever acts on the goal can work out the steps for itself.
ANALOGYYou know this from managing people. Suppose you give a capable owner a goal, such as “every order out by 6 p.m.”, instead of a task list like “call the supplier, then rebook the van”. The owner keeps an eye on that one number, notices when it slips, and fixes it without being told how.
WHERE THIS ANALOGY BREAKSThe owners inside Kubernetes have no judgement. They don’t ask why copies keep dying, and they don’t push back when a goal can’t be met: if there’s no room anywhere, a new copy simply waits. They never escalate, and they will restart a broken app forever. They also act only on the latest goal, ignoring any history of earlier instructions. In a person, all of that would be a problem. In software, it means you always know what it will do next, which is to close the gap between the goal and what’s running.
What if the goal changes twice in quick succession?
Say someone changes storefront’s goal from 2 copies to 5 and then, a moment later, to 3. If the loop hasn’t looked in between, it never acts on 5 at all, because it compares the latest goal with what’s running and heads straight for 3. If it had already started heading for 5, it simply turns around. Either way it ends at 3, since there’s no queue of instructions to work through. Engineers call this behaviour “level-triggered”.
Fennel writes its goals in files called manifests. Fig. 02 shows a shortened version of the manifest for storefront. Its line replicas: 3 is the desired state: three copies. Teams usually keep manifests in version control, so that a change to a goal can be reviewed before it goes in, and leaves a history behind.
kind: ReplicaSetthe kind of record: a set of identical copies
metadata: name: storefrontspec: replicas: 3the desired state: three copies
template:what each copy is
# … labels and other # details left out spec: containers: - name: storefront image: storefront:v38the image to run (Chapter 2)
Chapter 3 ended on a question: what if the record were the thing the machines act on, instead of a note about them? With Kubernetes, that’s how it works. The cluster keeps its own record of what’s wanted and what’s running, and every part of Kubernetes works from that record.
§ 03 Pods: what Kubernetes counts
There’s one more word to learn before Ari applies the storefront manifest. Kubernetes doesn’t count or place containers directly. Instead, it wraps each copy’s container in a Pod, and Pods are what Kubernetes counts, places and replaces. Most Pods hold just one container, which is why people often use “Pod” and “container” interchangeably in conversation. Sometimes a small helper container runs alongside it in the same Pod.
Pod1 address
one container: the usual case
Pod1 address, shared by both
sometimes a small helper runs alongside
New term · 02 / 04
Pod
A wrapper that Kubernetes puts around one container, or around a few that must run together. Kubernetes counts, places and replaces Pods rather than containers.
Chapter 2’s container, wrapped. The name comes from a pod of whales, or a pea pod.
§ 04 Moving day: what “apply” does
Fennel didn’t move to Kubernetes all at once. Ari’s team moved its services onto the cluster one at a time, and by the morning of moving day, orders, payments and dispatch were already running there. Moving day is the day the last service, storefront, moves too. Ari has written its manifest, the one in Fig. 02, and all that’s left is to hand it to Kubernetes.
Two parts of Kubernetes matter for what happens next. The first is the cluster’s record. It holds every goal Fennel has declared, such as “orders: 1 copy”, together with what Kubernetes knows is actually there: which Pods exist, and which node each one is on. The second is the API server, the program through which everything reads and changes that record. When Ari applies a manifest, the command sends it to the API server. The loops read the record through the API server and write their results back the same way. And on every node, a small program called the node agent regularly reports to the API server what is running on its node, so the record stays up to date.
Kubernetes was already busy before Ari applied anything. Several loops, each a small program that looks after one kind of thing, run all the time, and each one repeats the same three steps: it observes part of the cluster, compares what it sees with what the record says is wanted, and acts if the two differ. On the morning of moving day, orders, payments and dispatch were running exactly as declared, so every comparison came out even and the loops had nothing to do. Lane A shows what changes when storefront arrives.
-
PREDICTCheck 03 / 09
Ari applies the
storefrontmanifest, and Kubernetes answerscreatedin under a second. At that moment, what is new in the cluster? -
Answer the question above to see what happens
-
Answer the question above to see what happens
-
Answer the question above to see what happens
-
Answer the question above to see what happens
Every loop in lane A followed the three steps described at the start of this section: observe, compare with what’s wanted, act to close any gap, and then start again. That pattern is called a reconcile loop, and it isn’t special to moving day, because almost everything Kubernetes does is built from it. A controller is one implementation of the pattern: a part of Kubernetes that runs a reconcile loop for one kind of thing. The ReplicaSet loop, for example, is the controller for ReplicaSets. Engineers call one pass of the loop “reconciling”. The scheduler and the node agent work the same way, though engineers don’t usually call them controllers.
New term · 03 / 04
reconcile loop · controller
A loop that compares what the record says is wanted with what it observes, acts to close the gap, and then repeats, forever. A controller runs one such loop for one kind of thing.
the loop lens: which loop · wants · sees · does
You watched three of them on moving day.
The pattern is the same for every loop, but each loop fills it in differently, and filling it in is the quickest way to understand any one of them. Ask four questions: which loop is it, what does it want, what does it see, and what does it do about a gap? Together, these questions make up the loop lens. At the fourth step of lane A, for example, the loop was the scheduler. It wants every Pod to have a node; it saw three Pods with none; and it wrote a node into each one. Each new part of Kubernetes in the chapters ahead is another loop, so the loop lens will explain those too.
ANALOGYThe name fits. Kubernetes is Greek for helmsman, or pilot. A helmsman doesn’t plan for every wave; they hold a heading and correct the wheel whenever the ship drifts off it. Engineers often shorten the name to K8s, which is a K, then the number of letters in between, then an s.
WHERE THIS ANALOGY BREAKSA ship has one helmsman, but a cluster has many loops, each holding its own small heading, and no captain gives them orders. A helmsman also reads the weather, whereas a loop only compares what’s wanted with what’s there.
§ 05 Saturday, 03:04, again
-
PREDICTCheck 04 / 09
Node 03 has just stopped, and nobody will be woken. With Kubernetes’ default settings, how long until customers can place orders again?
-
Answer the question above to see what happens
-
Answer the question above to see what happens
-
Answer the question above to see what happens
-
Answer the question above to see what happens
-
Answer the question above to see what happens
Chapter 1 left Jordan with a question to ask about any machine Fennel depends on:
“If this box vanished tonight, how long until we’re back, and who has to be awake?”
Chapter 1
In Year 3, the answer is roughly six or seven minutes with default settings, sometimes less, and nobody has to be awake.
Those minutes still cost something. Fennel runs orders as a single copy, so it was down the whole time: when its one Pod stopped, there was no other copy to take orders while the loops replaced it. A service running as one copy is what Chapter 1 called a single point of failure. The loops restore what’s been declared, but they can’t prevent the gap while they do it. That makes this a question worth asking Ari: “Which of our services run as a single copy?”
Can it be faster than six minutes?
It can be. In a cloud, Kubernetes asks the provider whether a silent machine still exists, and if the provider says it’s gone, its Pods are cleaned up without waiting the full five minutes. Engineers can also shorten or lengthen the five-minute wait for each app. The numbers in Fennel’s story are the defaults. In every case, the old Pod records stay around, marked for deletion, until Kubernetes knows the machine is gone, but the ReplicaSet loop stops counting them as soon as they’re marked. The wait exists because at first a network glitch looks the same as a dead machine, and replacing Pods too eagerly could leave two copies running where only one was wanted.
What about machine 03 itself?
Kubernetes replaced the Pods, but not the machine. Until a machine is added back, the cluster has three nodes and less spare room. That room is the subject of Chapter 5.
Fig. 04 looks at each loop you’ve watched through the loop lens, and shows which of Sam’s jobs it took over.
| Loop | Wants | Sees | Does | Sam’s job, now a loop’s |
|---|---|---|---|---|
| Updating and scaling are handled by loops too, in Chapters 6 and 8. | ||||
Jordan asks Ari: “When node 03 died, what worked out that orders should go to node 04?” Which answer is accurate?
No part of Kubernetes made a plan for 03:04. The node loop didn’t know what a ReplicaSet was, the ReplicaSet loop didn’t know why its Pods had gone, and the scheduler didn’t know there had been a failure. Each one read the record, did its one job and wrote the result back, and the next loop picked up the work from there. That’s how the work gets split up without a single conductor.
Chapter 3 called Kubernetes an orchestrator because of the jobs it does, and that’s still true. Kubernetes’ own documentation, though, warns about what the word suggests. It describes orchestration as following a fixed script, “first do A, then B, then C”, and says that Kubernetes instead “comprises a set of independent, composable control processes”. It adds: “Centralized control is also not required.” So Chapter 3 used the word for what gets done, while the documentation is talking about how it gets done.
There’s a practical reason for building it this way. The same documentation prefers “simple controllers rather than one, monolithic set of control loops”, because controllers can fail. When the loops are separate, one of them can fail without stopping the rest.
But engineers talk about “the controller manager”
They do. Kubernetes’ built-in controllers are shipped together as one program, which keeps them simple to run. Inside that program, each controller is still a separate loop watching its own kind of thing, much like separate teams sharing one office building. The scheduler and the node agents run as separate programs.
Nothing at 03:04 needed a release, or anyone applying anything, because the loops had been running all along.
§ 06 Replaced, not repaired
Since 03:10, an orders Pod has been running on node 04. Is it the Pod that was on node 03?
Ari’s dashboard shows the difference. On Friday it listed orders-5c8x on node 03, and since 03:10 it has listed orders-q2rw on node 04. Kubernetes never moves a Pod. It replaces it with a new one, made from the same template, which has a new name and a new address and keeps nothing from inside the old one.
This is why Kubernetes treats Pods as disposable. People rarely create Pods by hand; a controller makes them, and each one lasts until it finishes its work, is removed, or loses its node. The new address is also the reason storefront needed another loop, Chapter 7’s, to find the new orders Pod.
What “disposable” asks of the app
Engineers will recognise this idea from the twelve-factor app, a widely used checklist for building software, which calls it “disposability”: an app’s processes “can be started or stopped at a moment’s notice”. For a service running on Kubernetes, that comes down to four things:
- Keep nothing important inside the Pod. Fennel’s orders live in its database, outside the app, as Chapter 2 explained; where that database should run is the subject of Chapter 10.
- Cope with being stopped or replaced at any moment.
- Start in seconds rather than minutes, because a slow start lengthens the gap every time a Pod is replaced.
- Finish any work in progress when asked to stop. By default, Kubernetes gives a Pod 30 seconds to do that.
Kubernetes can’t do these for you; they’re up to the app. They’re the questions to ask whenever one of your services moves onto the cluster.
Self-healing, honestly
You’ll hear what happened at 03:04 called self-healing. After last year’s 3 a.m. pages, it’s tempting to hear that as “Kubernetes makes our app reliable”. What the loops actually do is restore the declared state: they restart containers that crash, and they replace the Pods of nodes that die. There are several things they can’t do:
- Fix a bug. An app that crashes as it starts is restarted, crashes again, and is restarted again, forever. §08 shows an example.
- Find room that isn’t there. If no node has room for a new Pod, the Pod waits, unplaced, until room appears. Chapter 5 gives this state a name.
- Bring back what was inside a Pod. Anything written inside a Pod is lost with it; keeping data safe is the subject of Chapter 10.
- Keep a single copy up. As at 03:04, a service with only one copy is down while that copy is replaced.
In other words, Kubernetes restores the number of copies that are running. Whether the app itself is healthy is still your engineers’ responsibility.
§ 07 Who runs what
You’ve now met every part of a cluster that a manager needs to know by name. Fig. 05 brings them together and shows who runs each part.
Control plane · run by your provider, in every mode
controllers
↻ReplicaSet loop ↻node loopThe cluster’s record
what’s wanted
what’s reported
Pods
Nodes · yours in standard mode; the provider’s in fully managed modes
Always yours
- what goes into the record
- your images and code
- your data
- who may change what
- watching alerts
You met the API server in §04. Everything that reads or writes the record goes through it, whether that’s a person applying a manifest, one of the loops, or a node agent reporting in. The node agent on each node is what engineers call the kubelet; it starts whatever has been assigned to its node and reports back. Together, the API server, the record behind it, and the loops that work on that record, apart from the node agents, make up the control plane.
New term · 04 / 04
control plane
The API server, the cluster’s record behind it, and the loops that work on it. On a managed service, your provider runs it.
You met each part in §04 and §05.
On a managed service, the provider runs the whole control plane in every mode, and carries out its upgrades to new Kubernetes versions. Deciding when to upgrade, and checking that Fennel’s apps still work on the new version, is still partly Fennel’s job; that work is the subject of Chapter 14.
Who looks after the nodes depends on the mode. In each big provider’s standard mode, the customer does, which at Fennel means Ari’s team. In the fully managed modes, the provider does. Either way, Fennel pays for the computing its Pods run on, which in most modes means paying for the machines themselves. Some things are always Fennel’s responsibility: what goes into the record, its images and code, its data, who may change what, and watching and responding to alerts.
Without a managed service, Fennel’s own team would also have to run the control plane, including keeping the record safe.
What is the cluster’s record, exactly?
Engineers often call it etcd, after the software usually used to store it, although some providers store it differently. Only the API server reads and writes it directly; every loop goes through the API server.
What if the control plane itself goes down?
Pods that are already running keep running, because each part carries on with what it was last told. But no Pod is replaced or placed until the control plane is back. What providers promise about this is covered in Chapter 14.
Fennel’s cluster runs on a managed service, in the provider’s standard mode. Which of these is still Fennel’s job?
§ 08 The conversation
Fennel’s newest service, notifications, sends customers texts and emails about their orders. If it runs late for a while, that’s annoying but not a disaster. On Wednesday, a setting it needs goes missing.
Every time a notifications container starts, it crashes within seconds, because a setting it needs is missing. With default settings, what does Kubernetes do over the next hour?
On engineers’ dashboards, this state shows up as CrashLoopBackOff. An example in the Kubernetes documentation shows a Pod in this state that has restarted 200 times over two days and nine hours.
Ari says: “notifications keeps crash-looping, but Kubernetes keeps restarting it.” What would you ask? Think of one or two questions, then show the answer.
ANSWER“So Kubernetes is doing its job. What’s wrong with the app? And is restarting hiding the problem from us?”
The loop checks one thing, whether the container is running, and acts on that. It can’t see the missing setting, so restarting is all it can do. Meanwhile, a service that restarts quietly can look “handled” on a dashboard while texts and emails stop going out. Chapter 12 comes back to how teams catch that.
If your question was “can Kubernetes fix it?”, that’s exactly the trap this chapter warns about: the loops restore the number of copies running, but they don’t repair the app.
In short
- With Kubernetes, you write down what you want, the desired state, in manifests. Applying a manifest records that intention; it doesn’t run anything itself.
- Many small loops each compare one part of the record with what they observe, and act to close any gap, over and over. None of them plans for the others; work passes between them through the record.
- Kubernetes runs Pods, and Pods are disposable. When a node dies, its Pods are replaced by new ones within minutes with default settings, and nobody has to be awake. The app has to cope with that, and the loops can’t fix an app that is broken.
- Your provider runs the control plane. What goes into the record, and whether your apps survive being replaced, stays your responsibility.
From here on, whenever Kubernetes does something, the loop lens is the first thing to reach for: which loop is acting, what does it want, what does it see, and what does it do about the gap? In the next chapter, Morgan, the co-founder who runs Fennel’s money, opens the first cloud bill since the move and finds it has gone up. That story starts with the loop that picked node 04 for orders.