K8s Basics #1: What Is Kubernetes — Why Do We Need a Container Orchestrator?

12 min read

If you finish the Docker Basics series, you can build a container, run it, persist data, and push it to a registry. The next question follows naturally — I can launch one container. But how do I run 100 of them, restart them automatically when they die, and scale up and down with traffic? The de facto standard answer is Kubernetes (K8s). This series follows K8s from the start.

This series is K8s Basics, 7 posts.

  • #1 What is Kubernetes — why do we need a container orchestrator? ← this post
  • #2 Local environments — minikube / kind / Docker Desktop k8s
  • #3 kubectl and your first Pod
  • #4 Deployment / ReplicaSet
  • #5 Service — ClusterIP / NodePort / LoadBalancer
  • #6 ConfigMap / Secret
  • #7 Namespaces and labels

This post covers the limits of single-container tooling, the problems K8s solves, and then the big picture of desired state / reconcile loops and control plane / worker nodes. Hands-on starts in #2.

Tip
The hands-on posts in this series have you write YAML manifests by hand. One misplaced indent or quote sends kubectl apply into an error that points away from the real cause, leaving you to trace it back from the cluster side. Pasting the manifest into utilrepo’s YAML validator before applying surfaces syntax errors with line and column numbers. utilrepo is a collection of lightweight web utilities that run in your browser, so secrets never leave your machine, and it also catches multi-document manifests joined by --- and tab-space mixes you’d otherwise miss.

When docker run stops being enough #

The last post of the Docker Basics series (#6) ended with a production-ready single-container command. It looked like this:

One container — the destination of Docker Basics
docker run -d --name myapp \
  --restart unless-stopped \
  -p 127.0.0.1:8000:8000 \
  -v myapp-data:/app/data \
  ghcr.io/curtis/myapp:1.0.0

For one container on one host, or for web + db + cache bundled together with Compose on the same host, this is enough. But once traffic grows, zero-downtime deploys are required, and the service has to stay up even when a server dies — the same tooling can’t solve the new problems that show up.

1) When the node dies, the service dies with it. A container started by docker run is bound to that host. If the host reboots or the disk fails, there’s no standard way to move the container to another machine. Someone has to SSH into a new machine and rerun the command by hand.

2) Auto-recovery is weak when the container dies. --restart unless-stopped only works inside the same host. If the host itself is alive, the Docker daemon will restart the container — but if the host is gone, that’s the end. And it doesn’t catch partial failures like “the process is alive but stops responding.”

3) Scaling instances with traffic is fully manual. To scale an API server from 2 to 10 during a campaign, someone has to provision new machines, pull the same image and run the same command 10 times, then update the load balancer config by hand. Then scale back down when it’s over. A lot of human work.

4) Zero-downtime deploys (rolling updates) have to be hand-rolled. Rolling from v1.0.0 to v1.1.0 by replacing instances one at a time, while only sending traffic to containers that pass health checks, is hard to script by hand. Add automatic rollback on failure and it’s harder still.

5) Config, secrets, and service discovery have no standard. DB credentials need to be injected as environment variables, but Docker’s built-in tooling doesn’t have a standard way to inject different values per environment (stage / prod) safely. The name container A uses to find container B (service discovery) has to be wired up by hand.

These five gaps are the ceiling of single-host, single-container tooling. Closing those gaps is the job of a container orchestrator.

What container orchestrators actually solve #

Abstract the five items above into one line:

Bundle multiple machines into one cluster, declare what shape it should run in, and let the system maintain that shape on its own.

The names for this are declarative desired state and the reconcile loop.

  • Declarative — you write what should be true (what), not how to make it happen (how). Example: “3 replicas of myapp should always be running.”
  • Reconcile loop — the system constantly compares actual state with desired state and moves to close the gap. If one container dies and only 2 remain, the system spins up one more on its own to get back to 3.
Reconcile loop
   ┌──────────────────┐         ┌──────────────────┐
   │  desired state   │         │   actual state   │
   │  "myapp 3 replicas"│ ◀───▶  │  2 currently up  │
   └──────────────────┘ compare └──────────────────┘
                  │                       │
                  └────────┬──────────────┘
                    ┌─────────────┐
                    │  controller │  → diff → "spin up one more"
                    └─────────────┘

This is the core idea of K8s. A human declares “this is how it should look,” and the system keeps it that way. A node dies, a new version rolls out gradually, traffic scales 5 → 8 — all of it goes through the same model.

Where Kubernetes came from #

