Kubernetes is the industry-standard platform for running containers at scale. You don't need to operate it to understand it — but you do need a mental model to make sense of modern cloud infrastructure. This lesson gives you exactly that: how K8s works, what problem it solves, and how your Docker knowledge maps directly onto it.

1. What is Kubernetes?

Kubernetes (from the Greek for "helmsman"; abbreviated K8s) is a container orchestrator: a platform that automates the deployment, scaling, and lifecycle management of containerised applications across a cluster of machines.

It originated at Google as a public reimplementation of their internal Borg system, which had been running containers in production since 2003. Google donated it to the Cloud Native Computing Foundation (CNCF) in 2014. It is now the de-facto standard for production container workloads.

The Core Idea: Declare, Don't Impeach

With Docker you issue commands: docker run, docker stop. Kubernetes works differently — you declare the desired state in YAML and submit it to the cluster. Kubernetes's control loop continuously compares desired state with actual state and makes changes to reconcile them. If a container crashes, Kubernetes restarts it — not because you asked, but because the desired state says "3 replicas must be running."

📊 Industry: According to the 2023 CNCF Annual Survey, 96% of organisations are using or evaluating Kubernetes. But the vast majority run it as a managed service — Amazon EKS, Google GKE, Azure AKS — rather than self-hosted. You interact with K8s; a cloud provider operates the control plane for you.

2. Architecture

A Kubernetes cluster has two tiers: the Control Plane (the brain) and Worker Nodes (the muscle). You submit your desired state to the control plane; it schedules and supervises the actual workloads running on worker nodes.

Control Plane (declarative reconciliation engine) API Server single entry point etcd cluster state store Scheduler assigns pods→nodes Controller Mgr reconciliation loops ← you talk here via kubectl / API Worker Node 1 kubelet node agent kube-proxy network rules containerd (CRI) Pod A c1 c2 Pod B container Worker Node 2 kubelet node agent kube-proxy network rules containerd (CRI) Pod C container Pod D container Control plane watches desired state in etcd; scheduler places pods on nodes; kubelet runs them via containerd

Control Plane Components

  • API Server — The single entry point for all cluster operations. kubectl, CI pipelines, and internal components all talk to it. Validates and persists objects to etcd.
  • etcd — Distributed key-value store. Holds the entire cluster state. If etcd is healthy, the cluster is recoverable.
  • Scheduler — Watches for unscheduled pods and assigns them to suitable worker nodes based on resource availability and constraints.
  • Controller Manager — Runs dozens of control loops. The Deployment controller ensures the right replica count. The Node controller detects node failures. Each controller constantly reconciles actual→desired.

Worker Node Components

  • kubelet — The node agent. Talks to the API server, receives pod specs, and instructs the container runtime to start/stop containers.
  • Container Runtime — Actually runs containers. Must implement the Container Runtime Interface (CRI). Typically containerd or CRI-O. (Not Docker — see Section 4.)
  • kube-proxy — Maintains network rules on the node so Services route traffic to the correct pods.

3. Core Objects

Kubernetes has a rich API, but five objects cover 90% of what you'll encounter:

Pod — The Smallest Deployable Unit

A Pod is not a container. It is a wrapper around one or more containers that share a network namespace (same IP address, same ports) and optionally the same storage volumes. Containers inside a Pod talk to each other on localhost.

⚠️ Pods are ephemeral. When a Pod dies, it's gone. Its replacement is a new Pod with a new IP. You never manage Pods directly in production — you use a Deployment, which manages Pods for you.
Pod (shared network namespace — IP: 10.0.1.42) Container: app image: myapp:v2 port: 8080 listens on localhost:8080 Container: sidecar image: log-agent:1.3 port: 9090 scrapes localhost:8080 Shared Volume /var/log

Deployment — Desired Replica Count + Rolling Updates

A Deployment tells Kubernetes: "I always want N replicas of this Pod running." The Deployment controller watches actual replica count and creates/deletes Pods to match. It also handles rolling updates: bring up new Pods before terminating old ones, with configurable surge and unavailability limits. This gives you zero-downtime deploys for free.

Service — Stable Network Endpoint

Pods get new IPs every time they restart. A Service provides a stable DNS name and IP that other services can use. It load-balances traffic across all healthy Pods matching a label selector. Types:

  • ClusterIP — internal only (default)
  • NodePort — exposes on every node's IP at a fixed port
  • LoadBalancer — provisions a cloud load balancer (ELB, GCLB, etc.)

Namespace — Logical Isolation

Namespaces partition a cluster into virtual sub-clusters. Teams, environments (production, staging), or applications get their own namespace with separate resource quotas and RBAC policies. They share the same underlying nodes and control plane.

ConfigMap & Secret — Config Injection

ConfigMaps hold non-sensitive configuration (feature flags, connection strings). Secrets hold sensitive data (passwords, tokens) — stored base64-encoded in etcd (or encrypted at rest with KMS). Both are injected into Pods as environment variables or mounted as files, exactly like --env and bind mounts in Docker — but managed by the cluster, not the operator.

4. How Containers Fit In

Here is an important clarification that trips up many Docker users:

