SevyDevy

Guide · Backend

Kubernetes Architecture Explained: Control Plane, Nodes and Pods

26 min readUpdated By Saket Bhatnagar

Researched and drafted with AI, reviewed and edited by Saket Bhatnagar.

In short

  • Kubernetes architecture splits a cluster, the group of machines Kubernetes manages, into two halves. A control plane decides what should run where, and worker nodes, the machines, run your app.
  • A pod is the smallest thing Kubernetes runs: one or more containers (a program packed with everything it needs) that share one network address on one machine.
  • The control plane has four core parts. The API server is the only way in, etcd is the database that remembers everything, the scheduler picks a machine for each pod, and controllers keep correcting the cluster.
  • You rarely start pods by hand. You write down the desired state, meaning what you want running, and control loops keep fixing the cluster until it matches.
  • Beginners can stop after the deploy walkthrough. Engineers get the v1.37 failure timeline after it: about 50 seconds to notice a node that stops reporting, 5 minutes more before replacements, and when Kubernetes evicts nothing.

What goes wrong when you run containers on servers by hand?

Every release, crash and dead machine needs a person to log in and fix it. The servers are usually fine. The process around them does not scale. That process is what Kubernetes architecture was built to replace.

Picture a common setup. An audiobook app has a player API that streams audio, plus catalogue, search and payments services. About 1.8 lakh (180,000) people listen on a normal day. The setup is made up, but the problems are real.

The four services run on six rented virtual machines, each a full computer in the cloud. Each service is packed as a container: the program bundled with its libraries and settings, so it runs the same on any machine. The file you start a container from is called an image.

Containers made the code portable. Running them on six machines was still manual, and one quarter brought four kinds of pain.

  • Releases took an hour. An engineer copied the new image to each machine and restarted the services one by one, in an order kept in a text file.
  • Crashes waited for a human. When the player API ran out of memory at 2 a.m., the on-call engineer's phone rang.
  • Load was uneven. Machine 3 ran at 90% CPU while machine 5 sat at 15%, because nobody had time to move containers around.
  • A dead machine meant lost capacity. When one machine failed during a festival sale, its two player API copies stayed gone until someone built a new machine.

The team had written down what should run where. Nothing on the machines read that list or acted on it, so a tired person was the only thing keeping the plan true.

So the team moved to Kubernetes, an open-source system that runs containers across a group of machines and keeps them in the state you ask for. This guide follows that move, one part at a time.

What is Kubernetes architecture?

Kubernetes architecture is the way a cluster splits its work. A control plane decides what should happen, and worker nodes make it happen by running containers. Every part coordinates through one API.

A node is one machine in the cluster, physical or virtual. The official docs describe a cluster as a control plane plus a set of worker nodes, and every cluster needs at least one worker node to run pods.

Picture a container port. The harbour master's office plans which ship docks at which berth and where every box goes. The office never lifts a box itself. The berths, each with its own cranes and crew, do the lifting.

In Kubernetes, the control plane is the office and the worker nodes are the berths. Every box has the same standard shape, so any crane can lift it. A container image gives the same promise: any node can run it.

Where the picture breaks: a harbour office radios orders to each berth. Kubernetes parts never order each other. Each one watches a shared record in the API server and acts on what it sees.

Here is the team's cluster, simplified to two worker nodes. Start at the laptop. Notice that every arrow ends at the API server, and that only the API server touches etcd.

The audiobook app's Kubernetes cluster

Notice that the scheduler, controllers and kubelets never call each other. They all read and write through the API server, and only it talks to etcd.

Diagram connections:

  • Your laptop, kubectl to API server (kubectl apply)
  • API server to etcd (Read and write state)
  • Scheduler to API server (Watch pods, record a node)
  • Controller manager to API server (Watch and update objects)
  • Node 1: kubelet to API server (Watch pods, report status)
  • Node 2: kubelet to API server (Watch pods, report status)
  • Node 1: kubelet to Pods on node 1 (Start via the runtime)
  • Node 2: kubelet to Pods on node 2 (Start via the runtime)
  • Your laptop, kubectl: Sends the desired state as YAML
  • API server: Control plane: checks and records every change
  • etcd: Control plane: stores all cluster data
  • Scheduler: Control plane: picks a node for each new pod
  • Controller manager: Control plane: loops that fix drift from the plan
  • Node 1: kubelet: Worker: starts and watches pods on this machine
  • Node 2: kubelet: Worker: starts and watches pods on this machine
  • Pods on node 1: Player API and catalogue containers
  • Pods on node 2: Player API and search containers

