How do you keep a distributed application available, scaled and up to date while its containers and machines keep failing?
Containers make a service easy to package and start. Production asks for more: survive dying containers and machines, follow the load, change version without downtime. You declare the state you want as objects in a replicated store, and independent control loops keep comparing it with the state they observe and act to close the gap. Self-healing, rolling updates and autoscaling are that one loop applied to different objects: a ReplicaSet counts Pods, a Deployment moves them from one version to the next, an autoscaler rewrites the count from measured load. The lecture's demo goes from a Docker Compose file to an autoscaled cluster.
The lecture opens with four motivations:
“Production-ready” means those quality attributes, mostly the dependability attributes of Ch. 4. The lecture ties each feature to the ones it improves:
| Attributes | Kubernetes feature |
|---|---|
| Availability, scalability | autoscaling |
| Recoverability, reliability | self-healing |
| Configurability, maintainability | declarative configuration |
| Observability | monitoring integrations |
Correctness of the application's state is not on the list: Kubernetes manages processes, not your algorithm.
A lightweight form of virtualisation. It provides process and filesystem isolation by exploiting the Linux kernel, and it has replaced traditional virtual machines in many deployment scenarios. The most popular platform is Docker.
A container is a process sharing the host kernel, with an image that carries the application and its dependencies. Kernel namespaces restrict what it sees (PIDs, network, hostname, mounts); cgroups limit what it consumes. (Details: Giovanni Ciatto's lecture.)
Many containers on many hosts raise this course's questions: where each runs, who restarts it, how clients find it after it moves, how to replace it while it serves. Answering them automatically is container orchestration.
A portable, extensible, open source platform for managing containerised workloads and services.
What Kubernetes is not
| Docker Swarm | Kubernetes | |
|---|---|---|
| 1 · what it is | Docker's native clustering and orchestration tool | open-source container orchestration platform |
| 2 · complexity | simpler, easier to set up | more complex, more feature-rich |
| 3 · fits | smaller clusters, less complex applications | larger clusters, more complex applications |
| 4 · scaling | users can handle resource allocation and scaling manually | scaling, load balancing and failover handled automatically |
| 5 · configuration | docker-compose.yml | YAML files (manifests) |
| 6 · access control | simple, TLS-based | Role-Based Access Control (RBAC) |
Five key features (slides)
The first two key features are one design decision.
Declarative configuration. “Everything is an object”: many kinds of objects, written in YAML (or JSON), shape the environment and are managed with the declarative tool kubectl (kubectl create -f file.yaml). It improves configurability and maintainability.
Immutability. The slides: resources cannot be changed after creation; a change deletes the resource and creates a new one. Containers are ephemeral and stateless, so recreating them is routine. This ensures consistency and reliability, and changes produce new desired states rather than mutating existing ones.
Objects are persistent entities that Kubernetes uses to represent the state of the cluster:
An object is a record of intent. Once it is created, the system constantly works to ensure that it exists. Creating it tells Kubernetes what the cluster should look like: its desired state.
Almost every object has two nested fields. spec describes the characteristics you want: the desired state. status describes the current state, supplied and updated by Kubernetes. The control plane continually manages every object's actual state to match the desired state.
That last sentence is the whole machine. A controller runs a loop that never ends: observe spec and actual state, compute the difference, take an action that shrinks it (create a Pod, delete one, bind one to a node), repeat. This is the reconciliation loop.
Four consequences link this lecture to the rest of the course.
A Kubernetes cluster consists of a control plane plus a set of worker machines, called nodes.
Control plane: manages the cluster
kubectl, talks only to it.Worker node: runs the containerised applications
A plugin interface that lets Kubernetes use a wide variety of container runtimes; one must be working on each node. Kubernetes is not tied to Docker: other supported runtimes are containerd, CRI-O and Mirantis Container Runtime.
Kubernetes once drove Docker Engine through a built-in adapter, dockershim, which Kubernetes deprecated in v1.20 and removed in v1.24; Docker Engine now plugs in through the external cri-dockerd adapter. Images built with docker build are standard OCI images and run unchanged on containerd or CRI-O.
Creating an object means writing its spec plus basic information such as a name, usually in a manifest: a YAML file applied with kubectl apply -f <file> (the slides' first one is a single-container nginx:1.14.2 Pod on port 80). Required fields:
apiVersion: the version of the Kubernetes API to use.kind: the type of object.metadata: data that uniquely identifies it (name, UID); labels too.spec: the desired state, with a format specific to each kind.Pods are the smallest deployable units: a group of one or more containers with shared storage and network resources. The shared context is a set of Linux namespaces, cgroups and potentially other facets of isolation, as for a container.
Two ways to use Pods
Containers in a Pod share
localhost).A container spec gives image, command, exposed port and protocol. The resources field manages hardware: requests is the CPU/memory Kubernetes guarantees to the container (the scheduler places Pods by it), limits the maximum it can use. CPU is in millicpu (0.1 CPU = 10% = 100m), memory in bytes.
A ReplicaSet manages the number of replicas of a Pod, scaling them up and down to guarantee the availability of a specified number of identical Pods. It creates new Pods from its Pod template, and is usually managed through a Deployment.
A Deployment manages a set of Pods running an application workload. It provides declarative updates for Pods and ReplicaSets, rollout (current state → desired state, e.g. a new image version, avoiding downtime) and rollback, and is used to release new versions.
Deployment spec
replicas: the number of desired Pod replicas.selector: how the Deployment finds its Pods, by label.template: the Pod specification for new Pods.Other workload objects
A Service is an abstraction defining a logical set of Pods and a policy to access them: the entry point of the application. Pods are ephemeral, move between nodes and change IP; a Service gives a stable endpoint (IP address and DNS name) for the Pods matching its label selector.
In Ch. 1's terms: location, migration and replication transparency.
A web application that counts visits: a backend (Python + Flask) and an in-memory key-value store (Redis), at github.com/Mala1180/kubernetes-hpa-example. Three phases:
The app. / runs cache.incr("hits") on Redis and answers “This page has been visited n times” plus the hostname of the serving Pod. /busy-wait burns CPU. The Redis address comes from REDIS_HOST (default redis) and REDIS_PORT (6379), so the backend keeps no state of its own and can be replicated freely.
Compose. A Dockerfile (python:3.12-slim, pip/Poetry, EXPOSE 3000) builds the backend. The Compose file has backend (ports 3000:3000, the two variables, depends_on: redis) and redis (redis:7-alpine). docker compose up -d --build, then localhost:3000.
Migration. For each Compose service, define a Deployment and a Service, then add a HorizontalPodAutoscaler for the backend. The Service redis gets the DNS name redis, as in Compose, so the code does not change.
The lecture's backend-deployment.yaml and backend-service.yaml. Click a line to see what it declares and who acts on it.
The autoscaler (backend-hpa.yaml, autoscaling/v2) targets Deployment backend, minReplicas: 1, maxReplicas: 10, with average utilisation targets of 50% CPU and 70% memory (§7).
Kompose converts Compose files into manifests, steered by labels in compose.yml: kompose.service.type: nodeport, kompose.controller.type: deployment, kompose.image-pull-policy: always. Then kompose convert -f docker-compose.yml -o k8s.
Running it locally
minikube start: a local cluster.minikube addons enable metrics-server: the HPA needs its readings.kubectl apply -f k8s: apply the manifests.minikube service backend, or kubectl port-forward svc/backend 8080:3000 and localhost:8080.minikube dashboard: monitor the system.Only the backend is replicated. The counter lives in one Redis Pod with no volume, a single point of failure. Making it highly available is a Ch. 5 problem, not a Deployment setting.
The loop of §3 meets three kinds of change: more load, a failure, a new version.
Kubernetes scales automatically on resource usage or other metrics, in two ways:
It improves availability and scalability. The HorizontalPodAutoscaler is one more control loop whose output is another object's spec: it rewrites the Deployment's replicas. The slides say it triggers when CPU exceeds 50% or memory 70%; these are targets it steers towards, as percentages of each Pod's request (50% of 100m = 50m per Pod on average), so an HPA needs requests; and it scales down as well as up.
Per metric, desired = ⌈current replicas × current utilisation / target⌉, clamped to [min, max]; with several metrics the largest wins. Deviations within ~10% are ignored, and scale-down waits out a stabilisation window (5 minutes by default). The CPU limit caps what a Pod can report: at 200m, 200% of its request.
The stress test
stress-test.sh against the backend.This improves recoverability and reliability, via two loops. The kubelet restarts a crashed container in place, with growing back-off. The ReplicaSet controller replaces Pods that disappear. A dead node stops sending heartbeats; after a grace period the node controller marks it NotReady, and after a further toleration (5 minutes by default) evicts its Pods. Only those Pods are recreated, wherever the scheduler finds room. Detection is by timeout, so a slow node looks like a dead one (partial failure, Ch. 4), and until it fires the Service may still route to the silent node.
Changing the image in the template (kubectl set image, or re-applying an edited manifest) starts a rollout: the Deployment creates a new ReplicaSet and scales it up while scaling the old one down.
maxSurge bounds how many Pods may exist above replicas, maxUnavailable how many may be missing (both default 25%, never both 0). Beyond maxUnavailable, old Pods go only as new ones become ready, as readiness probes report. If the new version never gets ready, the rollout stalls and after progressDeadlineSeconds (600 s) reports ProgressDeadlineExceeded. It does not roll back by itself: you run kubectl rollout undo.
The backend on three nodes (4 Pod slots each). Break something or change the spec, then Step (one reconciliation round) or Run; click a Pod to delete it. Try: fail a node · roll out v2 · roll out broken v3, wait, undo · HPA on, raise the load.
After scalability, availability and reliability, one more quality attribute: a production system will eventually go down, for whatever reason.
An observable system allows understanding what happened, or what is happening, inside it.
Monitoring tools help achieve it. A monitoring system can:
The most common pair:
The lecture's Grafana dashboard shows Prometheus metrics during the stress test.
A script encodes a path from a known state; a declaration encodes the target, and a loop converges to it from any state, so first deployment, crash and lost node share one code path.
Heartbeats stop; the node goes NotReady and, after a toleration period, its Pods are evicted. The ReplicaSet sees 3/5 and creates 2; the scheduler binds them where there is room (or they stay Pending); once ready, the Service routes to them. The other 3 are untouched.
The backend is stateless, so replicas are interchangeable. Several Redis Pods would be independent counters unless they replicate, and then Ch. 5's consistency choices and CAP apply. A StatefulSet gives identity and storage, not consistency.
In etcd (Raft). Controllers never agree with each other: each reconciles its own objects through the API server, so only the stored desired state needs agreement.
joiner service already runs with container-id names); give the seed a stable hostname with a StatefulSet and a headless Service.| Slides | Content | Here |
|---|---|---|
| CX · Motivations, Premises | Four motivations; containerisation | §1, §2 |
| CX · Introduction, Swarm I–II | Is / is not; six-point comparison | §2 |
| CX · Key features I–II | Immutability; declarative config | §3 |
| CX · Key features III–IV | Autoscaling; self-healing | §7 |
| CX · Key features V | CRI | §4 |
| CX · Architecture I–II | Control plane; nodes | §4 |
| CX · Objects I–II | Record of intent; spec/status | §3 |
| CX · Objects III … Pods IV | Manifests; Pods; resources | §5 |
| CX · ReplicaSets … Services II | Workloads; Services | §5 |
| CX · Example … Migrating VIII | Compose, manifests, HPA, Kompose, Minikube | §6 |
| CX · Stress testing I–IV | Load test, HPA reaction | §7 |
| CX · System monitoring I–III | Observability; Prometheus, Grafana | §8 |