live broadcast · the Kubernetes basics GP, stage by stage

Kubernetes narrated
live from the paddock.

Good afternoon, everyone! Eight stages, from lights out (why this sport exists) to the final straight of autoscaling, where the cluster starts driving itself. At stake: leaving here knowing how to build, run, heal and scale a real cluster. Lights are about to go out, and the driver takes the wheel of the narration. Over to you.

Every Kubernetes concept read the way a driver reads telemetry: the problem each piece solves, a technically honest F1 analogy, YAML commented line by line, commands with the expected output, and a hands-on section per stage. At the end, the free platforms to practice on real clusters in your browser.

The cheat sheet: pin this to your visor

The same analogy holds from the first stage to the last. Don't try to memorize this table now. It's the track map, not the exam: go straight to Stage 01 and come back here whenever an analogy shows up in the text.

KubernetesFormula 1
ContainerThe car plus the team's kit: sealed off in its own garage, sharing the circuit's infrastructure
Container imageThe car's build spec, reproducible at any circuit on the calendar
Virtual machine (VM)Hauling the team's entire factory to every GP
Kubernetes (the orchestrator)The whole pit wall plus garage operation
Desired stateThe target called over the radio: you declare the goal, the system holds it
ClusterThe team assembled for race weekend
Control plane (master node)The pit wall
Worker nodeThe garage/box where cars get built and run
API ServerThe race engineer: every radio call goes through him, no exceptions
kubectlThe driver's radio button
kubeconfigThe paddock pass plus the team's radio frequency
etcdThe team's central data system (setup sheets plus the state of everything)
kubeletEach garage's head mechanic
kube-proxyThe pit lane marshal, routing each car to the right box
MinikubeThe factory simulator
PodThe car (chassis built and running)
The Pod's IPThe car's position on track, always changing
LabelsThe FIA tracking tags on parts and the car (used for selection)
AnnotationsThe engineer's debrief notebook entries (for humans)
YAML manifestThe car's spec sheet, version-controlled
Imperative vs. declarativeA direct radio order ("box, box") vs. a race plan written before the GP
ServiceThe pit box: a fixed spot in the pit lane, serving any car with the team tag
ClusterIPInternal radio channel: only the team hears it
NodePortThe circuit's numbered service gate
LoadBalancerThe circuit's official entrance, ticket booth included
ConfigMapThe setup sheet, kept separate from the chassis
SecretThe confidential engine maps
ReplicaSetThe team boss's rule: "N cars ready, always" (and the mechanics' all-nighter)
DeploymentThe development program: upgrade packages, spec by spec
Rolling updateRolling the upgrade into one car at a time
RollbackReverting to the previous spec (the old floor that used to work)
emptyDirThe garage whiteboard, wiped when the weekend ends
hostPathStoring data in that circuit's own freight crate
PVCThe data engineer's storage request
PVThe factory's physical storage that fills the request
StorageClassThe catalog of storage types (fast trackside vs. the factory datacenter)
ProbesTelemetry checks plus the radio check
Liveness probeTelemetry flatlined → full reset of the car
Readiness probeThe box's red light: holds the car off track without stripping anything down
Startup probeThe power unit's fire-up procedure: nothing is timed before the engine warms up
HPAThe strategy wall lining up more cars as the load grows (an F1 without the 2-car cap)
metrics-serverThe telemetry sensors: without them the pit wall sees nothing
resources.requestsThe declared energy/fuel allocation, the baseline for any percentage