What does the Kubernetes control plane do?

The control plane stores the desired state of the cluster, decides where each pod should run, and keeps nudging the cluster toward that state. Running your app's containers is not its job.

The docs note that setup scripts usually start all control plane parts on one machine and run no user containers there. Production clusters usually spread the control plane across several machines.

Desired state means the result you asked for, such as three copies of the player API. Current state means what is actually running right now. Almost everything in the control plane exists to close the gap between the two.

The API server: the front door

The API server, kube-apiserver, is the front end of the control plane. You talk to it through kubectl, a command-line tool, and so does every other part. It checks each request, then saves accepted changes to etcd.

The docs say kube-apiserver is designed to scale by running more copies, with traffic balanced between them. That is possible because the cluster's data lives in etcd, not inside any one API server.

etcd: the cluster's memory

etcd is a consistent, highly available key-value store: a database that saves each value under a name, like a large dictionary. Kubernetes keeps all its cluster data there, from every Deployment and pod to every node record.

In the port picture, etcd is the harbour master's ledger. Office staff can change shifts, but the ledger must survive. The docs tell you to have a backup plan for it, and to let only the API server reach it, because access to etcd equals full control of the cluster.

The scheduler: picking a node

The scheduler, kube-scheduler, watches for new pods that have no node yet and picks a node for each one. It works in two steps, filtering and then scoring.

Filtering removes nodes that cannot take the pod, such as nodes without enough CPU or memory left for the pod's request. Scoring ranks the nodes that remain, and the highest score wins.

If no node passes the filter, the pod stays unscheduled, shown as Pending, until one does. A pod with a higher priority can push lower-priority pods off a node to make room, which the docs call preemption.

The scheduler records its choice by telling the API server, a step the docs call binding. It never starts the pod itself. Starting is the node's job.

The controller manager: loops that fix drift

A controller is a loop that watches the cluster, compares the current state with the desired state, and acts to move one toward the other. The docs call this a control loop.

The controller manager, kube-controller-manager, runs many controllers compiled into one program. One watches nodes, one runs batch jobs, and one keeps the right number of copies of each pod.

Controllers also act only through the API server. The docs give the Job controller as an example: it never runs pods itself, it asks the API server to create or remove them. On a cloud, a separate cloud controller manager handles provider work such as cloud load balancers.

The main Kubernetes components, where they run and what happens when each one stops
ComponentRuns onIts jobIf it stops
API serverControl planeFront door; checks and records every changeNo one can change anything; running pods keep running
etcdControl planeStores all cluster dataWithout a majority of its members, no change can be saved
SchedulerControl planePicks a node for each new podNew pods stay Pending without a node
Controller managerControl planeRuns loops that fix drift from the planMissing pods are not replaced; dead nodes are not handled
kubeletEvery nodeStarts, watches and reports pods on its nodePods keep running unwatched; the node is marked unhealthy after about 50 seconds
kube-proxyMost nodesKeeps Service routing rules on its nodeRouting on that node stops following pod changes

What runs on a Kubernetes worker node?

Every worker node runs a kubelet and a container runtime, and usually kube-proxy as well. Together they turn the control plane's plan into running containers on that one machine.

The kubelet: the agent on each node

The kubelet is an agent that runs on every node. It watches the API server for pods assigned to its node and asks the runtime to start their containers. Then it keeps checking them and reports their status back.

At the port, the kubelet is the crane crew at one berth. The crew does not choose which boxes arrive. It lifts what the office assigns, then reports back once each box is stacked.

The kubelet also proves its node is alive. It renews a small record called a Lease every 10 seconds by default. That renewal is the node's heartbeat, and the control plane watches for it to stop.

The container runtime

The container runtime is the software that pulls images and starts and stops containers. containerd and CRI-O are common examples. The kubelet talks to it through the Container Runtime Interface, so a cluster can swap runtimes without rebuilding the kubelet.

