Skip to content

Phase 4 · ORCHESTRATION & IAC

Why Kubernetes — the problems it solves

Day 66 of 90 ~45 min 0/20 in phase Builds on Day 65

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

Section 1 of 5 · ~3 min

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.

The Kubernetes cluster split: you send desired state to the control plane (API server, etcd, scheduler, controllers), which schedules and keeps pods running on the worker nodes, each managed by a kubelet. kubectl (you) declare state desired state Control plane (brain) API server · etcd scheduler controllers (reconcile) schedule pods Worker node (muscle) kubelet + your pods Worker node (muscle) kubelet + your pods
You declare state to the control plane; it schedules and keeps your pods alive on the worker nodes.

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

Section 2 of 5 · ~3 min

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

Section 3 of 5 · ~3 min

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 cluster while 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 dockerd isn’t started, or your user isn’t in the docker group, kind’s very first docker ps fails and it can’t create anything. The Kubernetes side never even begins.

Fix: Start the daemon (sudo service docker start on WSL2, or launch Docker Desktop), confirm with docker info, then re-run kind create cluster. To drop the sudo, add yourself to the group once with sudo usermod -aG docker $USER and 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 kubectl with 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:8080 is 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/config has 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 with kubectl config use-context kind-devops. Confirm with kubectl 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 KUBECONFIG env var — the cluster is usually fine; the runner just isn’t pointed at it.

Common error: Misspelling a resource type in a kubectl get command — here noes instead of nodes:

error: the server doesn't have a resource type "noes"

Why: kubectl asks the API server to resolve the word after get against the resources it actually serves (nodes, pods, deployments, and their short names like no, 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-resources lists every resource type and its short name; kubectl get no is 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-resources before suspecting the API server.

Kubernetes Interview Questions

Section 4 of 5 · ~1 min

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

Section 5 of 5 · ~1 min

Optional extras if you have ~25 more minutes today:

  • 5 min — Run kubectl get pods -n kube-system -o wide and match each control-plane pod — kube-apiserver, etcd, kube-scheduler, kube-controller-manager — back to the boxes in the concept diagram (the kindnet and kube-proxy rows 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, then kind create cluster --name devops again, 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