Part III — Models and machinery · Chapter 9

Running it in production: Kubernetes

~25 min2 instrumentsA.Y. 2025/26 · Prof. Omicini
CX Addressing production-ready distributed systems through Kubernetes (M. Matteini)
The question

How do you keep a distributed application available, scaled and up to date while its containers and machines keep failing?

The argument in one paragraph

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.

What “production-ready” asks for

The lecture opens with four motivations:

  1. Containerisation frameworks such as Docker opened new ways to develop and deploy distributed systems.
  2. Most software systems today are distributed, and complex.
  3. Non-functional requirements (quality attributes) matter more and more in the design of these systems.
  4. Kubernetes is now the de facto standard for container orchestration.

“Production-ready” means those quality attributes, mostly the dependability attributes of Ch. 4. The lecture ties each feature to the ones it improves:

AttributesKubernetes feature
Availability, scalabilityautoscaling
Recoverability, reliabilityself-healing
Configurability, maintainabilitydeclarative configuration
Observabilitymonitoring integrations

Correctness of the application's state is not on the list: Kubernetes manages processes, not your algorithm.

Containers, and why they need orchestrating

Containerisation[Matteini, 2025; Docker]

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.

Kubernetes[Kubernetes docs]

A portable, extensible, open source platform for managing containerised workloads and services.

What Kubernetes is not

  1. A containerisation platform like Docker: it uses a runtime it does not implement.
  2. A Platform as a Service like Heroku.
  3. A cloud provider, though it works with them.
  4. A deployment tool. CI/CD is defined by each organisation.
  5. A logging/monitoring tool, though it offers integrations for them.

Kubernetes and Docker Swarm

Docker SwarmKubernetes
1 · what it isDocker's native clustering and orchestration toolopen-source container orchestration platform
2 · complexitysimpler, easier to set upmore complex, more feature-rich
3 · fitssmaller clusters, less complex applicationslarger clusters, more complex applications
4 · scalingusers can handle resource allocation and scaling manuallyscaling, load balancing and failover handled automatically
5 · configurationdocker-compose.ymlYAML files (manifests)
6 · access controlsimple, TLS-basedRole-Based Access Control (RBAC)

Five key features (slides)

  1. Immutability (§3)
  2. Declarative configuration (§3)
  3. Autoscaling (§7)
  4. Self-healing (§7)
  5. Container Runtime Interface (§4)

The one idea: desired state and the control loop

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.

Slide fix · what is immutable

Too strong. What you never patch is a running container: you replace it. Objects are updated in place all the time: kubectl scale patches a Deployment's replica count, kubectl apply its template, which starts a rollout. Only some fields are immutable once set (a Deployment's selector, most of an existing Pod's spec); changing those means deleting and recreating the object. Also, kubectl create is imperative; apply is declarative.

Kubernetes object[Matteini, 2025]

Objects are persistent entities that Kubernetes uses to represent the state of the cluster:

  1. what containerised applications are running, and on which nodes;
  2. the resources available to those applications;
  3. the policies for application behaviour, such as restarts and upgrades.

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.

Spec and status[Matteini, 2025]

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.

Desired state spec: replicas 3 in etcd, via API server Observed state status: 2 ready reported by kubelets Controller observe → diff → act diff = +1 Cluster Pods on nodes you: kubectl apply -f deployment.yaml act: create 1 Podreport repeat forever
Fig. 9.1 — The reconciliation loop. You write only the left box; controllers move the right one towards it.

Four consequences link this lecture to the rest of the course.

Cluster architecture

A Kubernetes cluster consists of a control plane plus a set of worker machines, called nodes.

Control plane: manages the cluster

  1. kube-apiserver exposes the Kubernetes HTTP API. Every other component, and kubectl, talks only to it.
  2. etcd: key-value database storing the cluster metadata (every object, spec and status).
  3. kube-scheduler watches for newly created Pods with no assigned node and selects a node for them.
  4. kube-controller-manager runs the controller processes (ReplicaSet, Deployment, node controller, …).
  5. cloud-controller-manager embeds cloud-specific control logic (e.g. cloud load balancers).
Slide fix · what the scheduler watches

The slide says it watches newly created services with no assigned node. Services are never assigned to nodes: the scheduler watches unscheduled Pods and binds each to a node with room for its resource requests.

Worker node: runs the containerised applications

  1. kubelet ensures that the containers of the Pods bound to its node are running, and reports their status: the node's local control loop.
  2. kube-proxy maintains network rules so that traffic to a Service reaches one of its Pods.
  3. container runtime: the software that runs containers (e.g. Docker).
CONTROL PLANE kube-apiserver etcdreplicated · Raft kube-scheduler controller-manager cloud-controller-mgr NODE 1 NODE 2 kubelet kube-proxy container runtimevia CRI Pod Pod kubelet kube-proxy container runtimevia CRI Pod watch / report
Fig. 9.2 — Hub and spokes: everything talks only to the API server, and only it touches etcd.
Container Runtime Interface (CRI)[Matteini, 2025]

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.

Beyond the slides · Docker images vs Docker Engine

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.

Objects: Pods, ReplicaSets, Deployments, Services

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:

  1. apiVersion: the version of the Kubernetes API to use.
  2. kind: the type of object.
  3. metadata: data that uniquely identifies it (name, UID); labels too.
  4. spec: the desired state, with a format specific to each kind.