Where the analogies break down (a driver who hides the car's limits crashes into the wall):

  • ReplicaSet (Stage 05): in F1, swapping a car only happens between sessions, and the rules cap each team at 2 cars. The ReplicaSet replaces Pods in seconds, at any moment, in any quantity. The "mechanics' all-nighter" captures the mechanism (rebuilding from spec), not the timing or the cap.
  • HPA (Stage 08): scaling replicas has no direct parallel on a 2-car grid. The stated analogy is an "endurance race with no entry limit." The pit wall's behavior (reacting to telemetry, respecting the floor and ceiling) stays faithful.
  • Service (Stage 04): a real pit box serves one car at a time; a Service balances continuous traffic across N simultaneous replicas. What stays faithful is the fixed address plus tag-based selection.
  • Multi-container Pod (Stage 03): a car with a power unit plus MGU-K illustrates "distinct units sharing systems," but a Pod's containers are independent processes that can be swapped individually in the spec.
YOUR PROGRESS0 / 8

Stage 01 motivation + setup

Why Kubernetes exists

LIGHTS OUT! It's lights out at the Kubernetes GP! First corner, first brake, and the question that opens every championship: why does this sport exist? The answer to this stage carries the other seven. Stay with me.

Think about the demand on a GP weekend: the team's website, timing, streaming. Everything sleeps all week and explodes on race Sunday. And there's the car that flies in the simulator but won't run on track: the classic "works on my machine." Every project runs into both problems, and Kubernetes automates the fix for both.

How it works

The piece both solutions share is the container. A virtual machine is like hauling the entire factory to every GP: the carbon-fiber press, the wind tunnel, the office, all loaded onto the plane. A container is the car plus the team's kit. Each team has its own sealed garage, its own secrets, its own parts, but the circuit's structure (the paddock building, the power, the pit lane, in other words the host operating system's kernel) is shared by all ten teams. Cadillac, debuting this year, didn't build its own racetrack to compete: it showed up with its crates and plugged into the infrastructure that was already there.

In technical terms: a VM carries a full operating system (gigabytes, minutes to boot); a container shares the host OS's kernel and only brings the files, binaries, and libraries the app actually needs (megabytes, seconds). A container image is an entire environment, reproducible anywhere. It's the build spec that guarantees the car shipped to Suzuka is identical to the one that ran in Melbourne. Goodbye, "works on my machine."

But containers solve the environment problem and create another one: with dozens or hundreds of them, someone has to create them, tear them down, watch their health, and recreate whatever fails. Doing that by hand does not scale. It's like asking one person to run both cars, the telemetry, and the pit stop, alone. Kubernetes (K8s) is the orchestrator that takes over those jobs: the entire pit wall plus garage operation.

Its philosophy is that of the race engineer calling a target: he doesn't issue an instruction every corner. He declares "target plus 0.3 a lap, engine mode 6," and the whole system works on its own to hold it. In K8s you declare the desired state ("I want 3 copies of this running") and it makes reality match that declaration, even at 3 a.m. when a container dies. The pit wall never sleeps.

The key concepts of this stage

Container ≠ VM

VM = virtual hardware + full OS (the whole factory, shipped). Container = only what the app needs, sharing the host kernel (the car in the circuit's garage). Much lighter.

Horizontal scaling

More copies of the app splitting the load (what K8s automates). Vertical = more CPU/RAM on the same machine: lining up more cars vs. a bigger engine in a single car.

Cluster and Pod

K8s organizes machines (nodes) into clusters. Containers run grouped in Pods, the smallest managed unit. Cluster = the team on race weekend; Pod = the car.

kubectl

The command-line tool that talks to the cluster's REST API. It's your radio button (details in Stage 02).

Minikube

A 1-node K8s cluster running on your machine. The factory simulator: built for practice, no risk of crashing the real car. It's this guide's lab.

Desired state

K8s's philosophy: you declare what you want, it makes it happen and keeps it that way. Something dies? It rebuilds it on its own. The target called over the radio.

Setting up the lab (step by step)

# 1. Install Docker Desktop (docker.com). On Linux, use the Docker
# Engine (docs.docker.com/engine/install). Check:
$ docker -v
Docker version 27.x
# 2. Install kubectl (kubernetes.io/docs/tasks/tools) and check:
$ kubectl version --output=yaml
# 3. Install Minikube (minikube.sigs.k8s.io/docs/start) and bring up the cluster:
$ minikube start
😄 minikube v1.33 on Windows 11
✨ Using the docker driver
🏄 Done! kubectl is now configured to use "minikube"
# 4. Confirm the cluster is up:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 1m v1.30.x
# 5. Empty cluster = success:
$ kubectl get pods
No resources found in default namespace.

💡 The first minikube start run downloads the cluster image and takes a few minutes. That's normal. It's the simulator's cold fire-up, and a cold simulator never starts instantly.

Essential commands for this stage

$minikube start # brings up the local 1-node cluster
$minikube stop # stops the cluster without deleting anything
$kubectl get nodes # lists nodes and their status
$kubectl get pods # lists pods in the current namespace

Hands-on

The smallest possible exercise: prove your lab is alive, the equivalent of the installation lap.

  1. Run minikube start and wait for "Done!".
  2. Run kubectl get nodes and check: the minikube node shows STATUS Ready. If it shows NotReady, wait a minute and check again.
  3. Run kubectl get pods and check: "No resources found." An empty, ready cluster is exactly the starting point for the next stage: a clean garage before the car arrives.
  4. Bonus: run minikube stop, then kubectl get nodes to see the connection error. Bring it back up with minikube start. Now you know what "cluster off the air" looks like in the terminal: a dead radio, and a dead radio mid-race is the worst feeling there is.

Tip: If minikube start fails, the most common cause is Docker Desktop not being open (on Linux, the service is stopped: sudo systemctl start docker). Bring Docker up first and try again. (Every simulator has a breaker someone forgot to flip.)

Takeaway: A container is a light, reproducible environment, the car that runs the same on any circuit; Kubernetes is the race engineer of desired state: you call the target, it holds it.

Sector 1 in the green! Lab standing, simulator on, cluster responding, a spotless start. Now comes the part every rookie underestimates: learning to talk on the radio. Stage 02 is next. Don't go anywhere.

Stage 02 the radio button

kubectl and the API: how you command the cluster

Second stage and the track tightens: what good is having the car in the garage if you don't know how to key the radio? F1 lives on this channel. It's where Kimi Räikkönen immortalized "leave me alone, I know what I'm doing" at Abu Dhabi 2012, and won. Today you're the one learning to give the orders.

Your cluster is standing, but it's a sealed box. How do you give it orders? How do you investigate when something breaks, with no "physical" access to the container? On track the driver has the same problem: at 300 km/h, there's no getting out to pop the hood. Everything he knows about the car arrives by radio and telemetry.

How it works

Everything in Kubernetes goes through the master node's REST API. kubectl is that API's client: every command you type becomes an HTTP request (GET, POST, PUT, DELETE) against the cluster. In F1, the driver doesn't talk to the front-left tire mechanic, or to the factory. He talks to one person: the race engineer. Every message goes through that single channel, which validates it, logs it, and passes it on to whoever executes it. kubectl is your radio button; the API Server is the race engineer: nothing happens in the cluster without going through him.

The path of a command: you type kubectl get pods, kubectl reads the kubeconfig file (the API Server's address, credentials, and the current cluster context; Minikube sets this up on its own, and you can think of it as the paddock pass with the team's radio frequency already tuned in), and fires a GET /api/v1/pods at the API Server. The response comes back formatted in your terminal.

The API is split into groups: the 'core' group holds the fundamental resources (pods, services, replicasets); other groups handle security, storage, autoscaling, like the team's departments: chassis, aero, power unit, strategy. Since it's plain REST, you can call it from any language (Python, Go, Java, C#), which opens the door to automation.

There are two ways to work: imperative (kubectl run nginx --image=nginx, the direct order, the "box, box, box" over the radio: fast, decisive, good for tests) and declarative (write a YAML and run kubectl apply -f file.yaml, the race plan written Saturday night: documented, reviewed, versioned). In real projects declarative wins, because the file is versionable in Git. No serious team improvises the entire strategy live on the radio.

The verbs that handle 90% of your day

get

Query: lists resources and each one's status. kubectl get pods, get svc, get nodes... It's the glance at the timing screen.

create / run / apply

Create: run spins up an imperative pod; apply -f applies a YAML (declarative).

delete

Remove: kubectl delete pod nginx. Careful: it can affect whatever depends on that resource.

describe

Detail: config plus the Events section. First stop when something breaks. The session's telemetry history, event by event.

logs

See what the container is printing. Your best friend for debugging: it's listening to the car's radio directly.

exec

Open a shell inside the container: kubectl exec -it <pod> -- /bin/sh. The mechanic plugging a laptop into the car.

Worked example: a Pod's life cycle

# Creates a pod named "nginx" from the nginx:1.14.2 image on Docker Hub
$ kubectl run nginx --image=nginx:1.14.2 --port=80
pod/nginx created
# Watch until the status reads Running (the first state is ContainerCreating)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 30s
# Details plus events (look for the "Pulling image" line in Events)
$ kubectl describe pod nginx
# What the container is printing
$ kubectl logs nginx
# Cleanup
$ kubectl delete pod nginx
pod "nginx" deleted

💡 If the status sits at ImagePullBackOff, the cluster couldn't pull the image. Check the name/tag and your internet connection. It's the parts crate that never reached the paddock: without the right spec, the car doesn't go together.

Essential commands for this stage

$kubectl run nginx --image=nginx:1.14.2 --port=80 # creates an imperative pod
$kubectl describe pod <name> # details + Events (troubleshooting)
$kubectl logs <name> # container output
$kubectl exec -it <name> -- /bin/sh # shell inside the container
$kubectl delete pod <name> # removes the pod

Hands-on

Run the full cycle and force your first real K8s error. Every driver needs that first lap to feel out the limit:

  1. Run the sequence from the example above (run → get → describe → logs → delete). In describe, look at the Events section: the Pulling image, Created container, Started container lines tell the pod's story in order, like a telemetry replay, frame by frame.
  2. Now create a pod that's broken on purpose: kubectl run broken --image=nginx:a-tag-that-does-not-exist.
  3. Run kubectl get pods and check the ImagePullBackOff (or ErrImagePull) status.
  4. Run kubectl describe pod broken and look in Events for the message explaining the image wasn't found. You just practiced a real debugging flow.
  5. Clean up: kubectl delete pod broken.

Tip: Memorize this flow: get pods (what's the status?) → describe pod (what do the Events say?) → logs (what is the app saying?). That sequence solves most problems. It's the driver's post-incident protocol: timing screen → telemetry history → the car's radio.

Takeaway: Everything in K8s is a call to the REST API; kubectl is your radio button and the API Server is the race engineer. And when something breaks, the path is always get → describe → logs.

Radio calibrated, and did you notice? Your first forced error, diagnosed without panic. This sector separates button-pushers from people who actually talk to the team. And heads up, because next comes the technical heart of the circuit: cluster anatomy and the spec sheet. Don't blink.

Stage 03 architecture + labels

Cluster anatomy and its smallest unit: the Pod

We're entering the most technical corner complex on the track. Courage doesn't help here, precision does. And the sport's history shows no mercy for a spec out of place: in 1999 Ferrari nearly lost a win in Malaysia over millimeters on the barge boards. A spec isn't paperwork, it's the result. Today you write your first spec sheet.

In Stage 02 you created a pod with an imperative command. It works, but it has two holes: you don't know exactly what got created or where. And if you need to recreate that same pod tomorrow, you're relying on memory. No team builds a car from memory: there's a spec sheet for everything, from wing angle to every bolt's torque.

How it works

A cluster is a group of machines (nodes) working together, with well-defined roles: it's the team assembled for race weekend. The Pod is the smallest unit Kubernetes manages. On our map, the Pod is the car: an 'envelope' holding one or more containers that share networking and storage.

Four facts about Pods: (1) a Pod represents a running process and can hold more than one container sharing the same IP and volumes, like the power unit and the MGU-K in the same car; (2) Pods are ephemeral, constantly born and dying; the car that raced Silverstone today gets stripped down to the last bolt before Spa, what survives is the spec (keep this line, it's the root of Stages 04, 05, and 06); (3) every Pod gets an internal cluster IP, the car's position on track: real, it exists, but it keeps changing; (4) Pods are described in YAML, the car's spec sheet.

To organize dozens of Pods there are labels and annotations. In F1, every car component (power unit, turbo, MGU-K, gearbox) carries an FIA-sealed tracking tag: that's how the system knows which engine is in which car; the tag exists to IDENTIFY AND SELECT. The engineer's debrief notebook ("driver reported vibration at turn 7") is information for humans, and nobody filters components by notebook entry. Labels are the FIA tag (it's by label that a Service finds its Pods, as you'll see in Stage 04); annotations are the debrief notebook (author, documentation, audit trail; they do NOT take part in selection). That difference is what shows up on the test.

Who does what inside the cluster

Master node

The pit wall: manages the cluster and decides where Pods run.

Worker node

The garages: run the Pods and other resources. Where the car gets built and actually runs.

etcd

The team's central data system: a distributed database holding the cluster's config and state, every setup sheet there is.

kubelet

Each garage's head mechanic: the agent on every node that manages its local Pods.

kube-proxy

The pit lane marshal: routes network traffic to the right Pods.

API Server

The race engineer: everything kubectl does passes through here (Stage 02).

Your first YAML: memorize this skeleton

apiVersion: v1 # the K8s API version for this object
kind: Pod # the object's TYPE
metadata: # data ABOUT the object
name: my-pod
labels: # labels: key-value pairs used to SELECT
app: myapp
environment: study
annotations: # annotations: free-form metadata (docs)
author: "Davi Gomes"
spec: # the SPEC: what runs inside
containers:
- name: my-container
image: nginx:1.14.2
ports:
- containerPort: 80

💡 Every K8s object follows this skeleton: apiVersion + kind + metadata + spec. The kind and the spec's contents change; the rest is always the same. It's like the technical regulations: once you understand one article's structure, you can read all the others.

Essential commands for this stage

$kubectl apply -f file.yaml # creates/updates from a YAML
$kubectl get pods --show-labels # lists pods, showing their labels
$kubectl get pods -l app=myapp # filters by label
$kubectl delete -f file.yaml # removes whatever the file created

Hands-on

  1. Save the YAML above as my-pod.yaml and apply it with kubectl apply -f my-pod.yaml.
  2. Run kubectl get pods --show-labels and check both labels in the last column.
  3. Run kubectl get pods -l app=myapp, then kubectl get pods -l app=something-else. Notice: the first finds the pod, the second comes back empty. Right tag, car found; wrong tag, empty garage.
  4. Add a label live: kubectl label pod my-pod team=devops and confirm with --show-labels.
  5. Don't delete the pod: it'll be the Service's target in the next stage. (If you already deleted it, just apply the YAML again. That's the beauty of declarative: the spec sheet rebuilds the identical car.)

Tip: Labels = operational selection (what K8s uses). Annotations = documentation (what humans use). A Pod's author? Annotation. Grouping an app's Pods? Label. The FIA tag versus the debrief notebook.

Takeaway: Every K8s object is apiVersion + kind + metadata + spec, the car's spec sheet; and labels are the FIA tag by which everything in the cluster finds everything else.

What a clean sector, ladies and gentlemen! YAML skeleton in your pocket, labels mastered. And notice how the guide set this up: that app: myapp label still alive on track is the overtake being prepared for the next stage. Off to the pit box!

Stage 04 networking + config

Services: a fixed address for Pods that keep changing

Lap 4 and the classic pit lane problem shows up: how do you find a car that keeps changing position? Anyone who follows F1 knows address and pit procedure are never a detail. Ask Christijan Albers, who left the box in 2007 with the fuel hose still attached. A fixed address and config kept out of the chassis: that's what this stage is about.

Remember fact #2 from Stage 03? Pods are ephemeral: every new Pod is born with a different IP. Trying to talk to them by IP is like finding a car by its track position: on lap 12 it's P4, by lap 30 it's P7. And there's a second problem: config baked into the image forces a rebuild for every change. A new chassis every time the pit wall asks for half a degree more wing.

How it works

Problem 1 → Service. The Service is the team's pit box: the box's position in the pit lane stays fixed all season, everyone knows where Ferrari's box is. Which car pulls in each lap changes (Leclerc's, Hamilton's, a chassis built yesterday), and whoever needs the box doesn't care: the address is the same, and the box serves any car carrying the team's tag.

The flow: a request hits the Service (fixed IP and name), the Service selects Pods by label selector (Stage 03's mechanism, the FIA tag again), and spreads traffic evenly across the healthy replicas. The Service's name becomes an internal cluster hostname: if the Service is called "auth-service," any Pod reaches it at http://auth-service, even if the Pods behind it swap IPs a thousand times.

Problem 2 → ConfigMap. The ConfigMap is the setup sheet kept separate from the chassis: wing, pressures, maps. None of it is welded to the car; it lives on a sheet the mechanics apply before the car leaves the garage. You store the values in one central object and inject them three ways: environment variables (the most common, via envFrom), mounted files (great for long configs), or command-line arguments. Config changed? Update the ConfigMap; the image stays the same. Half a degree of wing doesn't need a new chassis.

The 3 kinds of Service

ClusterIP (default)

Internal IP, cluster-only. The internal radio channel: only the team hears it. Use it for service-to-service traffic (e.g., API ↔ database).

NodePort

A fixed port (30000–32767) opened on the node. The circuit's numbered service gate, the way to expose something on Minikube.

LoadBalancer

An external load balancer with a public IP. The circuit's official main entrance, ticket booth included. This is the production type in the cloud (AWS, GCP, Azure).

The YAMLs: watch the selector

# app.yaml: save the Service in this file
apiVersion: v1
kind: Service
metadata:
name: my-service # becomes the internal hostname
spec:
type: NodePort # swap for ClusterIP (internal) or LoadBalancer (cloud)
selector: # ← THE LINK: looks for Pods with this label
app: myapp
ports:
- protocol: TCP
port: 80 # port the Service listens on
targetPort: 80 # port the container receives on
nodePort: 30080 # port exposed on the node (NodePort only)

💡 The Service's selector matches the Pod's label (app: myapp), the exact my-pod you created in Stage 03. That's how it knows where to send traffic: the box reads the tag on the car pulling in.

Essential commands for this stage

$kubectl get svc # lists Services
$minikube service <name> --url # access URL for a NodePort
$kubectl get configmap # lists ConfigMaps
$kubectl exec <pod> -- env # checks injected variables

Hands-on

  1. With the Stage 03 my-pod running (label app: myapp), save the Service YAML as app.yaml and apply it.
  2. Run minikube service my-service --url, open the URL in your browser, and check the nginx welcome page. You just reached an ephemeral Pod through a stable address: you found the car by its box, not by its track position.
  3. Test the link: delete the pod (kubectl delete pod my-pod) and reload the browser to see the error (empty box, no car with the team's tag). Recreate it with kubectl apply -f my-pod.yaml and reload: it's back, without touching the Service. The box never moved.
  4. Apply the ConfigMap + pod (kubectl apply -f configmap-pod.yaml), run kubectl exec pod-config -- env, and check MESSAGE and MODE in the variable list. The setup sheet made it onto the car.

Tip: Golden rule: expose the minimum. Database = ClusterIP (internal channel; race strategy doesn't leak over an open radio). Only the system's entry point becomes NodePort/LoadBalancer. For passwords and keys, ConfigMap's secure sibling is the Secret: the confidential engine maps not every mechanic on the team gets to see.

Takeaway: Service is the pit box: a fixed address for cars (Pods) that keep changing, found by their tag (label); ConfigMap is the setup sheet, config kept outside the chassis (image).

Purple sector! A Pod reached through a stable address, config kept outside the chassis. And you even proved the link by deleting the car and watching the box stand there unmoved. Stay sharp, though, because next comes the corner that decides races: what happens when the car STOPS on track?

Stage 05 self-healing + scale

Self-healing and scale: ReplicaSets and Deployments

THIS is where the point-scorers separate from the rest of the grid! Just today, lap 46, the Red Bull engine died and Verstappen watched the finish from the wall. Zero points. In production, your single Pod dying at 3 a.m. is exactly that scene. Self-healing and the way back: the most important stage of the guide starts NOW.

Up to now you've created Pods by hand. Great for learning, but think about Silverstone today: Verstappen stopped on lap 46 and that was it, there's no substitute mid-race. In production, your one Pod dying overnight is the same thing: the site is down until someone wakes up. And when you ship 2.0, how do you swap versions without taking the service down? And if it ships with a catastrophic bug, how do you get back fast?

How it works

ReplicaSet: the team boss of headcount. You declare "replicas: 3" and it watches the Pods by label selector (the FIA tag, again!). One drops? It notices the count is short and spins up another in seconds, without you asking. It's the team boss's non-negotiable rule, "N cars ready, always": the driver wrecks the chassis Saturday, the mechanics work through the night, and Sunday there's a whole car on the grid, rebuilt from spec. That's Kubernetes self-healing, Stage 01's "desired state" kept for real.

The analogy breaks here, and it's worth flagging: in F1, replacing a car only happens between sessions, and the rules cap each team at 2 cars. The ReplicaSet has no parc fermé and no rulebook: it replaces the Pod in seconds, any time, any quantity. Think of K8s as an endurance race with no entry limit, where the "mechanics' all-nighter" takes thirty seconds.

Deployment: the spec manager. It wraps the ReplicaSet and adds the development program: Rolling Update (swap the image, and it brings up new replicas while gradually shutting down the old ones, so the service never goes dark, like an upgrade rolled into one car while the other keeps running the old spec), Rollback (kubectl rollout undo goes back to the previous version in seconds: the new floor doesn't work, the car reverts to the old spec; Ferrari showed up in Barcelona this year with a new package and Hamilton won, but if the package had hurt the car, they would have reverted to the Monaco spec with no drama), and manual scale (kubectl scale --replicas=5: five cars on the grid).

The ReplicaSet and Deployment YAMLs are nearly identical, but in practice we almost always use the Deployment, for version control. Nobody manages just the car count; you manage the count AND each one's spec. The Deployment creates and manages the ReplicaSet under the hood.

What each one guarantees

ReplicaSet

N replicas running ALWAYS. One drops, another is born in seconds: the mechanics' all-nighter, compressed. Selection by label selector.

Rolling Update

Gradual version swap: brings up the new ones, shuts down the old. Zero downtime: the upgrade rolled in one car at a time.

Rollback

New version broke something? kubectl rollout undo restores the previous one in seconds, back to the spec that worked.

The full Deployment + its life cycle

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-fiap
spec:
replicas: 3 # I want 3 copies, always
selector:
matchLabels:
app: nginx-app # I manage Pods with this label...
template: # ...and this is each Pod's "mold"
metadata:
labels:
app: nginx-app # has to match the selector above
spec:
containers:
- name: nginx
image: nginx:1.14.2 # changing this version triggers the rolling update
ports:
- containerPort: 80

💡 Notice: the template is literally the Pod skeleton from Stage 03, embedded inside the Deployment. Nothing here is new, it's composition: the car's spec sheet, attached to the team boss's order ("three cars at this spec, always").

Essential commands for this stage

$kubectl scale deployment <name> --replicas=5 # manual scale
$kubectl set image deployment/<name> ctn=img:tag # triggers a rolling update
$kubectl rollout status deployment/<name> # tracks the rollout
$kubectl rollout history deployment/<name> # lists revisions
$kubectl rollout undo deployment/<name> # rollback!

Hands-on

The moment the grandstands were waiting for: the guide's own pit stop. But here, unlike a tire change, rushing is the enemy: the value of this exercise is in WATCHING each state change in the terminal. Stay calm, keep your eyes open.

  1. Apply deployment.yaml and confirm the 3 pods are Running: three cars on track, same spec.
  2. Delete a pod by hand (copy a real name from kubectl get pods) and run kubectl get pods again, fast, to catch the replacement in ContainerCreating. You just tried to violate the desired state and the cluster corrected it.
  3. Run the rolling update to nginx:1.16.1, track it with kubectl rollout status, and watch for the success message. The upgrade rolled in car by car, nobody left the track.
  4. Break it on purpose with the nginx:does-not-exist image and watch kubectl get pods show old pods Running, holding the service, and new ones stuck in ImagePullBackOff.
  5. Run kubectl rollout undo deployment/nginx-fiap and confirm with kubectl rollout history, checking the listed revisions. You just did a production rollback in one command: back to qualifying spec without losing the race.

Tip: Run the self-healing test at least once: kill a pod and watch its replacement get born, the mechanics' all-nighter compressed into seconds. It's the moment Kubernetes finally clicks.

Takeaway: Nobody runs a standalone Pod in production, because a car with no team never finishes the race: the Deployment guarantees N replicas (self-healing), swaps specs without pulling anyone off track (rolling update), and reverts to the previous spec in one command (rollback).

And the grandstand is on its feet! You KILLED a Pod and the cluster rebuilt it before the replay even finished. A decisive sector, completed in the green. Now, the question that haunts every top team: what about the DATA?

Stage 06 data persistence

Volumes: data that outlives the Pod

We're entering the high-speed sector, and the subject that never makes the highlight reel but wins championships: data. Williams dominated the '90s because it turned telemetry into development before anyone else did. A car gets stripped down; history, never. Stage 06, and it counts.

Pods are ephemeral, and Stage 05 made that radical, with pods dying and being born on every rolling update. Great for the app, catastrophic for data: the car that raced today gets stripped to the monocoque, but gigabytes of telemetry are already back at the factory before the driver even pulls off his helmet. If the PostgreSQL Pod gets recreated mid-update, every Black Friday order disappears with it. Data that matters can't live on a container's filesystem.

How it works

Kubernetes solves this with a chain of 4 concepts: Volume, PersistentVolume (PV), PersistentVolumeClaim (PVC), and StorageClass (SC). Memorize it through the team's data operation: the PVC is the data engineer's REQUEST ("I need 1Gi, read-write from one node"); the PV is the factory's physical STORAGE that fills the request (NFS, AWS EBS, hostPath...); the StorageClass is the CATALOG of storage types (the fast trackside server? the factory datacenter?).

The Pod only references the PVC: the car doesn't know (and doesn't need to know) which rack in the factory holds its telemetry. That separation is what lets the same YAML run on Minikube and on AWS, the same way the same data procedure works at Interlagos and at Suzuka. On Minikube, the 'standard' StorageClass already provisions PVs automatically: you make the request (PVC) and the storage (PV) appears.

PVC access modes (who can mount the volume): ReadWriteOnce (read-write from ONE node, the typical database case; only the team's own garage writes to its own data), ReadOnlyMany (read-only, many nodes, for static assets; like the FIA timing feed: every team reads it, nobody changes it), and ReadWriteMany (read-write from many nodes; needs NFS or similar).

The storage chain

emptyDir

Lives and dies with the Pod. The garage whiteboard: session notes, wiped when the weekend ends. For temporary data and sharing between a Pod's own containers.

hostPath

Mounts a directory from the NODE into the container. That circuit's freight crate: fine for dev; be careful in production (it ties the Pod to that node).

PVC → PV

The request (Claim) matches the storage (Volume). Data survives the Pod dying: telemetry safe at the factory.

StorageClass

The catalog: provisions PVs automatically by profile. On Minikube, the 'standard' class already handles this.

Prove the persistence: write, destroy, read it back

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce # one node reads and writes (typical for a database)
resources:
requests:
storage: 1Gi # how much disk I want
# on Minikube, the "standard" StorageClass provisions the PV on its own

Essential commands for this stage

$kubectl get pvc # volume requests (Bound = ok)
$kubectl get pv # the cluster's persistent disks
$kubectl delete pvc <name> # removes the request (and frees the disk)

Hands-on

  1. Apply the PVC and run kubectl get pvc. Check the STATUS: Bound: your request was filled by a PV created automatically (confirm with kubectl get pv).
  2. Run the full sequence from the example: write the file → delete the pod → recreate the pod → read the file. Watch "i survived!" come back. That's the PVC's contract: the car dies, the telemetry stays.
  3. Bonus experiment (the contrast that teaches): repeat the test swapping the volume for emptyDir, and when you recreate the Pod, the file will be gone. That's the difference between the garage whiteboard and the factory server, felt firsthand.

Tip: Important data ALWAYS goes in a PVC. If data vanished after a restart, I'd bet it was living in emptyDir or the container's filesystem: someone wrote the race strategy on the whiteboard and the cleaning crew came through.

Takeaway: PVC is the request, PV is the storage, StorageClass is the catalog. And data that matters always lives behind a PVC: the car gets stripped down, the telemetry never does.

"I SURVIVED!" And the file came back! The car was stripped down to the monocoque and the telemetry was right there, intact, at the factory. Green sector. But hold on tight, because next comes the sneakiest trap on the circuit: the car that's running... but isn't.

Stage 07 app health

Probes: how the cluster knows your app is actually alive

Careful, this corner is BLIND! Today, at Silverstone, the whole world watched: Antonelli started from pole, set a 1:31.777 (the fastest lap of the race), and crossed the line SIXTEENTH. Fifty-two laps on track, and nothing about it was healthy. And the veterans remember Senna at Interlagos, 1991: the car was moving, but only sixth gear still worked. That's exactly the gap probes close.

Antonelli was out there, running, for all 52 laps, and something was clearly not healthy anyway. It's identical in the cluster: your app deadlocks at 3 a.m., the process is still standing (Kubernetes sees Running across the board), but no request gets answered. Stage 05's self-healing only recreates pods that DIE; it can't see pods that are alive but stuck: the car that's running... but isn't.

How it works

Probes are health checks declared in the Pod's manifest. It's telemetry plus a radio check: the team doesn't trust the fact that the car is moving. It checks specific channels at regular intervals: oil pressure, power unit temperature, the driver's response on the radio. It's not enough for the car to be on track; it has to respond to being poked.

How a probe checks (3 mechanisms): httpGet (the kubelet fires a GET at an endpoint like /health and expects 200 OK; the most common one, the radio check: "give me an OK, driver"), tcpSocket (tests whether the port accepts a connection, for services that don't speak HTTP; the radio carrier opens, even with no actual talk), and exec (runs a command inside the container; exit code 0 = healthy, the mechanic plugging in a laptop and running diagnostics).

Best practices from the lesson: use all three probes together in production; use different endpoints for each probe (separate telemetry channels; you don't measure oil pressure on the brake sensor); start from the default settings and only tune intervals when the app actually needs it; monitor probe failures (e.g., with Prometheus), because they're the first sign of trouble, like that flicker in the telemetry two laps before a part fails.

The 3 probes and what happens when they fail

Liveness

"Is it alive?" Fails → the Pod gets RESTARTED. The full cycle: turn the car off and back on. For apps that hang and only come back with a restart.

Readiness

"Ready for traffic?" Fails → it LEAVES the load balancer, no restart. The box's red light: held in the garage, nobody strips it down. For apps that load data before they're ready.

Startup

"Done booting?" While it runs, it HOLDS the other two off. The power unit's fire-up procedure: nobody clocks a lap time on a cold engine. For apps that are slow to boot.

A Pod with all 3 probes, plus a teaching sabotage

apiVersion: v1
kind: Pod
metadata:
name: probes-example
spec:
containers:
- name: app
image: example:latest
ports:
- containerPort: 80
livenessProbe: # fails → RESTARTS the pod
httpGet:
path: /health
port: 80
periodSeconds: 10 # checks every 10s
timeoutSeconds: 5
failureThreshold: 3 # 3 failures in a row → restart (3 is the default)
readinessProbe: # fails → LEAVES the load balancer
httpGet:
path: /ready # good practice: a separate endpoint
port: 80
periodSeconds: 5
startupProbe: # holds the others off until the app is up
httpGet:
path: /startup
port: 80
initialDelaySeconds: 120 # slow app: wait 2 minutes
periodSeconds: 30

💡 This YAML is illustrative: the example:latest image is fictional. The runnable experiment is in the next tab.

Essential commands for this stage

$kubectl get pods -w # watches changes live
$kubectl describe pod <name> # Events show the probe failures

Hands-on

  1. Apply liveness-exec.yaml, run kubectl get pods -w, and watch the RESTARTS column climb on its own every ~35-45s. Every increment is the kubelet sending "turn the car off and on" to a Pod that stopped answering the telemetry.
  2. In another terminal, run kubectl describe pod liveness-exec and look in Events for the Warning Unhealthy plus Normal Killing pair. Learn to recognize that duo: in production, it's the signature of a failing probe.
  3. Clean up: kubectl delete pod liveness-exec.

Tip: Running does NOT mean healthy. Antonelli ran all 52 laps at Silverstone with pole and the fastest lap in his pocket, and finished P16. A car on track isn't a competitive car; a standing process isn't a working app. That's exactly why probes exist.

Takeaway: Liveness fails = turn the car off and on (restart); Readiness fails = the box's red light (leaves the load balancer without restarting); Startup = the power unit's fire-up (holds the other two off until the app finishes warming up).

RESTARTS climbing on its own on screen: you sabotaged the sensor and watched the pit wall call the reset, three readings confirmed, no panic. Few sectors teach this much with so little YAML. And now, the final straight of the circuit: the cluster is about to learn to drive itself.

Stage 08 automatic scaling

HPA: automatic scaling driven by metrics

FINAL STAGE, checkered flag in sight! And what a way to close it out: the pit wall calling strategy in real time. That's how Ross Brawn won Hungary in 1998, with Schumacher flying and a three-stop strategy recalculated mid-race. Today, the one reading and reacting is the HPA, and the desired-state loop opened back in Stage 01 closes right in front of you.

Go back to Stage 01's problem: demand explodes on race Sunday. You already know how to scale by hand (kubectl scale, Stage 05), but are you going to sit at the terminal adjusting replicas for every spike? At 2 a.m. too? No pit wall works that way: strategy reacts to the race in real time, based on telemetry. The loop needs to close: the cluster has to measure the load and scale itself.

How it works

The Horizontal Pod Autoscaler (HPA) watches Pod metrics (CPU, memory...) and adjusts the replica count between a minimum and a maximum you define. It's the strategy wall with the power to line up more cars as the race load grows, keeping in mind Stage 05's deal: F1 itself caps out at 2 cars, so think of it as an endurance race with no entry limit. The pace tightened past the target? Line up more cars (up to the ceiling). The race calmed down? Pull back gradually.

How it works: the HPA compares Pod utilization against the configured target (e.g., keep average CPU at 70%). Went over? It creates replicas (up to the max). Dropped? It removes them (down to the min), saving resources. Scale-down is deliberately slow (~5 minutes of stability) so it doesn't 'porpoise' on every quick blip. It's the same reason the pit wall doesn't rewrite strategy over every cloud on the radar: it waits for the pattern to confirm before it tears down the operation over the first safety car.

Supported metrics: CPU (the most common), memory, custom app metrics (requests/sec, queue depth), external metrics (e.g., Prometheus), and disk I/O. The right metric depends on the app's profile, the same way you pick a strategy around what actually degrades the car: tires at Barcelona, brakes at Montreal.

Two prerequisites that trip everyone up: (1) the cluster needs metrics-server to read CPU/memory. On Minikube: minikube addons enable metrics-server. No sensors on the car, and the pit wall sees nothing. (2) The container MUST declare resources.requests.cpu, because '70% CPU' means 70% OF WHAT THE POD ASKED FOR, the same way ERS energy allocation per lap works: 'I used 70%' only makes sense against a declared allocation. Without requests, the HPA shows <unknown> and doesn't scale.

What the HPA understands

CPU / Memory

The classic metrics. A percentage calculated against the container's resources.requests: the percentage of the declared allocation, never a raw absolute number.

Custom metrics

From your own app (req/s, queue depth) or external ones (Prometheus). Scale on what actually matters, because every track degrades something different.

min / max

You always set the floor and the ceiling on replicas. The HPA works inside that range, the same way strategy works inside the rulebook.

Deployment + HPA + Service + load test

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-hpa
spec:
replicas: 1 # start with 1; the HPA takes it from here
selector:
matchLabels:
app: nginx-hpa
template:
metadata:
labels:
app: nginx-hpa
spec:
containers:
- name: nginx
image: nginx:1.14.2
resources: # WITHOUT requests the HPA can't compute %
requests:
cpu: "100m" # 100 millicores = 0.1 CPU
limits:
cpu: "200m"

Essential commands for this stage

$minikube addons enable metrics-server # HPA prerequisite
$kubectl get hpa -w # watches the HPA react to load
$kubectl top pods # CPU/memory per pod
$kubectl autoscale deployment <n> --cpu-percent=70 --min=1 --max=10 # creates an imperative HPA

Hands-on

The weekend's final quick lap, with every sector coming together at once:

  1. Enable metrics-server and apply all three YAMLs (Deployment, HPA, Service). Run kubectl get hpa and check the targets: if it shows <unknown>, wait about a minute (a freshly installed sensor gives no reading before its first lap).
  2. Start the load generator in one terminal and kubectl get hpa -w in another, and watch the CPU percentage shoot past 70% and the REPLICAS column climb. If it plateaus below target, use the load tab's plan B. Confirm with kubectl get pods: new pods were born without a single command from you, the pit wall lined up cars on its own, reading the telemetry.
  3. Stop the load (Ctrl+C) and keep watching: replicas take about 5 minutes to shrink back. That slowness is deliberate (nobody tears down the garage the moment things go quiet).
  4. Use kubectl top pods during the test to see the raw metric feeding the HPA, the telemetry behind the strategy call.

Tip: kubectl get hpa showing <unknown> in the targets? Either metrics-server isn't running, or the container never declared resources.requests. Always one of those two causes: either the sensor is off, or nobody declared the reference allocation.

Takeaway: HPA = desired state applied to scale: you set the target, floor, and ceiling, and the pit wall does the rest. But without metrics-server (sensors) and resources.requests (declared allocation), it sees nothing.

And the cluster SCALED ITSELF, ladies and gentlemen! Replicas climbing on screen without a single command, and the loop closes: you declare the target, the pit wall runs the race. Final corner complete. Now it's the cool-down lap, a wave to the grandstand, and the podium just ahead.

Stage 09 free labs

Practice for free on real clusters

Cool-down lap: the engine cools off, but the championship keeps going, and every driver knows a season is built in practice. Here is the testing calendar.

Think of this as any driver's ladder: public simulator, karting, F4, and only then the real car. These platforms give you a terminal with a real Kubernetes cluster right in the browser. Start with the 100% free ones.

Killercoda

100% free

Interactive Kubernetes scenarios right in the browser: you get a terminal with a real cluster and a guided script. Katacoda's successor.

Good for: Practice every stage of this guide without installing anything. Start here.

killercoda.com/kubernetes

Play with Kubernetes

100% free

A real, temporary Kubernetes cluster (4-hour sessions) in the browser, run by Docker. You build the cluster by hand with kubeadm.

Good for: Freely test kubectl commands and understand how a cluster comes together.

labs.play-with-k8s.com

Kube by Example

100% free

Short tutorials and examples maintained by Red Hat: one concept per page (Pods, Services, Deployments...), straight to the point.

Good for: Quickly review a specific concept, like a second explanation.

kubebyexample.com

Introduction to Kubernetes (LFS158)

100% free

The Linux Foundation's official free course (who maintains K8s), also available on edX. Solid theory with a certificate of completion.

Good for: Consolidate the theory with the official material after finishing this guide.

training.linuxfoundation.org

Kubernetes the Hard Way

100% free

Kelsey Hightower's legendary walkthrough: building a cluster piece by piece, by hand, with no installers. It's "build the car bolt by bolt": once you've done it, you never look at the garage the same way.

Good for: Advanced level, for when you want to understand what Minikube hides from you.

github.com/kelseyhightower

KodeKloud

Free tier + paid

Guided, challenge-style labs: "this Pod won't come up, figure out why." Some labs are free; the full catalog (plus CKA/CKAD practice exams) is paid.

Good for: Realistic troubleshooting, the closest thing to a technical interview.

kodekloud.com

iximiuz Labs

Free tier + paid

Container and Kubernetes playgrounds in the browser with hands-on challenges. Several playgrounds and challenges are unlocked on the free plan.

Good for: Deeper experiments: networking, images, container internals.

labs.iximiuz.com

Suggested route: at every stage of this guide, open Killercoda and repeat the commands in the matching scenario (Pods, Deployments, Volumes...). Finished all 8 stages? Head to KodeKloud and take on troubleshooting challenges.