K8s didn’t invent this model. Its roots are in Borg, a cluster management system Google ran in-house for over a decade. Google services like Search, Gmail, and Maps ran on Borg, and out of that operational experience came the next-generation system Omega. Then Kubernetes was a clean-room reimplementation of those ideas as open source.

The rough timeline:

  • June 2014 — Google open-sources the early version of Kubernetes
  • July 2015 — Kubernetes 1.0 ships. At the same time CNCF (Cloud Native Computing Foundation) is founded, and K8s is donated as its first project
  • After that — every major cloud (AWS EKS, GCP GKE, Azure AKS) offers managed K8s, and it becomes the de facto standard for container orchestration

“Kubernetes” is Greek for helmsman. The shorthand K8s abbreviates the eight letters between K and s. The two are used interchangeably and refer to the same thing.

The big picture of a K8s cluster #

A K8s cluster is built from two kinds of nodes — control plane and worker nodes.

K8s cluster — components at a glance
                ┌───────────────────────────────────┐
                │           Control Plane            │
                │                                    │
   kubectl ───▶ │   kube-apiserver  ◀──────┐         │
                │        │                 │         │
                │        ▼                 │         │
                │       etcd               │         │
                │                          │         │
                │   kube-scheduler  ───────┤         │
                │   kube-controller-manager ─┤       │
                │   cloud-controller-manager ┘       │
                └────────────┬───────────────────────┘
        ┌────────────────────┼────────────────────┐
        ▼                    ▼                    ▼
 ┌────────────────┐  ┌────────────────┐  ┌────────────────┐
 │  Worker Node 1 │  │  Worker Node 2 │  │  Worker Node 3 │
 │                │  │                │  │                │
 │   kubelet      │  │   kubelet      │  │   kubelet      │
 │   kube-proxy   │  │   kube-proxy   │  │   kube-proxy   │
 │   containerd   │  │   containerd   │  │   containerd   │
 │                │  │                │  │                │
 │   ┌──────┐     │  │   ┌──────┐     │  │   ┌──────┐     │
 │   │ Pod  │ ... │  │   │ Pod  │ ... │  │   │ Pod  │ ... │
 │   └──────┘     │  │   └──────┘     │  │   └──────┘     │
 └────────────────┘  └────────────────┘  └────────────────┘

Control plane — the brain of the cluster #

The control plane receives and stores the cluster’s desired state and decides what’s needed to keep that state in place.

  • kube-apiserver — the front door of the cluster. Every tool, including kubectl, sends REST requests here. The API server runs auth, authorization, and validation, then writes the result to etcd.
  • etcd — a distributed key-value store that holds all cluster state (which Pods should exist, which Services are defined, etc.). It is the single source of truth for the cluster.
  • kube-scheduler — decides which worker node a newly created Pod should land on, based on available resources, labels, and constraints.
  • kube-controller-manager — runs many controllers in a single process. This is the actual machine running the reconcile loops described above. Example: the ReplicaSet controller continuously compares the declaration “3 Pods should be up” with the actual count.
  • cloud-controller-manager — on clusters that sit on top of a cloud (AWS / GCP / Azure / etc.), connects that cloud’s load balancers, disks, and node objects to K8s resources. Self-hosted on-prem clusters might not have it.

Worker node — where the work happens #