kube-proxy and Services

A Service is a stable name and address placed in front of a changing group of pods. Pods come and go and get new addresses each time. The Service called player-api keeps the same name throughout.

kube-proxy keeps network rules on each node so that traffic sent to a Service reaches one of its pods. Some network plugins do this job themselves, and then nodes do not need kube-proxy. A cluster DNS add-on lets the search service simply call catalogue by name.

What is a pod in Kubernetes, and why not just a container?

A pod is the smallest unit Kubernetes runs: one or more containers that share a network address and can share storage, always placed together on one node. Kubernetes schedules pods, not bare containers.

Most pods hold just one container. Some add a small helper, called a sidecar, that ships logs or refreshes a certificate. Containers in one pod can talk over localhost, the machine's own address for itself.

In the port picture, a pod is two boxes chained together because they must travel on the same ship. The crane lifts them as one unit, and they never end up at different berths.

Pods are also disposable. The docs describe them as relatively ephemeral, meaning short-lived. When a pod is lost, say because its node fails, Kubernetes does not repair or move it. A controller makes a brand new pod, with a new name and a new address.

That is why you rarely create pods directly. Instead you create a Deployment, an object that says which image to run and how many copies to keep. The Deployment creates a ReplicaSet, whose only job is to keep exactly that many pods alive.

Here is the team's Deployment for the player API. It is a manifest: a file in YAML, a plain text format of names and values, that describes the state you want.

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: player-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: player-api
  template:
    metadata:
      labels:
        app: player-api
    spec:
      containers:
      - name: player-api
        image: registry.example.com/player-api:2.4.1
        resources:
          requests: {cpu: 250m, memory: 256Mi}

Four lines matter most. The replicas line asks for three copies. The selector and labels link the Deployment to its pods: a label is a small name-value tag, and the Deployment owns every pod tagged app: player-api.

The image line names an exact version, so every node runs the same code. The requests line reserves a quarter of a CPU core, written 250m, and 256 MiB of memory (about 268 MB). The scheduler filters nodes on these numbers.

What happens inside Kubernetes when you deploy an app?

When you deploy, kubectl sends your manifest to the API server, which saves it in etcd. Then controllers create pod records, the scheduler assigns each one a node, and kubelets start the containers.

Follow the boxes in order. Each box is one message, and its second line says who talks to whom.

One deploy, message by message

Notice that each part reacts to the step before it by watching the API server. No part gives another direct orders.

Diagram connections:

  • Send the Deployment to Save desired state
  • Save desired state to Create ReplicaSet and pods
  • Create ReplicaSet and pods to Pick a node for each pod
  • Pick a node for each pod to Start the containers
  • Start the containers to Report pods as running
  • Send the Deployment: kubectl → API server
  • Save desired state: API server → etcd
  • Create ReplicaSet and pods: Controller manager → API server
  • Pick a node for each pod: Scheduler → API server
  • Start the containers: Kubelet → container runtime
  • Report pods as running: Kubelet → API server

Here is the same flow for a real release, when the team moves the player API from version 2.4.0 to 2.4.1.

  1. Apply the manifest

    kubectl sends the new manifest. The API server checks who is asking and whether they may make the change, validates it, and saves it to etcd. Nothing has started yet.

  2. The Deployment controller notices

    It sees a new image version. It creates a second ReplicaSet for version 2.4.1 and starts raising its pod count.

  3. The ReplicaSet controller asks for pods

    It sees fewer pods than wanted and asks the API server to create pod records. They exist only as records, with no node yet.

  4. The scheduler places them

    It filters out nodes without room, scores the rest, and binds each pod to a node through the API server.

  5. Kubelets start the containers

    Each kubelet sees a pod bound to its node. It pulls the image, asks the runtime to start it, and reports the pod's status.

  6. Old pods are removed

    As new pods become ready, the Deployment shrinks the old ReplicaSet until only version 2.4.1 is left.

What just happened? With three replicas and the default rollout settings (25% surge, 25% unavailable), Kubernetes runs at most one extra pod at a time. The rollout itself never takes you below three available pods.