🐳 Not Just Docker: Kubernetes does not call Docker to run containers. It talks to a Container Runtime Interface (CRI) — an abstraction layer. The default runtime on most clusters today is containerd (which is what Docker itself uses under the hood). Others include CRI-O (Red Hat's minimal CRI runtime). In 2022, Kubernetes removed the built-in dockershim bridge that had allowed the Docker daemon to act as a CRI. If you were using the Docker runtime in K8s, you had to migrate — but the containers themselves (OCI images) didn't change at all.

This means everything you already know still applies:

  • Dockerfiles — still how you build images (or Buildpacks, or Buildah, etc.)
  • OCI images — the artefacts K8s pulls and runs, regardless of how they were built
  • Registries — Docker Hub, ECR, GCR, your private registry — K8s pulls from all of them
  • Image layers, caching, multi-stage builds — all apply unchanged

Kubernetes manages WHERE the containers run and HOW MANY. It doesn't rewrite your images. The container boundary is the same: namespaces, cgroups, the root filesystem from your image layers.

5. A Minimal Deployment

Here is the simplest real-world Kubernetes setup: one Deployment (3 replicas of your app) and one Service to expose it. This is the K8s equivalent of docker run -p 80:8080 myapp:v2 — just with scaling, self-healing, and rolling updates included.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: production
spec:
  replicas: 3                       # desired pod count (cf. docker run × 3)
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: registry.example.com/myapp:v2   # same image you built with Docker
        ports:
        - containerPort: 8080
        env:
        - name: LOG_LEVEL             # like docker run -e LOG_LEVEL=info
          valueFrom:
            configMapKeyRef:
              name: myapp-config
              key: log_level
        - name: DB_PASSWORD           # like docker run -e DB_PASSWORD=... (from Secret)
          valueFrom:
            secretKeyRef:
              name: myapp-secret
              key: db_password
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
  namespace: production
spec:
  selector:
    app: myapp                    # routes to all pods with this label
  ports:
  - port: 80
    targetPort: 8080              # like docker run -p 80:8080
  type: LoadBalancer              # provisions cloud load balancer

Apply both with: kubectl apply -f deployment.yaml -f service.yaml

Docker → Kubernetes Concept Mapping

Docker Concept Kubernetes Equivalent Notes
docker run myimage Pod spec with container image K8s schedules it; you don't pick the host
-p 80:8080 Service (ClusterIP / LoadBalancer) Stable DNS, not ephemeral port
--env KEY=VAL ConfigMap / Secret env injection Cluster-managed; rotatable without redeploy
-v /host:/container PersistentVolumeClaim (PVC) Decoupled from node; survives pod restarts
docker-compose scale web=3 Deployment replicas: 3 Built into the object, not a separate command
docker-compose service Deployment + Service pair K8s separates compute from networking
Docker network Namespace + CNI plugin Every Pod gets its own IP by default
Docker registry Same — any OCI registry ECR, GCR, Docker Hub, private — unchanged
Dockerfile / image Same — OCI image K8s pulls your existing images, no changes needed
Restart policy Pod restartPolicy + liveness probes More granular; probes define "healthy"

6. When Kubernetes is Overkill

Kubernetes solves real problems — but only at a scale where those problems exist. Choosing it for every project is like hiring a freight logistics company to deliver a pizza.

The K8s Tax

  • Complexity — Dozens of objects, controllers, and networking layers. The learning curve is steep.
  • YAML sprawl — Real clusters have hundreds of manifests. Helm, Kustomize, and ArgoCD exist just to manage the YAML.
  • Operational overhead — Even managed K8s requires node pool management, upgrade windows, and understanding of failure modes.
  • Cost — Control plane fees + node compute. A minimal EKS cluster costs ~$70/month before a single workload runs.

Simpler Alternatives

Alternative Best For Trade-off
Cloud Run / App Runner Stateless HTTP services, scale-to-zero Less control; vendor lock-in
AWS Fargate / ECS Teams already on AWS, simpler ops AWS-specific; less portable
Docker Swarm Small clusters, Docker-native teams Limited ecosystem; declining adoption
HashiCorp Nomad Mixed workloads (containers + binaries + VMs) Smaller community than K8s
Fly.io / Railway / Render Startups, side projects, fast deploys Less customisability at scale
Rule of thumb: If you're a team of 1–5 engineers with a single-region, single-service application, start with a managed platform (Cloud Run, Fargate, Fly.io). Reach for K8s when you have multiple services, complex traffic routing, multi-tenancy needs, or a platform team that can own cluster operations. Managed K8s (EKS, GKE, AKS) dramatically lowers the bar — but the application-layer complexity remains yours.

🧠 Knowledge Check

Key Takeaways

  • K8s is a declarative reconciliation system — you describe desired state; it makes it so, continuously
  • Architecture = Control Plane + Worker Nodes — API server, etcd, scheduler, controller manager; kubelet + containerd + kube-proxy per node
  • Pod ≠ container — a Pod wraps one or more containers sharing a network namespace; always manage Pods via Deployments
  • Services give Pods a stable identity — DNS name + load balancing over ephemeral Pod IPs
  • K8s doesn't run Docker — it runs containerd/CRI-O via CRI; your images and Dockerfiles are unchanged
  • Docker → K8s mapping is direct — image, ports, env vars, volumes all have K8s equivalents
  • K8s has a real cost — complexity, YAML, ops burden; managed services are often the right call for small teams
  • 96% of orgs evaluate K8s — but managed K8s (EKS, GKE, AKS) dominates; you interact with it, a provider operates it