Cloud Explainers

How Kubernetes Works

Kubernetes10 min videoPublished Nov 3, 2026

How Kubernetes works, for beginners, proven on a real Kubernetes cluster: a deleted pod comes back by itself, and we follow why through every part: the control plane (API server, etcd, scheduler, controllers), worker nodes with the kubelet and containerd, Deployments, ReplicaSets and pods, a Service that gives changing pods one stable name, a whole worker node switched off (and the 5-minute default wait before its pods are replaced), and a rolling update.

How Kubernetes Works: the full diagram
The whole picture, from the video's recap.

A pod comes back

When your app's container crashes, or the whole server under it dies, who starts it again? On Kubernetes, nobody has to. Ana's tea shop runs as three copies of the same container. We delete one, and a replacement is already starting. By the end, you'll know every part of Kubernetes that made that happen, and what it does when a whole server dies.

A pod comes back: diagram from the video

Here's the real thing. Three copies, each one called a pod, all running. Ana deletes the first. Look again: a new pod, 0 seconds old, already creating its container. Three again.

Ana's laptop
kubectl get pods -l app=shopNAME                   READY   STATUS    RESTARTS   AGE
shop-8f9c4c6b8-2rc5q   1/1     Running   0          23s
shop-8f9c4c6b8-nkhpp   1/1     Running   0          23s
shop-8f9c4c6b8-xmb4q   1/1     Running   0          23s
Ana's laptop
kubectl delete pod shop-8f9c4c6b8-2rc5qpod "shop-8f9c4c6b8-2rc5q" deleted from default namespace
Ana's laptop
kubectl get pods -l app=shopNAME                   READY   STATUS              RESTARTS   AGE
shop-8f9c4c6b8-f56gl   0/1     ContainerCreating   0          0s
shop-8f9c4c6b8-nkhpp   1/1     Running             0          23s
shop-8f9c4c6b8-xmb4q   1/1     Running             0          23s

The cluster

Why not just run the container on one server? Then everything is your job. If the app crashes, it stays down until someone restarts it; Kubernetes restarts it for you. If the server dies, so does the shop; Kubernetes moves the work to another machine. More visitors? You change one number. And new versions roll out without downtime.

The cluster: diagram from the video

Kubernetes runs on a group of machines called a cluster. Each machine is a node. Worker nodes run your apps. The control plane runs Kubernetes itself: the API server, the front door every request goes through; etcd, a database that stores what the cluster should look like; the scheduler, which picks a node for each new pod; and the controllers, which keep everything the way you asked.

Our cluster is real Kubernetes 1.37, made with a tool called kind, which runs each node as a container on one lab machine. On a real cluster, each node is its own machine. Three nodes: one control plane, two workers. And the control plane's parts are pods too: etcd, the API server, the controller manager and the scheduler, all on the control plane node.

Ana's laptop
kubectl get nodesNAME                 STATUS   ROLES           AGE   VERSION
shop-control-plane   Ready    control-plane   66s   v1.37.0
shop-worker          Ready    <none>          48s   v1.37.0
shop-worker2         Ready    <none>          48s   v1.37.0
Ana's laptop
kubectl get pods -n kube-system -o custom-columns=POD:.metadata.name,NODE:.spec.nodeNamePOD                                          NODE
coredns-559f6c778d-7nx6c                     shop-control-plane
coredns-559f6c778d-mzd2v                     shop-control-plane
etcd-shop-control-plane                      shop-control-plane
kindnet-2z86p                                shop-worker
kindnet-44fdw                                shop-control-plane
kindnet-f27hc                                shop-worker2
kube-apiserver-shop-control-plane            shop-control-plane
kube-controller-manager-shop-control-plane   shop-control-plane
kube-proxy-7c9tg                             shop-worker2
kube-proxy-b7w7r                             shop-control-plane
kube-proxy-krclv                             shop-worker
kube-scheduler-shop-control-plane            shop-control-plane

Desired state