A worker node is the machine that actually runs containers. Three things are typically installed on each one:

  • kubelet — the node’s agent. Receives instructions from the API server like “run this Pod on this node,” asks the container runtime to actually run it, watches container health, and reports state back to the API server.
  • kube-proxy — manages the node’s networking rules (iptables/IPVS) so a Service’s (#5) virtual IP gets delivered to real Pods.
  • container runtime — the layer that actually runs containers. The current standard is containerd; CRI-O is also common. The shim K8s used to talk to the Docker daemon directly (dockershim) was removed in Kubernetes 1.24 (2022). So “K8s uses Docker” is no longer accurate. K8s uses an OCI-compatible runtime, and Docker-built images (which are OCI images) still run unchanged.

The flow in one sweep #

What happens the moment you run kubectl apply -f deployment.yaml, in short:

  1. kubectl sends a request to the API server: “this Deployment should exist”
  2. The API server runs auth and validation and writes the fact to etcd
  3. The Deployment controller in controller-manager decides “we need 3 more Pods” and creates 3 Pod objects
  4. The scheduler assigns each Pod to a node
  5. The kubelet on each chosen node sees the assignment and asks containerd to run the container
  6. The kubelet reports the result back to the API server, and kubectl get pods shows that state

The whole thing is one pattern — desired state is recorded in etcd via the API server, and each component reconciles its own slice of it.

The core resources in one table #

The unit a person works with in K8s is not a container, but a resource (a.k.a. object) — Pod, Deployment, Service, and so on. Each gets its own deep dive in later posts; here’s a single table to fix the shape in your head ahead of time.

ResourceOne-line definitionWhere it’s covered
PodThe smallest execution unit in K8s — one container, or a few containers that run together and share an IP#3
ReplicaSetGuarantees “N replicas of this Pod template are running”#4
DeploymentSits on top of ReplicaSet to handle rolling updates and rollbacks. The unit people touch most often#4
ServiceA network abstraction that puts a stable virtual IP / DNS name in front of a set of Pods#5
ConfigMapSplits configuration (env vars, config files) out of the code and injects it#6
SecretSplits secrets (passwords, tokens, certs) out for injection. base64 encoding plus access control#6
NamespaceLogical partition inside a cluster — separates permissions, resource quotas, and name collisions#7

Get comfortable with these six and you can handle most day-to-day work. Deeper resources (Job/CronJob, StatefulSet, Ingress, PersistentVolume) are the K8s Intermediate territory.

How is this different from Docker Compose? #

The first comparison that comes to mind for Docker Basics readers is Docker Compose. Compose also declares multiple containers in one YAML file, which feels K8s-like. But the scope of the problem each one solves is different.

Docker ComposeKubernetes
ScopeMulti-container on one hostContainer workloads across many nodes (a cluster)
Self-healingRestarts on the same host, at mostAuto-relocates to another node when one dies
ScalingManual via --scaleDeclared as desired state → maintained automatically; autoscaling possible
Zero-downtime deploysDIYBuilt into Deployment’s rolling update
Config / secrets.env, secrets at mostConfigMap / Secret are first-class resources
Where it fitsLocal development, single-server opsMulti-machine ops / serious SLA environments

Compose simplifies dev and ops on a single host. K8s steps in for environments past that ceiling. They’re not really competitors — they solve different stages of the same problem. Local development is still lighter on Compose, and serious production naturally lands on K8s or its managed variants.

What this series will cover #

K8s is a wide topic. Let me pin down upfront what these 7 posts will cover.

What this K8s Basics series covers:

  • Spinning up a K8s cluster locally (#2)
  • Launching your first Pod with kubectl and inspecting it (#3)
  • Running multiple Pods reliably with Deployment, deploying new versions (#4)
  • Creating internal and external access with Service (#5)
  • Handling config and secrets with ConfigMap / Secret (#6)
  • Organizing a cluster with namespaces and labels (#7)

After these 7 posts, you’ll be able to deploy and operate a small web app the K8s way on a local cluster.

What’s left for follow-up series:

TopicSeries
Ingress, PersistentVolume, StatefulSet, Job/CronJob, RBACK8s Intermediate
Helm, kustomize, GitOps (Argo CD/Flux), multi-clusterK8s in Practice
Bootstrapping clusters yourself (kubeadm), upgrades, backup/restore, security hardeningK8s Operations
Managed clusters — EKS / GKE / AKSCloud K8s series

In the basics series, the focus is on a single cycle — defining resources in YAML, applying them to the cluster with kubectl, and reading back the result.

Summary #

The picture this post pinned down:

  • Single-container tooling alone can’t solve five things: node failure, auto-recovery, scaling, zero-downtime deploys, and config management. That gap is the reason container orchestrators exist.
  • The core idea of K8s is declarative desired state + reconcile loop. You write only “what should be true,” and the system maintains that shape continuously.
  • K8s is an open-source reimplementation of Google’s internal Borg. Since 1.0 in 2015 it was donated to CNCF and has become the de facto standard.
  • A cluster splits into control plane (kube-apiserver / etcd / kube-scheduler / kube-controller-manager / cloud-controller-manager) and worker nodes (kubelet / kube-proxy / containerd). Every flow comes down to the same pattern: desired state goes through the API server into etcd, and each component reconciles its slice of it.
  • The six resources you’ll work with day-to-day are Pod / Deployment / Service / ConfigMap / Secret / Namespace. This series covers them one post at a time.
  • Docker Compose is one host; K8s is many nodes. They look similar but solve different stages of the same problem.

Next — getting K8s running locally #

This post covered why K8s exists and the big picture. The next post walks through bringing up a real cluster locally.

#2 Local environments — minikube / kind / Docker Desktop k8s covers (1) the three ways to try K8s locally (minikube / kind / Docker Desktop’s built-in K8s), (2) the trade-offs and when to pick which, (3) installing kubectl and switching contexts for the first time, and (4) checking your first cluster with kubectl get nodes. Once you’ve got a local cluster ready, #3 is next.

X