This whole phase has been building to one habit, and the habit is awkward because the tooling fights it.
An event is an object
kubectl get events shows you a stream, so it feels like a stream. It isn’t.
Each line is an EventEventAn object recording that something happened, addressed to nobody and deleted within the hour., and an Event is really just an object like
every other object in this phase: it has a kind, it has a namespace, it was written through
the API server, and it’s sitting in etcd right next to your Deployment.
kind: Event
metadata:
namespace: shop
reason: FailedScheduling
message: "0/3 nodes are available: 3 Insufficient cpu"
involvedObject:
kind: Pod
name: report-nightly-29598317-m4xqt
count: 847
firstTimestamp: "2026-04-11T09:17:03Z"
lastTimestamp: "2026-04-11T09:31:44Z"
type: Warning
Three fields there are worth more than the rest.
count is 847 and there’s one object. The API server folds repeats of the same
thing about the same object into one record and bumps a counter, so a big number
just means something has been retrying for a while, not that 847 things happened
to you.
Both timestamps describe the past. There’s no field on an Event saying what’s true now, because that isn’t what it’s for.
And one field doesn’t appear at all: nothing says who should handle it. Events are addressed to nobody.
They also expire
Events are kept for about an hour by default, and then deleted. Not archived, deleted.
So the feed is a window roughly one hour deep onto a system that’s been running for months. A problem that started this morning is invisible in it, while the object that problem is happening to says exactly what’s wrong, in full, with no expiry at all.
That’s the first reason to distrust the feed, and it’s the one that catches experienced people, because an empty feed reads as good news.
Edge and level
There are two ways to build something that reacts to a system.
An edge-triggered thing gets told when something changes, and responds to the change. The word comes from electronics: you act on the moment a signal goes from low to high.
A level-triggeredLevel-triggeredActing on how things currently are, rather than on being told that they changed. thing looks at how things are right now and acts on that. Nobody has to tell it anything, because the current level is the input.
The whole of Kubernetes is the second one. Run through what an edge-triggered version would have to survive:
| What happens | Costs an edge-triggered system | Costs a level-triggered one |
|---|---|---|
| A notification is lost | The change never happens | Nothing. The next look sees the same gap |
| A notification arrives twice | The change happens twice | Nothing. The second look finds no gap |
| Two arrive out of order | The system ends up in the older state | Nothing. Only the current state was ever read |
| The listener restarts | Everything sent while it was down is gone | Nothing. It reads everything on the way up |
The right-hand column is empty because a level-triggered component never asked to be told. A controller gets handed the name of an object, goes and reads it, and compares. Feed it the same name a thousand times and you get one outcome. Never feed it at all and the periodic sweep gets there anyway.
That’s the property the whole system is built on, and events are what it looks like when you don’t have it.
So who reads the events?
Nothing does.
No controller subscribes to them. The scheduler doesn’t consume them. The kubelet doesn’t act on them. They get written, they get read by humans and by dashboards, and they get deleted.
They’re basically a diary. Components write into it as a courtesy so a person can reconstruct what happened, and the system would work identically if the diary was switched off. If you’ve ever seen an event about a pod being scheduled and assumed that the event is how the node found out, this is the correction: the node found out by reading the pod.
Reading a feed
Knowing that isn’t the same as doing it.
Five feeds, five situations. Each one has a second command available that tells you what is actually true, and you never have to run it.
What to read instead
If the feed isn’t the state, the state is on the object. Two places, and they aren’t equivalent.
Status is the report a controller writes about what it currently sees.
NAME READY UP-TO-DATE AVAILABLE
web 2/3 1 2
Conditions are the part worth learning to read, because they carry the reason as well as the state:
status:
conditions:
- type: Available
status: "True"
reason: MinimumReplicasAvailable
- type: Progressing
status: "False"
reason: ProgressDeadlineExceeded
lastTransitionTime: "2026-04-11T09:03:12Z"
A conditionConditionA claim about an object right now, with a reason and the time it last changed. is a claim about the object right now, with a machine name for the reason and a timestamp for when it last flipped. Available true and Progressing false is a precise sentence: it’s serving, it’s been trying to change to something else for half an hour, and it’s given up. No event says that. Several events hint at it.
Conditions don’t expire, they don’t accumulate, and there’s just a small fixed
set of them per kind. kubectl describe prints the conditions and the recent events
together, which is a convenience and also, if you read it top to bottom, the
correct order of operations.
The gap is normal
Because everything works by looking and closing gaps, there’s always a moment between something breaking and something noticing. During that moment the system is wrong and converging at the same time.
That window is the price of the design that makes the rest of it survivable, and it’s what eventual consistencyEventual consistencyThe system is not always correct, but something is always working on it. means in practice here. So the useful question during an incident is rarely “is it correct”. It’s:
- Is the gap closing?
- Is it closing fast enough?
- Is anything trying, or has something given up and said so in a condition?
A Deployment at 2 of 3 might be thirty seconds into a rollout or thirty minutes into a broken one, and those look identical in a count. The condition tells them apart.
The rule
Never build anything that acts on an event.
That goes for scripts, operators, and alerts that fire on a single line in the feed too. Anything acting on the arrival of an event inherits every row of that table.
Alert on conditions and on status, which describe now and which don’t expire. Use the feed for what it’s actually good for, which is answering “what on earth happened here” after the fact, within the hour, if you’re quick.
And when you’re the one at the terminal, you’re a controller too. Look at what’s wanted, look at what’s there, act on the difference, then look again.
This is the end of phase 2 of this course, summed up
An object is what you want, the API server is the only way to say it, etcd is the only thing that remembers it, a controller is a loop that reads it, and an event is a note about something that has already happened.
Next comes the other half: what actually runs, starting with the thing every one of these lessons has been quietly assuming.