Phase 4 · ORCHESTRATION & IAC
Why Kubernetes — the problems it solves
By the end of today
- Explain the four problems Kubernetes solves that docker compose cannot
- Describe the control-plane and worker-node split from memory
- Install kind and kubectl, then create a cluster and list nodes
What Kubernetes actually solves — and when to skip it
Phase 2 ended with docker compose up -d bringing a whole stack up on one host. That is compose’s ceiling: one host. Reboot that machine and your containers stay down until someone re-runs compose. Traffic triples and you can’t spread load across three machines. A container dies at 3am and nothing restarts it until you wake up. Compose describes a stack; it does not keep that stack alive.
Kubernetes is the thing that keeps it alive. You hand it a description of what you want running — “3 copies of this image, always” — and it works continuously to make reality match. That one idea, declarative desired-state, is the whole game. You stop issuing commands (“start this container”) and start declaring outcomes (“I want 3 of these”); a control loop closes the gap forever.
Four capabilities fall out of that idea, none of which compose has:
- Self-healing — a pod crashes or a node dies, Kubernetes sees actual (2) drift below desired (3) and starts a replacement on a healthy node, unattended.
- Scaling — change the number from 3 to 10 and it schedules the extra copies wherever there’s room.
- Rolling updates — ship a new image and it replaces pods a few at a time, watching health, rolling back if the new ones fail. No downtime window.
- Scheduling across nodes — you never pick the machine. You say “run this,” and the scheduler places it on a node with spare CPU and memory.
To do all that, a cluster splits into two planes. The control plane is the brain — the API server you talk to, etcd storing desired state, the scheduler placing pods, and controllers running the reconcile loops. The worker nodes are the muscle — each runs a kubelet that starts containers and reports health, alongside your actual pods. You declare state to the control plane; the workers make it real.
Real world: Compose is a chef who cooks one dinner and goes home — perfect until a plate drops and no one’s left in the kitchen. Kubernetes is the restaurant manager who never leaves: told “there must always be three hot mains on the pass,” they watch the pass all night, and the instant one is dropped or served they fire another, pull a cook over from a quieter station, and never wait to be asked.
A named example makes it concrete: Kubernetes is Google’s own design set free. It grew out of Borg, the internal system that has scheduled Google’s containers across data centers for well over a decade; the 2015 1.0 release handed that battle-tested control-plane/worker model to everyone. When the split feels solid, that’s why — it ran Google’s fleet first.
So when do you not need it? Often. A single app on one VM, a side project, a low-traffic internal tool — compose or one container is simpler, cheaper, and easier to debug. Kubernetes buys self-healing and multi-node scale at the price of real complexity: more parts, more to learn, more to break. Reach for it when you genuinely have multiple services, need zero-downtime deploys, or must survive a node dying — not because it’s on your résumé.
That’s why you’ll learn it on kind — Kubernetes-in-Docker, a real cluster running as containers on your laptop, no cloud and no bill. Today you install the tools and create your first node; the rest of Phase 4 builds on it.
Hands-On Lab
Budget about 20 minutes. Open your WSL2 Ubuntu 24.04 terminal with Docker 27+ running — kind runs the whole cluster inside Docker, so the daemon must be up first. Everything today is local: no cloud account, no bill. Version strings, ports, container IDs and pod-name suffixes are generated per install and per run — yours will differ from the samples below.
# 1. Confirm the Docker daemon is up — kind builds its cluster from Docker containers.
docker info --format '{{.ServerVersion}}'
# Output (your version will differ):
# 27.5.1
# 2. Install kubectl — download the current stable release for Linux amd64.
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
# No output on success — `install` copies the binary and sets its mode silently.
# 3. Install kind 0.24 — one static binary onto your PATH.
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.24.0/kind-linux-amd64
chmod +x ./kind && sudo mv ./kind /usr/local/bin/kind
# No output on success.
# 4. Check both tools answer. kubectl --client works before any cluster exists.
kubectl version --client
kind version
# Output (versions will differ):
# Client Version: v1.31.1
# Kustomize Version: v5.4.2
# kind v0.24.0 go1.22.6 linux/amd64
# 5. Create your first cluster. kind pulls a node image, then boots the control plane.
kind create cluster --name devops
# Output (emoji and timings vary; the node image tag pins the K8s version):
# Creating cluster "devops" ...
# ✓ Ensuring node image (kindest/node:v1.31.0) 🖼
# ✓ Preparing nodes 📦
# ✓ Writing configuration 📜
# ✓ Starting control-plane 🕹️
# ✓ Installing CNI 🔌
# ✓ Installing StorageClass 💾
# Set kubectl context to "kind-devops"
# 6. List the cluster's nodes. A default kind cluster is a single control-plane node.
kubectl get nodes
# Output (AGE and the exact version will differ):
# NAME STATUS ROLES AGE VERSION
# devops-control-plane Ready control-plane 58s v1.31.0
# 7. Ask where the control plane lives. These URLs are your cluster's API endpoints.
kubectl cluster-info
# Output (the localhost port is random per cluster — yours will differ):
# Kubernetes control plane is running at https://127.0.0.1:39421
# CoreDNS is running at https://127.0.0.1:39421/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
# 8. See the control-plane components themselves — they run as pods in kube-system.
kubectl get pods -n kube-system
# Output (pod-name suffixes are random; the control-plane pods match the diagram above):
# NAME READY STATUS RESTARTS AGE
# coredns-7c65d6cfc9-4b2xq 1/1 Running 0 70s
# coredns-7c65d6cfc9-p8n7d 1/1 Running 0 70s
# etcd-devops-control-plane 1/1 Running 0 78s
# kindnet-x9k2p 1/1 Running 0 78s
# kube-apiserver-devops-control-plane 1/1 Running 0 78s
# kube-controller-manager-devops-control-plane 1/1 Running 0 78s
# kube-proxy-6m4wf 1/1 Running 0 78s
# kube-scheduler-devops-control-plane 1/1 Running 0 78s
Two of those rows aren’t in the diagram: kindnet is kind’s built-in CNI (it wires pod-to-pod networking) and kube-proxy programs each node’s Service routing — both are node-level networking add-ons that run as DaemonSets rather than as boxes in the concept diagram.
# 9. Prove the whole cluster is "just Docker" — the node is a container on your host.
docker ps --filter "name=devops-control-plane"
# Output (CONTAINER ID and the published port will differ):
# CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
# a1b2c3d4e5f6 kindest/node:v1.31.0 "/usr/local/bin/entr…" Up 2 minutes 127.0.0.1:39421->6443/tcp devops-control-plane
# 10. Confirm kubectl is pointed at this cluster. The context name kind- prefixes it.
kubectl config current-context
# Output:
# kind-devops
Read those last outputs back to yourself: you installed two binaries, and one command stood up a real Kubernetes control plane running as a Docker container on your laptop. kubectl get nodes shows the node is Ready; kubectl get pods -n kube-system shows the API server, etcd, scheduler and controllers from the concept diagram, alive as pods. This was your first real cluster — a look at Kubernetes running before you build on it. Tear it down now with kind delete cluster --name devops; tomorrow (day 67) you create the kind cluster you’ll keep for the rest of Phase 4.
Common Errors & Fixes
These three trip up almost everyone on their first cluster. Read the error text slowly — parsing it is the actual skill.
Common error: Running
kind create clusterwhile the Docker daemon is down or unreachable:ERROR: failed to create cluster: failed to list nodes: command "docker ps -a --filter label=io.x-k8s.kind.cluster=devops --format '{{.Names}}'" failed with error: exit status 1 Command Output: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?Why: kind has no runtime of its own — it builds the “node” as a Docker container and drives it through the Docker socket. If
dockerdisn’t started, or your user isn’t in thedockergroup, kind’s very firstdocker psfails and it can’t create anything. The Kubernetes side never even begins.Fix: Start the daemon (
sudo service docker starton WSL2, or launch Docker Desktop), confirm withdocker info, then re-runkind create cluster. To drop thesudo, add yourself to the group once withsudo usermod -aG docker $USERand open a fresh shell.How you’d spot it in prod: Any kind or CI job that fails on a
docker-shaped command before touching Kubernetes is a runtime problem, not a manifest problem — check that the container runtime is up and the runner has socket access before you debug the cluster.
Common error: Running
kubectlwith no working context — before a cluster exists, or after the kubeconfig got wiped:The connection to the server localhost:8080 was refused - did you specify the right host or port?Why:
localhost:8080is kubectl’s built-in fallback when it can’t find a cluster address in its kubeconfig. It means kubectl has nowhere to send the request — either no cluster is running, or~/.kube/confighas no current context pointing at one. It is not a problem with the resource you asked for.Fix: Create the cluster (
kind create cluster) if none exists — kind writes the context for you — or select an existing one withkubectl config use-context kind-devops. Confirm withkubectl config current-context, then retry.How you’d spot it in prod: A pipeline that suddenly hits “connection to localhost:8080 refused” lost its kubeconfig or its
KUBECONFIGenv var — the cluster is usually fine; the runner just isn’t pointed at it.
Common error: Misspelling a resource type in a
kubectl getcommand — herenoesinstead ofnodes:error: the server doesn't have a resource type "noes"Why: kubectl asks the API server to resolve the word after
getagainst the resources it actually serves (nodes,pods,deployments, and their short names likeno,po). A typo matches nothing, so the API server reports that no such resource type exists — this is a client-side spelling slip, not a broken cluster.Fix: Retype the correct type:
kubectl get nodes. When unsure,kubectl api-resourceslists every resource type and its short name;kubectl get nois the valid abbreviation for nodes.How you’d spot it in prod: A script failing with “the server doesn’t have a resource type” is almost always a typo or a resource from a CRD that isn’t installed on this cluster — check the spelling and
kubectl api-resourcesbefore suspecting the API server.
Kubernetes Interview Questions
Cover the answers below and say your own version out loud first — explain what Kubernetes solves that compose can’t, and the control-plane/worker split, before you reveal each answer. Recalling before revealing is what makes these stick when an interviewer asks them cold. The four questions and answers render right after this note.
Go Deeper
Optional extras if you have ~25 more minutes today:
- 5 min — Run
kubectl get pods -n kube-system -o wideand match each control-plane pod —kube-apiserver,etcd,kube-scheduler,kube-controller-manager— back to the boxes in the concept diagram (thekindnetandkube-proxyrows are node-level networking add-ons, not drawn as boxes). Seeing the control plane as ordinary pods is the whole mental model. - 10 min — Run
kind delete cluster --name devops, thenkind create cluster --name devopsagain, timing it. Feel how disposable a kind cluster is — a full control plane in under a minute, gone and rebuilt on demand. - 10 min — Skim the official Kubernetes Components page and map each named piece to “control plane” or “node” — it’s the canonical version of today’s two-plane split.
What does Kubernetes give you that docker compose doesn't? Both
Compose brings a stack up on one host and stops there — if that host reboots or a container dies, nothing restarts it, and you can't spread load across machines. Kubernetes adds four things compose can't: self-healing (it restarts pods and reschedules them off dead nodes automatically), scaling (change the replica count and it places the new copies for you), rolling updates with automatic rollback, and scheduling across many nodes. Underneath all of it is declarative desired-state: you say what you want running, and a control loop keeps reality matching it continuously. Compose describes a stack; Kubernetes keeps that stack alive across failures and traffic changes.
Explain the control plane versus the worker nodes. Both
A Kubernetes cluster has two planes. The control plane is the brain: the API server you send every request to, etcd storing the cluster's desired state, the scheduler that decides which node each pod runs on, and controllers running the reconcile loops that keep actual state matching desired. The worker nodes are the muscle — each runs a kubelet that starts containers and reports health, plus your application pods. You talk only to the control plane, declaring what you want; the workers make it real. On kind both planes live in one Docker container by default, but the split matches a real multi-node cluster exactly.
What is declarative desired-state and reconciliation? Both
Instead of running commands step by step — start this container, now this one — you hand Kubernetes a description of the end state you want: three replicas of this image, this much memory, this service exposed. Kubernetes stores that desired state and runs control loops that constantly compare it to what's actually running, then act to close any gap. If a pod dies and actual drops to two, the loop notices and starts a third. That's reconciliation, and it's why Kubernetes self-heals: nobody re-issues a command, the loop just keeps driving reality toward the declared state. It's the same declarative idea a compose file hints at, but enforced continuously rather than once at up time.
When would you choose not to use Kubernetes? Product
Often, honestly. Kubernetes earns its complexity when you have several services, need zero-downtime deploys, or must survive a node failing. For a single app on one VM, a side project, or a low-traffic internal tool it's overkill — docker compose or even a single container is simpler to run, cheaper, and far easier to debug at 3am. The cost of Kubernetes is real: more moving parts, a steeper learning curve, and more ways to break. So I reach for it when the workload genuinely needs self-healing or multi-node scale, not because it looks good on a résumé. Starting simpler and migrating later is usually the cheaper mistake.
Mark Day 66 complete
Tomorrow you go under the hood of the cluster you just built — the API server, etcd, scheduler and kubelet — and learn to drive it fluently with kubectl.
Stuck on today’s lab? Ask in Mission 90 Q&A