How Kubernetes Works
What this module is for
Section titled “What this module is for”Every earlier module in this course taught you to use Kubernetes: writing manifests, applying them with kubectl, reading back status, scaling, scheduling, securing, storing, networking, and shipping with Helm/GitOps. None of it required knowing what the API server actually does with a request between “you typed kubectl apply” and “the object exists,” how a controller actually finds out something changed, what a kube-scheduler plugin actually is under the extension-point names you have already seen, what order admission webhooks and ValidatingAdmissionPolicy actually run in, or what managedFields really tracks when two tools apply the same object.
That gap is exactly where AI-generated Kubernetes tooling fails in ways that look fine on the surface: a controller written with List in a for-loop instead of a shared informer, silently polling and missing events between polls; a reconcile function with no rate limiting that hot-loops on a permanent error; a webhook assumed to run “before validation” when the real chain interleaves mutating and validating differently; a script that “fixes” a managedFields conflict by force-taking every field instead of reading which one is actually contested. None of these are syntax errors. None of them are caught by kubectl apply --dry-run=client. All of them are facts about the runtime this whole module makes provable.
This module has one job: give you five pieces of runtime knowledge that let you look at someone else’s Kubernetes code or YAML — a controller, a webhook config, a scheduler profile, a script full of kubectl apply --server-side calls — and answer:
- What actually happens to a request inside the API server, and how does a watch really work? (the API server and etcd —
resourceVersion, watch, bookmarks,410 Gone, and etcd as the one place state lives) - How does a controller actually learn “something changed,” without polling? (controllers, informers, and workqueues — Reflector → DeltaFIFO → Indexer/Lister → workqueue → reconcile, straight from
client-go) - What is a scheduler “plugin,” concretely, and in what order do they run? (the scheduler framework — the real extension-point interfaces and the real plugin names from
pkg/scheduler) - What order do mutating webhooks, schema validation, validating webhooks, and
ValidatingAdmissionPolicyactually run in? (the admission chain — proven with a real chain, a real dry-run, and the exact source that decides the order) - What does
managedFieldsactually track, and what does “conflict” really mean? (Server-Side Apply internals — thef:/k:/v:markers,x-kubernetes-list-type, and thestructured-merge-difflibrary that makes it work)
Every claim in this module is backed by one of three things: the real Kubernetes source at github.com/kubernetes/kubernetes, tag v1.37.0 (this course’s pinned version), cited by file and line; the official docs at kubernetes.io/docs; or a real, reproduced run against a live 3-node kind cluster (kind create cluster --name internals, 1 control-plane + 2 workers, Kubernetes 1.37.0) — a real kubectl -v=8 trace, a real etcdctl get, a real scheduler log, a real API-server error — pasted below the claim, never a guess about internals.
What this module covers
Section titled “What this module covers”| Lesson | Answers the question |
|---|---|
| API Server and etcd Watch | What is the real, provable request path through the API server, and how does a watch actually stay consistent with resourceVersion? |
| Controllers, Informers, and Workqueues | What does a controller actually do, mechanically, from the moment a watch event arrives to the moment it reconciles? |
| Scheduler Framework | What are the real extension points and plugins the scheduler runs, and how do you change which ones are active? |
| Admission Chain | In what real, source-proven order do mutating webhooks, validation, validating webhooks, and VAP/MAP actually run? |
| Server-Side Apply Internals | What does managedFields actually record, and what decides whether two managers conflict? |
The Foundations module already introduced kubectl apply --server-side and a real field-manager conflict error from the outside. This module goes past that framing into the actual request path, the actual watch, the actual scheduler plugins, the actual admission order, and the actual merge algorithm.
flowchart TD kubectl["kubectl apply -f pod.yaml"] --> authn["Authentication"] authn --> authz["Authorization (RBAC)"] authz --> mutate["Mutating admission chain\n(in-tree -> MutatingAdmissionPolicy -> MutatingAdmissionWebhook)\n(Admission Chain)"] mutate --> schema["Schema + strategy validation\n(OpenAPI, x-kubernetes-list-type)\n(Server-Side Apply Internals)"] schema --> validate["Validating admission chain\n(in-tree -> ValidatingAdmissionPolicy -> ValidatingAdmissionWebhook)\n(Admission Chain)"] validate --> etcd["etcd: the one place state lives\n(API Server and etcd Watch)"] etcd --> watch["Watch stream, resourceVersion-ordered"] watch --> reflector["client-go Reflector -> DeltaFIFO -> Indexer/Lister\n(Controllers, Informers, and Workqueues)"] reflector --> queue["workqueue (rate-limited, level-triggered)"] queue --> reconcile["Controller reconcile\n(e.g. ReplicaSet controller creates a Pod)"] reconcile --> sched["kube-scheduler: Filter -> Score -> Bind\n(Scheduler Framework)"] sched --> kubelet["kubelet starts the container"]
Mental Model
Section titled “Mental Model”Everything in this module is one story told from five different points on the same loop: you send a mutation, the API server runs it through a fixed pipeline (auth, admission, validation, storage) and writes exactly one copy of the truth to etcd; every other component — a controller, the scheduler, kubectl get -w — finds out about that write the same way, by watching a resourceVersion-ordered stream from that one API server, never by polling, never by talking to etcd directly. A controller is nothing more than “watch, queue, reconcile” wired through client-go’s informer machinery; the scheduler is nothing more than one more consumer of that same watch stream, running a pipeline of named plugins before it writes its own decision back through the same API server; admission and Server-Side Apply are both just extra structure the API server imposes on the write path itself, before anything reaches etcd. Once you can place any given fact in this module against that one loop, “how does Kubernetes actually work” stops being five separate things to memorize.
Common Misconceptions
Section titled “Common Misconceptions”- “A controller polls the API server on an interval to check for changes.” Controllers, Informers, and Workqueues proves the opposite with a real, running informer: it lists once, then holds a long-lived HTTP watch connection and reacts to individual
ADDED/MODIFIED/DELETEDevents — the periodic fullListyou may have heard of is a much longer resync, a safety net, not the primary mechanism. - “Admission webhooks all run in one undifferentiated pool, in registration order.” Admission Chain proves the real order straight from
pkg/kubeapiserver/options/plugins.go: mutating admission (in-tree plugins, thenMutatingAdmissionPolicy, thenMutatingAdmissionWebhook) all completes, then schema/strategy validation runs, then validating admission (in-tree,ValidatingAdmissionPolicy,ValidatingAdmissionWebhook) runs — two genuinely separate phases, not one pool. - “The scheduler is a black box that just picks a node.” Scheduler Framework proves it is a pipeline of separately named, separately swappable plugins — real names straight from
pkg/scheduler/framework/plugins/names, at fixed extension points defined ink8s.io/kube-scheduler/framework. - “
managedFieldsis basically the same idea as the oldlast-applied-configurationannotation, just server-side.” Server-Side Apply Internals proves they are structurally different:managedFieldsis a per-manager, per-field ownership map keyed by real OpenAPI schema markers (x-kubernetes-list-type: mapvsatomic), computed by thestructured-merge-difflibrary — not a single stashed blob of “what I applied last time.”
Reading AI-generated code
Section titled “Reading AI-generated code”The whole point of this module is this section, five times over — one per lesson. As a preview, here is the survey-level version: a snippet that looks like a correct controller and is missing the one thing that makes it a controller rather than a poller.
🐛 Spot the bug
// A "controller" a teammate wrote to react to Pod changessetInterval(async () => {const pods = await api.listNamespacedPod('default');for (const pod of pods.body.items) { reconcile(pod);}}, 5000);Show the answer
This runs reconcile on every Pod every 5 seconds, forever, whether anything changed or not — proven wrong by Controllers, Informers, and Workqueues: a real controller uses a watch, delivered as individual ADDED/MODIFIED/DELETED events through a Reflector into a DeltaFIFO, and reconciles only the object whose key was actually enqueued. This setInterval version reconciles every object on every tick regardless of whether it changed (wasted API server load that scales with cluster size, not with the change rate), and — worse — it can miss a change entirely if an object is created and deleted again inside one 5-second window, since nothing about polling guarantees you see every intermediate state the way an ordered watch stream does.