The release that took an hour now takes a few minutes (made up), and it is one command. Gradual rollouts like this, and their blue-green and canary cousins, are a topic of their own.

How does Kubernetes heal itself when a pod or node fails?

Kubernetes heals itself with controllers, loops that keep comparing the desired state stored in etcd with what is actually running. They create or remove pods until the two match again.

Here is that idea as a toy Python loop. The four function arguments are stand-ins for calls to the API server. Real controllers are written in Go and react to change events instead of sleeping, but the logic is the same.

python
import time

def reconcile(desired, running_pods, start_pod, stop_pod):
    gap = desired - len(running_pods)
    if gap > 0:
        for _ in range(gap):
            start_pod()                  # too few: ask for more
    elif gap < 0:
        for pod in running_pods[:-gap]:
            stop_pod(pod)                # too many: remove extras

def control_loop(get_desired, list_running, start_pod, stop_pod):
    while True:                          # never finishes, on purpose
        reconcile(get_desired(), list_running(), start_pod, stop_pod)
        time.sleep(2)

What just happened? The gap line is the whole idea. A positive gap means pods are missing, so the loop asks for more. A negative gap means extras, so it stops some.

We ran it with fake pods. Losing two of three pods made it start two, scaling down to two stopped one, and deleting a pod by hand made it start one. The loop never ends, because the cluster can drift at any moment.

Notice that the loop does not care why a pod is missing. A crash, a deleted pod and a dead node all look the same: the count is short. That is what makes this design hard to break.

When a container crashes

If the player API container crashes, the kubelet on that node restarts it inside the same pod. By default, the docs give the waits between restarts as 10, 20, then 40 seconds, doubling up to a cap of 5 minutes.

Once a container runs for 10 minutes without trouble, the wait resets. kubectl shows a container stuck in this cycle as CrashLoopBackOff. The 2 a.m. phone call is now a line in a log.

When a whole node dies

Pause and predict: a node holding two of the three player API pods loses power. How long until two new pods run elsewhere? Guess before reading the table.

Two terms first. A taint is a mark on a node that repels pods, and a taint with the NoExecute effect also removes pods already running there. A toleration is a rule on a pod that lets it stay despite a taint.

What happens after a node's last heartbeat, with Kubernetes v1.37 defaults
Time after last heartbeatWhat happensSetting that controls it
Every 10 s while healthyThe kubelet renews its node's LeaseKubelet nodeLeaseDurationSeconds (40 s) times 0.25
About 50 sNode controller sets the node's Ready condition to Unknownkube-controller-manager --node-monitor-grace-period
Right after, unless rate limits hold itNode gets the node.kubernetes.io/unreachable taint with effect NoExecuteRate-limited by --node-eviction-rate (0.1 nodes per second)
About 50 s plus 5 minPods' tolerations run out, so they are deletedPod tolerationSeconds, default 300 from the API server
Seconds laterReplicaSet creates replacements; the scheduler places them on healthy nodesFree room on other nodes

By default, the API server adds two 300-second tolerations, for the unreachable and not-ready taints, to every pod that does not set its own. The docs say this keeps pods bound to a failed node for 5 minutes after the problem is detected.

What happens when a worker node goes silent

Notice the two separate waits: about 50 seconds to decide the node is unreachable, then 5 more minutes before its pods are deleted.

Diagram connections:

  • Node 4 stops heartbeats to Heartbeat within 50 s?
  • Heartbeat within 50 s? to Node stays Ready (Yes)
  • Heartbeat within 50 s? to Ready becomes Unknown (No)
  • Ready becomes Unknown to Delete its pods (After 5 minutes)
  • Delete its pods to ReplicaSet adds new pods
  • ReplicaSet adds new pods to Scheduler places them
  • Node 4 stops heartbeats: Its kubelet no longer renews the Lease
  • Heartbeat within 50 s?: Node controller checks every 5 s
  • Node stays Ready: Its pods keep running there
  • Ready becomes Unknown: Node gets the unreachable taint
  • Delete its pods: After the 300 s toleration runs out
  • ReplicaSet adds new pods: Brings player API back to 3 copies
  • Scheduler places them: On healthy nodes with free room

