Cluster Architecture: What Actually Runs a Pod
The control plane decides, the nodes execute. Learn who talks to whom, why the API server is the only component touching etcd, and where Docker fits.
Start With the Shape
Kubernetes has a reputation for being enormous. It is — but the architecture underneath is small enough to hold in your head, and almost everything confusing later becomes obvious once you know which component is responsible for what.
There are two halves: a control plane that decides what should happen, and nodes that actually run containers.
A terminology note worth absorbing early: the project says control plane and node. The old "master" wording persists in a great deal of older material, and using the current terms is a small but real signal that you've learned from current sources.
The Control Plane
kube-apiserver is the front door and the centre of everything. Every other component — including the ones inside the control plane — talks to the cluster through it. It validates requests, applies admission control, and persists the result.
The detail that matters most: the API server is the only component that reads or writes etcd. The scheduler doesn't. Controllers don't. The kubelet doesn't. That single chokepoint is why authentication, authorization and admission control can be enforced in one place, and it's why "can I do X?" in Kubernetes is always a question about the API.
etcd is a distributed key-value store holding all cluster state — every object, its spec, and its status. It is the source of truth. Lose etcd without a backup and you have lost the cluster's definition of itself, which is why etcd backups are the one operational task nobody should skip.
kube-scheduler watches for pods that have no node assigned, and decides which node each should run on. Then it writes that decision back to the API server. Note what it does not do — it never contacts a node. It only records a choice.
kube-controller-manager runs the control loops: the Deployment controller, the ReplicaSet controller, the node controller, and many more. Each watches the API for a difference between desired and actual state, and acts to close the gap. This is the engine of the whole system, and the next guide is devoted to it.
The Node
kubelet is the agent on every node. It watches the API server for pods assigned to its node, and makes them real — pulling images, starting containers, mounting volumes, running probes, and reporting status back. The kubelet is the only component that talks to the container runtime.
kube-proxy implements Service networking, programming the node's packet-handling rules so that traffic to a Service's virtual IP reaches a healthy pod. Modern clusters may run it in nftables mode, and some replace it entirely with an eBPF-based dataplane such as Cilium.
The container runtime actually runs containers. Kubernetes talks to it through the Container Runtime Interface (CRI) — a stable API that any compliant runtime can implement. In practice that means containerd or CRI-O.
Check yourself
A pod is stuck in Pending. Which component's behaviour should you reason about first?
Where Docker Fits (and Doesn't)
This confuses almost everyone at some point, so let's settle it.
Kubernetes talks to runtimes through the CRI. Docker Engine never implemented the CRI — it needed an adapter called dockershim, which Kubernetes maintained and then removed in v1.24. Since then, nodes run containerd or CRI-O.
What that does not mean is that your Docker-built images stopped working. Images built with docker build follow the OCI image specification, and containerd runs OCI images natively. Docker remains an excellent tool for building images; it simply isn't what runs them in a cluster.
Version housekeeping that matters if you operate clusters: Kubernetes 1.35 is the last release supporting containerd 1.x. Clusters must move to containerd 2.0 or later before upgrading beyond it. This is the kind of dependency that turns a routine upgrade into an incident when nobody checked.
Following a Request End to End
Tie it together by tracing kubectl apply -f deployment.yaml:
- kubectl → API server. The manifest is sent over HTTPS. The API server authenticates you, checks RBAC, runs admission control, validates the object, and writes it to etcd. At this moment your Deployment exists — and nothing is running.
- Deployment controller notices a Deployment with no matching ReplicaSet and creates one.
- ReplicaSet controller notices a ReplicaSet wanting 3 replicas and 0 existing, and creates 3 Pod objects. These have no node assigned. Status: Pending.
- Scheduler notices unscheduled pods, filters nodes that can't host them, scores the rest, and writes its chosen node onto each pod.
- kubelet on each chosen node notices a pod assigned to it, pulls the image via the CRI, starts the containers, and reports status.
- kube-proxy programs routing rules once the pods are ready and appear in the Service's endpoints.
Six steps, six independent components, none of them calling each other directly. Every interaction went through the API server.
That last point is the most useful thing in this guide. Components don't call each other — they watch the API and react. It's why kubectl get events is so informative (every actor records what it did there), and why a failure usually shows up as "nothing happened" rather than an error propagating back to you.
Check yourself
Why does Kubernetes route every component's interaction through the API server rather than letting the scheduler write directly to etcd?
What's Next
You know the components. The next guide covers the idea that ties them together — the reconciliation loop — and why a manifest can be flawless and still never produce a running pod.