Ana never tells Kubernetes to start a container. She describes what she wants, in a file. This one is a Deployment named shop: a description of an app for Kubernetes to keep running. Three replicas, each running the container image kettle-shop, version 1.0, and each labelled app: shop.

Ana's laptop
cat deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
  name: shop
spec:
  replicas: 3
  selector:
    matchLabels:
      app: shop
  template:
    metadata:
      labels:
        app: shop
    spec:
      containers:
      - name: web
        image: kettle-shop:1.0
        ports:
        - containerPort: 8080

kubectl, the command-line tool for Kubernetes, sends that file to the API server with apply, which checks it and saves it in etcd. That's the desired state: what Ana wants. Nothing has started yet. From here on, Kubernetes compares what's running, the actual state, with what's wanted, and works to close any gap.

Desired state: diagram from the video

Ana applies the same file again: unchanged. What runs already matches what she wants, so there's nothing to do. Now a one-line text replace changes three replicas to five, grep shows the new line, and she applies it: configured. Seconds later, five pods, 3 on shop-worker and 2 on shop-worker2.

Ana's laptop
kubectl apply -f deployment.yamldeployment.apps/shop unchanged
Ana's laptop
sed -i 's/replicas: 3/replicas: 5/' deployment.yaml
Ana's laptop
grep replicas deployment.yaml  replicas: 5
Ana's laptop
kubectl apply -f deployment.yamldeployment.apps/shop configured
Ana's laptop
kubectl get pods -o custom-columns=POD:.metadata.name,NODE:.spec.nodeName -l app=shopPOD                    NODE
shop-8f9c4c6b8-dzcp8   shop-worker2
shop-8f9c4c6b8-f56gl   shop-worker
shop-8f9c4c6b8-hp922   shop-worker
shop-8f9c4c6b8-nkhpp   shop-worker2
shop-8f9c4c6b8-xmb4q   shop-worker

The scheduler

But who chose those nodes? A new pod starts with no node at all. The scheduler watches the API server for pods like that. For each one, it checks which nodes have enough free CPU and memory, scores them, and writes the winner into the pod. It never starts anything itself.

Kubernetes records events like these. For one of Ana's new pods: the default scheduler assigned it to shop-worker. Then the kubelet on that node, found the image already there, created the container, and started it.

Ana's laptop
kubectl events --for pod/shop-8f9c4c6b8-hp922LAST SEEN   TYPE     REASON      OBJECT                     MESSAGE
4s          Normal   Scheduled   Pod/shop-8f9c4c6b8-hp922   Successfully assigned default/shop-8f9c4c6b8-hp922 to shop-worker
4s          Normal   Pulled      Pod/shop-8f9c4c6b8-hp922   Container image "kettle-shop:1.0" already present on machine and can be accessed by the pod
4s          Normal   Created     Pod/shop-8f9c4c6b8-hp922   Container created
4s          Normal   Started     Pod/shop-8f9c4c6b8-hp922   Container started

Here's the whole path from a file to a running container. kubectl sends the Deployment to the API server. The controllers turn it into pods with no node yet. The scheduler sees them and binds each one to a node. The kubelet on that node sees a pod assigned to it, starts the container, and reports back: running. No part gives orders to another: each one watches the API server, and does its own job.

The scheduler: diagram from the video

On the node

The kubelet is the agent on every node. It watches the API server for pods assigned to its node, and asks the container runtime, here containerd, to start their containers. A pod is one or more containers that share an IP address. And each container is an ordinary process on the node's Linux.

On the node: diagram from the video

Here's a root shell on shop-worker. The kubelet and containerd are running. And there are Ana's shop containers: 3 Python processes, one for each of the 3 pods on this node.

A root shell on the node shop-worker
pgrep -l 'kubelet|containerd$'138 containerd
251 kubelet
A root shell on the node shop-worker
pgrep -a python3937 python3 -u /shop.py
1165 python3 -u /shop.py
1276 python3 -u /shop.py

Controllers