So the answer is about 6 minutes: roughly 50 seconds plus 300 seconds, plus image pull and start time. In the team's second week a node did lose power, and two fresh pods were running elsewhere about six minutes later (made up, but it matches the defaults).

A deleted pod on a dead node can show as Terminating for a long time, because its kubelet never confirms the stop. The ReplicaSet controller does not wait: in the v1.37.1 source, it counts a pod being deleted as already gone.

If the node was cut off rather than dead, the docs warn its old pods may keep running until it reconnects. For a while, the app can have more copies running than you asked for.

One docs sentence is out of date. The heartbeat section of the Node Status reference page still says 40 seconds, as of 19 October 2026. The same page's condition table, the kube-controller-manager flag reference and the v1.37.1 code all give 50 seconds.

When does Kubernetes stop evicting pods from failed nodes?

Kubernetes slows or stops evicting when many nodes in one zone fail together, or when every node in the cluster looks unhealthy. Unhealthy means the node's Ready condition is Unknown or False. A zone is a separate data centre within one cloud region. A cluster without zones counts as one zone.

The reasoning is simple. If most nodes vanish at once, the real problem is more likely the control plane's network than the nodes. Evicting everything would make a network blip into an outage.

  • Normal case: the node controller taints at most 0.1 nodes per second, so one node every 10 seconds.
  • Partial disruption: when 55% or more of a zone's nodes are unhealthy, a zone of 50 nodes or fewer stops tainting new nodes. A bigger zone slows to 0.01 nodes per second.
  • Whole-zone outage: if every node in one zone is down while other zones are healthy, that zone goes back to the normal rate, so work moves to healthy zones.
  • Whole-cluster disruption: when no node anywhere is Ready, the node controller removes the taints and evicts nothing.

The Nodes concept page states the stop rule for a cluster of 50 nodes or fewer. The kube-controller-manager flag reference is more exact: the 50-node test applies per zone, and a zone needs at least three unhealthy nodes. The v1.37.1 code matches it.

We ported the v1.37.1 zone-state function and ran it. In a six-node zone, three dead nodes still get evicted at the normal rate, but four dead nodes stop all new evictions in that zone. The Nodes concept page leaves both details out as of 19 October 2026, so pin your version.

What happens if the Kubernetes control plane goes down?

If the control plane goes down, pods that are already running keep running, but nothing can change. There are no deploys, no scaling, and no replacements for pods on a failed node. The cluster freezes in its last state.

The kubelet handles container restarts on its own node, so crashed containers still restart. But a node that dies in this window is not healed, because the controllers that would notice cannot act.

That is why production control planes usually run across several machines. API server copies sit behind a load balancer. The scheduler and controller manager also run several copies, but a Lease-based election lets only one of each act at a time.

The etcd store needs the most care. It uses Raft, a consensus algorithm: a method for machines to agree on each change. It keeps accepting writes only while a majority of its members, called a quorum, can reach each other.

With three members, the quorum is two, so the store survives one failure. With five, it survives two. The etcd FAQ recommends an odd number of members, because a fourth member adds no extra failure tolerance.

We checked this on a local cluster of etcd 3.4.30, an older build; the etcd 3.6 FAQ gives the same majority rule. Three members with one stopped still took writes, and with two stopped the write failed. Four members with two stopped also failed, while five with two stopped still worked.

The team uses a managed Kubernetes service, where the cloud provider runs the control plane and etcd. If you run your own, keep this short list.

  • Spread the control plane across three zones.
  • Back up etcd on a schedule and keep the backups outside the cluster.
  • Practise a restore every quarter, because a backup nobody has restored is only a hope.
  • Keep every manifest in version control, so the desired state can be rebuilt from scratch.

How can you check each Kubernetes part on a real cluster?