HPA Deployment ReplicaSet Service Podapp=backend Podapp=backend Podapp=backend scalesownsselects by labelcreates Pods from its template
Fig. 9.3 — Ownership runs down a chain of controllers; the Service finds the Pods by label.

Pod

Pod[Matteini, 2025]

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

  1. One container, the most common case: the Pod wraps it.
  2. Several co-located, tightly coupled containers: an advanced use, for patterns like Sidecar, Ambassador and Adapter [Burns, 2024].

Containers in a Pod share

  1. IP address and ports (they reach each other on localhost).
  2. Hostname.
  3. Storage volumes.
  4. Process identifiers (PIDs).
Slide fix · PIDs are shared only on request

Network and hostname are shared by default, volumes by the containers that mount them; the PID namespace only if the Pod sets shareProcessNamespace: true.

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.

Slide check · Mi is not MB

The slide's “122Mi = 128MB” holds only approximately: Mi is a mebibyte (220 bytes), so 122Mi ≈ 127.9 MB. The manifests' 128Mi is 134,217,728 bytes, about 134 MB. M (106) is a different suffix.

ReplicaSet and Deployment

ReplicaSet[Matteini, 2025]

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.

Deployment[Matteini, 2025]

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

  1. replicas: the number of desired Pod replicas.
  2. selector: how the Deployment finds its Pods, by label.
  3. template: the Pod specification for new Pods.

Other workload objects

  1. Job: short-lived, one-off tasks.
  2. CronJob: regular scheduled actions (backups, reports).
  3. StatefulSet: like a Deployment, but each Pod keeps a sticky identity.
  4. DaemonSet: a copy of a Pod on all (or some) nodes, added and garbage-collected as nodes join and leave.

Service

Service[Matteini, 2025]

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.

  1. ClusterIP (default): a cluster-internal IP.
  2. NodePort: each node's IP, at a static port.
  3. LoadBalancer: an external load balancer from a cloud provider (or MetalLB on-premise).

In Ch. 1's terms: location, migration and replication transparency.

From Docker Compose to Kubernetes: the hit counter

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:

  1. Development with Docker Compose.
  2. Migration to Kubernetes.
  3. Monitoring with Prometheus and Grafana.

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.

Instrument 1Anatomy of the backend manifest

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

  1. minikube start: a local cluster.
  2. minikube addons enable metrics-server: the HPA needs its readings.
  3. kubectl apply -f k8s: apply the manifests.
  4. minikube service backend, or kubectl port-forward svc/backend 8080:3000 and localhost:8080.
  5. 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.

Scaling, self-healing and rolling updates

The loop of §3 meets three kinds of change: more load, a failure, a new version.

Autoscaling

Kubernetes scales automatically on resource usage or other metrics, in two ways:

  1. Horizontal scaling: more or fewer replicas of a resource.
  2. Vertical scaling: more or less CPU/memory for a container.

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.

Beyond the slides · the HPA rule

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

  1. Run stress-test.sh against the backend.
  2. The page immediately becomes unresponsive.
  3. Within seconds the HPA starts new Pod replicas.
  4. The page responds again, and reloads show different Pod ids.

Self-healing

  1. Failed containers are replaced automatically.
  2. Volumes follow the workload: if a node with an attached volume fails, the volume can be reattached to a new instance on another node.
  3. Traffic avoids failures: if a container behind a Service fails, its route is removed and traffic goes to healthy instances.

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.

Rolling updates

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.

Beyond the slides · how a rollout avoids downtime, and when it stops

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.

Instrument 2Reconciliation loop simulator

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.

Observability

After scalability, availability and reliability, one more quality attribute: a production system will eventually go down, for whatever reason.

Observability[Matteini, 2025]

An observable system allows understanding what happened, or what is happening, inside it.

Monitoring tools help achieve it. A monitoring system can:

  1. Collect and show metrics.
  2. Alert the system administrator.
  3. Log events.

The most common pair:

  1. Prometheus: open-source systems monitoring and alerting toolkit.
  2. Grafana: open-source web interface for analytics and monitoring.

The lecture's Grafana dashboard shows Prometheus metrics during the stress test.

For the oral

What does “declarative” buy over a deployment script?

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.

A node hosting 2 of your 5 replicas dies. What happens?

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.

Why can the backend scale to 10 but Redis cannot, just by changing a number?

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.

Where is consensus in Kubernetes?

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.

Project lens · Thunk
Slide coverage: where each slide section went
SlidesContentHere
CX · Motivations, PremisesFour motivations; containerisation§1, §2
CX · Introduction, Swarm I–IIIs / is not; six-point comparison§2
CX · Key features I–IIImmutability; declarative config§3
CX · Key features III–IVAutoscaling; self-healing§7
CX · Key features VCRI§4
CX · Architecture I–IIControl plane; nodes§4
CX · Objects I–IIRecord of intent; spec/status§3
CX · Objects III … Pods IVManifests; Pods; resources§5
CX · ReplicaSets … Services IIWorkloads; Services§5
CX · Example … Migrating VIIICompose, manifests, HPA, Kompose, Minikube§6
CX · Stress testing I–IVLoad test, HPA reaction§7
CX · System monitoring I–IIIObservability; Prometheus, Grafana§8