The problem: containers are easy, fleets are hard
You already know how to run a container:
docker run -d -p 8080:80 --name web nginx:1.27
That works fine on one laptop. Now picture production: 40 services, each running 3 to 10 copies, spread across 12 machines. At 3 a.m. a machine's disk fails. Who restarts its containers somewhere else? Who decides which machine has free memory? Who swaps version 1.4 for 1.5 without dropping traffic, and who stops it again when 1.5 crashes?
Most teams first try to solve this with scripts:
#!/bin/bash
# deploy.sh: the 'imperative' approach
for host in node1 node2 node3; do
ssh $host "docker pull shop-api:1.5 && docker rm -f api && docker run -d --name api shop-api:1.5"
done
# What if node2 is down? The script has already moved on.
# What if the container crashes an hour later? Nothing notices.
A script like this is imperative: it runs a list of steps once and then stops watching. Anything that goes wrong after it finishes is your problem. Google hit this problem at huge scale years ago with an internal system called Borg. In 2014 it open-sourced a new system inspired by Borg, called Kubernetes, and in 2015 donated it to the newly formed Cloud Native Computing Foundation (CNCF).
The big idea: declare what you want, let loops make it so
Kubernetes works the other way round. It is declarative: you describe the end state you want, and the system keeps working towards it. Here is a taste of what that looks like. You will write files like this properly in later levels:
# desired-state.yaml: "I want 3 copies of shop-api 1.5 running"
apiVersion: apps/v1
kind: Deployment
metadata:
name: shop-api
spec:
replicas: 3 # the desired state: always 3 copies
selector:
matchLabels:
app: shop-api
template:
metadata:
labels:
app: shop-api
spec:
containers:
- name: api
image: shop-api:1.5
The file contains no steps, only a goal. Something inside the cluster then runs a reconciliation loop, which the Kubernetes docs compare to a thermostat. You set 21°C (the desired state), the room is at 18°C (the current state), and the thermostat keeps turning the heating on or off to close the gap. In code, every controller in Kubernetes does roughly this:
while True:
desired = read_desired_state() # e.g. replicas: 3
actual = observe_cluster() # e.g. 2 running (one node just died)
if actual != desired:
act_to_close_gap() # ask for 1 more copy
# ...then loop forever, never 'finished'
This loop is why Kubernetes beats the script. When a node dies at 3 a.m., the loop sees 2 copies instead of 3 and asks for another, with nobody paged. The loop never finishes, so the system heals itself without anyone remembering to rerun anything.
Pitfall: Declarative doesn't mean instant. Reconciliation takes seconds or more, and if a node is short of memory the cluster may never reach your desired state. It keeps trying, and you have to look at the status to find out why.
The architecture: a control plane and worker nodes
A cluster has two halves. The control plane is the brain: it stores the desired state and makes decisions. The nodes are the muscle: machines that actually run your containers.
CONTROL PLANE WORKER NODE (x many)
+--------------------------------+ +-----------------------------+
| kube-apiserver <---> etcd | | kubelet |
| ^ ^ | <----> | -> container runtime |
| | | | | (containerd, CRI-O) |
| kube-scheduler | | kube-proxy (Service rules) |
| kube-controller-manager | +-----------------------------+
| cloud-controller-manager (opt) |
+--------------------------------+
Control plane components:
- kube-apiserver: the front door. It exposes the Kubernetes HTTP API, and every tool and component, including the command-line client you'll install next level, talks to the cluster through it. It validates requests and is the only component that reads and writes etcd directly.
- etcd: a consistent, highly available key-value store holding all cluster data, including your desired state. Lose etcd without a backup and you lose the cluster's memory.
- kube-scheduler: watches for Pods (the units that wrap your containers, covered in Level 3) that have no node yet, and picks a suitable node for each, based on free resources and constraints.
- kube-controller-manager: runs the built-in controllers, which are the reconciliation loops above. Controllers don't start containers themselves. They tell the API server to create or delete objects, and other components act on that.
- cloud-controller-manager (optional): connects the cluster to a cloud provider, for example to create load balancers.
Node components:
- kubelet: an agent on every node. It watches the API server for Pods assigned to its node, makes sure their containers are running, and reports their status back.
- Container runtime: the software that actually runs containers, such as containerd or CRI-O. The kubelet talks to it through a standard interface called the CRI. Since v1.24, Kubernetes no longer has built-in support for Docker Engine (the "dockershim" was removed), but images built with
docker buildstill run fine. - kube-proxy (optional): maintains network rules on each node so that Services, the stable addresses for groups of Pods, reach the right containers. Some network plugins replace it entirely.
Walkthrough: what happens when you submit that manifest
1. You submit desired-state.yaml -> kube-apiserver authenticates and validates it
2. kube-apiserver -> stores the Deployment in etcd
3. Controller in controller-manager -> sees 'want 3, have 0'; asks API server to create 3 Pod objects
4. kube-scheduler -> sees 3 unassigned Pods; binds each to a node
5. kubelet on each chosen node -> sees a Pod assigned to it; tells containerd to pull and start the image
6. kubelet -> reports 'Running' back to the API server, which updates etcd
7. Later, a node dies -> controller sees 'want 3, have 2' and goes back to step 3
In step 3 the controller actually goes through an intermediate object called a ReplicaSet, which Level 4 covers. Notice that no component calls another directly to say "do this now". Each one watches the API server and reacts to changes. That loose coupling is why a scheduler restart or a brief network blip doesn't break anything: when the component comes back, it picks up the current state and carries on.
Best practice: In production, the control plane runs on several machines and etcd is backed up. The managed services EKS, AKS and GKE take most of this work off you, which a later level covers.
A note on certification
The Linux Foundation's CKA (administrator) and CKAD (application developer) exams are performance-based. There are no multiple-choice questions: you get 2 hours to solve tasks from a command line against live clusters. This course gives you a solid practical grounding with a local cluster, but it is not exam preparation. If you go for a certificate later, plan extra timed practice on exam-style tasks.
Key takeaways
- Scripts are imperative and run once. Kubernetes is declarative, and its controllers keep reconciling current state towards desired state.
- The control plane is made up of the API server (the front door, and the only component that talks to etcd), etcd (the cluster's data store), the scheduler (picks a node for each Pod) and the controller manager (runs the reconciliation loops).
- Each node runs a kubelet (makes sure its Pods are running), a container runtime (containerd or CRI-O, via the CRI) and, optionally, kube-proxy (Service networking).
- Components coordinate by watching the API server, not by calling each other.
- CKA and CKAD are hands-on exams. This course is a practical grounding, not exam preparation.