You can see every part described here with kubectl. Run these on a test cluster you are allowed to inspect, replacing NODE_NAME and POD_NAME with real names.

  1. List the nodes

    Run kubectl get nodes. Expect one line per machine with STATUS Ready. A silent node shows NotReady about 50 seconds after its last heartbeat.

  2. Find the control plane

    Run kubectl get pods -n kube-system. On a kubeadm cluster, expect etcd, kube-apiserver, kube-scheduler and kube-controller-manager pods, each name ending in the node name. Managed services hide these.

  3. Watch a heartbeat

    Run kubectl get lease NODE_NAME -n kube-node-lease -o yaml twice, 10 seconds apart. leaseDurationSeconds should read 40, and renewTime should move forward by about 10 seconds.

  4. See the default tolerations

    Run kubectl get pod POD_NAME -o jsonpath='{.spec.tolerations}'. Expect entries for node.kubernetes.io/not-ready and node.kubernetes.io/unreachable, each with effect NoExecute and tolerationSeconds 300.

  5. Follow a rollout

    Run kubectl rollout status deployment/player-api. It prints progress lines, then: deployment "player-api" successfully rolled out. kubectl get rs then shows the new ReplicaSet at 3 and the old one at 0.

When is Kubernetes the wrong choice?

Kubernetes is often the wrong choice when you have a few services, a small team and steady traffic. Its many moving parts then cost more time than they save.

If you are still deciding whether to split your app into separate services at all, settle that question first. These signs say you can wait.

  • You run one or two services that a simple platform can host for you.
  • Releases are rare, and a few minutes of manual work is fine.
  • Nobody on the team has time to learn and run the cluster.

These signs point toward Kubernetes.

  • You run many services, and placing them by hand wastes machines.
  • Crashes and machine failures need fixing faster than a person can respond.
  • You release often and want gradual rollouts with a quick way back.

How do you explain Kubernetes architecture in a system design interview?

Explain it as a split between a control plane that stores and decides, and nodes that run containers, joined by one API and kept in line by control loops. Then walk one deploy and one failure through it.

  1. Draw the two halves

    Put the control plane on top and worker nodes below. Say that the control plane decides and the nodes run.

  2. Name the control plane parts

    API server as the only door, etcd as the store, the scheduler to place pods, and controllers to fix drift.

  3. Name what runs on every node

    The kubelet, the container runtime and usually kube-proxy. Pods, not containers, are what Kubernetes schedules.

  4. Walk one deploy

    Manifest to API server, saved to etcd, controllers create pods, the scheduler binds them, kubelets start them.

  5. Walk one failure

    A crashed container restarts locally with growing waits. A silent node is marked unreachable after about 50 seconds, and its pods are replaced 5 minutes later.

  6. Cover the control plane's own failure

    Running pods survive, but nothing changes. Then mention several control plane machines, an odd-sized etcd cluster and tested backups.

Questions people ask

Is Kubernetes the same as Docker?
No. Docker is best known as a tool for building container images and running containers on one machine. Kubernetes runs containers across many machines and keeps them in the state you describe. On each node, a runtime such as containerd or CRI-O pulls and runs the images. An image name with no registry, like busybox, is pulled from Docker Hub by default.
What is the difference between a pod and a node?
A node is a machine, and a pod is a unit of work placed on that machine. One node usually runs many pods, often from different apps. The scheduler decides which node each pod goes to, and the pod stays there until it finishes, is deleted, is evicted or the node fails.
Why does Kubernetes use etcd?
Kubernetes uses etcd because it needs one consistent place to store all cluster data, even when machines fail. Every write is agreed by a majority of members using the Raft algorithm, so a three-member store keeps working when one member dies. The docs advise that only the API server should have access to it.
How many control plane nodes does a Kubernetes cluster need?
For production, three is a common choice, so the cluster survives one control plane machine failing. The etcd store needs a majority of its members, so use odd counts: three tolerate one failure and five tolerate two. The etcd FAQ suggests no more than seven. For learning on a laptop, one machine is fine.
Can a pod move to another node?
No, a pod never moves. Once bound to a node, it stays there until it ends. When a node fails or a pod is evicted, its controller creates a brand new pod, which the scheduler may place on a different node. So never keep important data only inside a pod.
How long does Kubernetes take to replace pods from a dead node?
With v1.37 defaults, about 6 minutes plus start-up time. The node controller marks a silent node unreachable about 50 seconds after its last heartbeat. Pods then tolerate that for 300 seconds before they are deleted and replaced. Many unhealthy nodes in one zone can slow or stop this, and you can shorten tolerationSeconds per pod.

Sources and further reading

Practise this with Lala

Practise