So who replaced the pod Ana deleted? A controller. Each controller runs the same loop, over and over: look at the actual state, compare it with the desired state, and act to close the gap. One pod short? Create one. One too many? Delete one. Then look again.

Controllers: diagram from the video

For Ana's shop, two controllers work as a chain. The Deployment controller creates a ReplicaSet, whose only job is to keep a count of identical pods. The ReplicaSet controller then creates or deletes pods to match that count. Her ReplicaSet wants five, has five, and all five are ready.

Controllers: diagram from the video

Here it is: desired five, current five, ready five. Its name is the start of every pod's name, which is how you can tell which ReplicaSet made a pod.

Ana's laptop
kubectl get replicasetNAME             DESIRED   CURRENT   READY   AGE
shop-8f9c4c6b8   5         5         5       29s

Pod names follow the same chain. Take this one. First, the Deployment's name, shop. Then a code made from the pod template, the part of the file that describes each pod: together, that's the ReplicaSet's name. Last, five random characters, so every pod's name is different.

Controllers: diagram from the video

Services

Now, pods come and go, and each new pod gets a new IP address. So how does anything find the shop? With a Service. A Service has one name and one address that never change. Kubernetes keeps a list of the ready pods that carry its label, app: shop, and kube-proxy, a small program on every node, sends each connection to one of them.

Services: diagram from the video

Ana's Service, shop, has its own cluster address. Its list holds five pod addresses. From a small test pod inside the cluster, we ask for http://shop, six times. Same name every time, answered by 4 different pods.

Ana's laptop
kubectl get service shopNAME   TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
shop   ClusterIP   10.96.126.140   <none>        80/TCP    29s
Ana's laptop
kubectl get endpointslices -l kubernetes.io/service-name=shopNAME         ADDRESSTYPE   PORTS   ENDPOINTS                                      AGE
shop-cxx96   IPv4          8080    10.244.2.2,10.244.1.2,10.244.2.4 + 2 more...   30s
Ana's laptop
kubectl exec client -- sh -c 'for i in 1 2 3 4 5 6; do wget -qO- http://shop; done'Kettle & Bean 1.0, served by shop-8f9c4c6b8-xmb4q
Kettle & Bean 1.0, served by shop-8f9c4c6b8-f56gl
Kettle & Bean 1.0, served by shop-8f9c4c6b8-nkhpp
Kettle & Bean 1.0, served by shop-8f9c4c6b8-xmb4q
Kettle & Bean 1.0, served by shop-8f9c4c6b8-hp922
Kettle & Bean 1.0, served by shop-8f9c4c6b8-xmb4q

What happened in each of those visits? First, the test pod asked the cluster's own DNS server, CoreDNS, for the name shop, and got the Service's address back. Then it connected to that address. No program listens there: rules that kube-proxy wrote on the node rewrite the connection to one ready pod, a different one from visit to visit.

Services: diagram from the video

When a node dies

How does the control plane notice a dead node? Every kubelet checks in with the API server every few seconds, renewing its lease, a small record kept by the API server. When the check-ins stop, the node controller waits. After 50 seconds of silence, the default from Kubernetes 1.32 on, it marks the node NotReady. Older versions waited 40.

When a node dies: diagram from the video

Now the big one. We switch off the whole worker node shop-worker, with 3 of the shop's pods on it. Its kubelet stops reporting in. After 50 seconds of silence, the control plane marks the node NotReady, and its pods are taken out of the Service's list.

shop-worker: NotReady. And the shop? Of six visits, 5 were answered, all by pods on shop-worker2. One failed: for a moment, a node can still send a connection to a pod that's already gone, which is why apps retry.

Ana's laptop
kubectl get nodesNAME                 STATUS     ROLES           AGE    VERSION
shop-control-plane   Ready      control-plane   2m2s   v1.37.0
shop-worker          NotReady   <none>          104s   v1.37.0
shop-worker2         Ready      <none>          104s   v1.37.0
Ana's laptop
kubectl exec client -- sh -c 'for i in 1 2 3 4 5 6; do wget -qO- http://shop; done'wget: can't connect to remote host (10.96.126.140): Host is unreachable
Kettle & Bean 1.0, served by shop-8f9c4c6b8-nkhpp
Kettle & Bean 1.0, served by shop-8f9c4c6b8-nkhpp
Kettle & Bean 1.0, served by shop-8f9c4c6b8-nkhpp
Kettle & Bean 1.0, served by shop-8f9c4c6b8-dzcp8
Kettle & Bean 1.0, served by shop-8f9c4c6b8-dzcp8

But Kubernetes doesn't replace the lost pods yet. A node can drop off the network for a moment and come back. So every pod carries a toleration: stay on a node that's not ready, or unreachable, for 300 seconds. That's five minutes by default, and you can change it in the pod's spec.

When a node dies: diagram from the video

After the wait, the controllers act. 5 minutes and 50 seconds after we switched off shop-worker, 3 new pods are running on shop-worker2. The old ones show as Terminating: that machine is off, so nothing can confirm they've stopped.

Ana's laptop
kubectl get pods -l app=shopNAME                   READY   STATUS        RESTARTS   AGE
shop-8f9c4c6b8-9644m   1/1     Running       0          4s
shop-8f9c4c6b8-dzcp8   1/1     Running       0          5m59s
shop-8f9c4c6b8-f56gl   1/1     Terminating   0          6m
shop-8f9c4c6b8-hp922   1/1     Terminating   0          5m59s
shop-8f9c4c6b8-kh6hk   1/1     Running       0          4s
shop-8f9c4c6b8-nkhpp   1/1     Running       0          6m23s
shop-8f9c4c6b8-pv6kx   1/1     Running       0          4s
shop-8f9c4c6b8-xmb4q   1/1     Terminating   0          6m23s
Ana's laptop
kubectl get pods -o custom-columns=POD:.metadata.name,NODE:.spec.nodeName -l app=shopPOD                    NODE
shop-8f9c4c6b8-9644m   shop-worker2
shop-8f9c4c6b8-dzcp8   shop-worker2
shop-8f9c4c6b8-f56gl   shop-worker
shop-8f9c4c6b8-hp922   shop-worker
shop-8f9c4c6b8-kh6hk   shop-worker2
shop-8f9c4c6b8-nkhpp   shop-worker2
shop-8f9c4c6b8-pv6kx   shop-worker2
shop-8f9c4c6b8-xmb4q   shop-worker

Each part did one small job. The control plane noticed the silent node. The controllers saw five pods wanted and only two left, the scheduler placed the new ones on the node that was left, and its kubelet started them.

Rolling updates

One last thing Kubernetes does for you: updates. With shop-worker back on, Ana changes the Deployment's image to version 2.0. The Deployment controller makes a second ReplicaSet for the new version, then grows it and shrinks the old one a few pods at a time, so the shop never goes down.

Rolling updates: diagram from the video

One command sets the new image. Rollout status follows along: new replicas updated, old replicas waiting to stop, then successfully rolled out. Six visits later: every answer says version 2.0.

Ana's laptop
kubectl set image deployment/shop web=kettle-shop:2.0deployment.apps/shop image updated
Ana's laptop
kubectl rollout status deployment/shopWaiting for deployment spec update to be observed...
Waiting for deployment "shop" rollout to finish: 2 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 2 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 3 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 4 out of 5 new replicas have been updated...
Waiting for deployment "shop" rollout to finish: 2 old replicas are pending termination...
Waiting for deployment "shop" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "shop" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "shop" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "shop" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "shop" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "shop" rollout to finish: 4 of 5 updated replicas are available...
deployment "shop" successfully rolled out
Ana's laptop
kubectl exec client -- sh -c 'for i in 1 2 3 4 5 6; do wget -qO- http://shop; done'Kettle & Bean 2.0, served by shop-9c5f595bf-sjzl7
Kettle & Bean 2.0, served by shop-9c5f595bf-8vppp
Kettle & Bean 2.0, served by shop-9c5f595bf-sjzl7
Kettle & Bean 2.0, served by shop-9c5f595bf-8vppp
Kettle & Bean 2.0, served by shop-9c5f595bf-8vppp
Kettle & Bean 2.0, served by shop-9c5f595bf-sjzl7

Recap

So here's how Kubernetes works. You describe what you want, and the API server stores it in etcd. The scheduler picks a node for each pod. The kubelet on that node starts its containers. Controllers keep comparing what runs with what you asked for, and fix any difference, and a Service gives the changing pods one stable name.

If watching a real node fail made Kubernetes less of a mystery, subscribing helps this channel reach more beginners.

Every command and number in this video came from a real Kubernetes 1.37 cluster on our own machine. Thanks for watching Cloud Explainers.

Sources

Demos: real output from a Kubernetes cluster made with kind on our own machine, running a made-up shop's website; the failed server is a worker node we switched off on purpose. Watch the full video on YouTube.

More videos

Docker10 min

How to Fix Docker Port Already Allocated Errors

PostgreSQL10 min

How PostgreSQL Works

Git8 min

How to Fix Git Merge Conflicts

Linux9 min

How to Fix No Space Left on Device in Linux

Networking10 min

How the Internet Works

Databases & SQL9 min

SQL vs NoSQL: When Each One Wins

Auth (OAuth, JWT)8 min

How to Fix JWT Token Expired Errors (401 Unauthorized)

Claude & the Claude Developer Platform9 min

Claude API vs Claude Pro: Why Your Subscription Doesn't Include It

AI agent skills9 min

Why AI Agent Skills Beat Giant Prompts

Linux10 min

Linux Cron Jobs Will Finally Make Sense After This

Cybersecurity10 min

Security Logs Will Finally Make Sense After This

Cybersecurity8 min

How to Fix a Security Leak: An API Key Pushed to GitHub

DNS9 min

DNS Will Finally Make Sense After This

Cybersecurity9 min

Never Ignore a Security Update

Auth (OAuth, JWT)9 min

Two-Factor Authentication Will Finally Make Sense

Networking8 min

Never Expose a Database to the Internet

Cybersecurity10 min

SSH Keys vs Passwords: Which Is Safer for Security?

Linux10 min

The 5 Levels of Linux Boot: From Power On to Login

Networking12 min

The 5 Levels of Network Firewalls

Cybersecurity10 min

Password Security: Why Hackers Crack Weak Passwords

Linux9 min

Why Your Linux Server Runs Out of Memory

Networking10 min

HTTP vs HTTPS: What Actually Changes on Your Network

Linux12 min

Every Linux File Permission You Need to Know

Linux10 min

Linux Processes vs Threads: When Each One Wins

Networking11 min

The OSI Model Will Finally Make Sense After This

Git11 min

The 7 Levels of Git

Networking10 min

Every Network Troubleshooting Command You Need

Cybersecurity10 min

Hashing vs Encryption in Cybersecurity: When Each One Wins

Networking11 min

Why the Internet Ran Out of IP Addresses

Linux11 min

Linux Package Managers: apt vs Snap vs Flatpak

Python9 min

Why Python Is Slow (and When It Doesn't Matter)

Networking11 min

TCP vs UDP: When Each One Wins

Linux8 min

Stop Running Linux as Root (Do This Instead)

Linux11 min

Linux Users and Groups Will Finally Make Sense

Cybersecurity9 min

Why Phishing Beats Good Security (and How to Spot It)

Docker12 min

The 7 Levels of Docker

AI agents & MCP12 min

AI Agents Will Finally Make Sense After This

Git8 min

Stop Using git push --force (Use This Instead)

AI agent skills10 min

Agent Skills vs MCP: When Each One Wins

Networking12 min

Networking Will Finally Make Sense After This

Linux12 min

Every Linux Command You Actually Need

Cybersecurity12 min

The 7 Levels of Cybersecurity

System design12 min

Every System Design Concept You Need, in One Diagram

PostgreSQL10 min

Why PostgreSQL Ignores Your Index