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.
| Kubernetes | Formula 1 |
|---|---|
| Container | The car plus the team's kit: sealed off in its own garage, sharing the circuit's infrastructure |
| Container image | The 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 state | The target called over the radio: you declare the goal, the system holds it |
| Cluster | The team assembled for race weekend |
| Control plane (master node) | The pit wall |
| Worker node | The garage/box where cars get built and run |
| API Server | The race engineer: every radio call goes through him, no exceptions |
| kubectl | The driver's radio button |
| kubeconfig | The paddock pass plus the team's radio frequency |
| etcd | The team's central data system (setup sheets plus the state of everything) |
| kubelet | Each garage's head mechanic |
| kube-proxy | The pit lane marshal, routing each car to the right box |
| Minikube | The factory simulator |
| Pod | The car (chassis built and running) |
| The Pod's IP | The car's position on track, always changing |
| Labels | The FIA tracking tags on parts and the car (used for selection) |
| Annotations | The engineer's debrief notebook entries (for humans) |
| YAML manifest | The car's spec sheet, version-controlled |
| Imperative vs. declarative | A direct radio order ("box, box") vs. a race plan written before the GP |
| Service | The pit box: a fixed spot in the pit lane, serving any car with the team tag |
| ClusterIP | Internal radio channel: only the team hears it |
| NodePort | The circuit's numbered service gate |
| LoadBalancer | The circuit's official entrance, ticket booth included |
| ConfigMap | The setup sheet, kept separate from the chassis |
| Secret | The confidential engine maps |
| ReplicaSet | The team boss's rule: "N cars ready, always" (and the mechanics' all-nighter) |
| Deployment | The development program: upgrade packages, spec by spec |
| Rolling update | Rolling the upgrade into one car at a time |
| Rollback | Reverting to the previous spec (the old floor that used to work) |
| emptyDir | The garage whiteboard, wiped when the weekend ends |
| hostPath | Storing data in that circuit's own freight crate |
| PVC | The data engineer's storage request |
| PV | The factory's physical storage that fills the request |
| StorageClass | The catalog of storage types (fast trackside vs. the factory datacenter) |
| Probes | Telemetry checks plus the radio check |
| Liveness probe | Telemetry flatlined → full reset of the car |
| Readiness probe | The box's red light: holds the car off track without stripping anything down |
| Startup probe | The power unit's fire-up procedure: nothing is timed before the engine warms up |
| HPA | The strategy wall lining up more cars as the load grows (an F1 without the 2-car cap) |
| metrics-server | The telemetry sensors: without them the pit wall sees nothing |
| resources.requests | The 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.
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 -vDocker 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 nodesNAME STATUS ROLES AGE VERSIONminikube Ready control-plane 1m v1.30.x# 5. Empty cluster = success:$ kubectl get podsNo 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
Hands-on
The smallest possible exercise: prove your lab is alive, the equivalent of the installation lap.
- Run minikube start and wait for "Done!".
- Run kubectl get nodes and check: the minikube node shows STATUS Ready. If it shows NotReady, wait a minute and check again.
- 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.
- 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=80pod/nginx created# Watch until the status reads Running (the first state is ContainerCreating)$ kubectl get podsNAME READY STATUS RESTARTS AGEnginx 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 nginxpod "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
Hands-on
Run the full cycle and force your first real K8s error. Every driver needs that first lap to feel out the limit:
- 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.
- Now create a pod that's broken on purpose: kubectl run broken --image=nginx:a-tag-that-does-not-exist.
- Run kubectl get pods and check the ImagePullBackOff (or ErrImagePull) status.
- 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.
- 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 objectkind: Pod # the object's TYPEmetadata: # data ABOUT the objectname: my-podlabels: # labels: key-value pairs used to SELECTapp: myappenvironment: studyannotations: # annotations: free-form metadata (docs)author: "Davi Gomes"spec: # the SPEC: what runs insidecontainers:- name: my-containerimage: nginx:1.14.2ports:- 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
Hands-on
- Save the YAML above as my-pod.yaml and apply it with kubectl apply -f my-pod.yaml.
- Run kubectl get pods --show-labels and check both labels in the last column.
- 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.
- Add a label live: kubectl label pod my-pod team=devops and confirm with --show-labels.
- 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 fileapiVersion: v1kind: Servicemetadata:name: my-service # becomes the internal hostnamespec:type: NodePort # swap for ClusterIP (internal) or LoadBalancer (cloud)selector: # ← THE LINK: looks for Pods with this labelapp: myappports:- protocol: TCPport: 80 # port the Service listens ontargetPort: 80 # port the container receives onnodePort: 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
Hands-on
- With the Stage 03 my-pod running (label app: myapp), save the Service YAML as app.yaml and apply it.
- 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.
- 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.
- 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/v1kind: Deploymentmetadata:name: nginx-fiapspec:replicas: 3 # I want 3 copies, alwaysselector: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 abovespec:containers:- name: nginximage: nginx:1.14.2 # changing this version triggers the rolling updateports:- 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
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.
- Apply deployment.yaml and confirm the 3 pods are Running: three cars on track, same spec.
- 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.
- 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.
- 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.
- 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: v1kind: PersistentVolumeClaimmetadata:name: my-pvcspec: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
Hands-on
- 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).
- 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.
- 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: v1kind: Podmetadata:name: probes-examplespec:containers:- name: appimage: example:latestports:- containerPort: 80livenessProbe: # fails → RESTARTS the podhttpGet:path: /healthport: 80periodSeconds: 10 # checks every 10stimeoutSeconds: 5failureThreshold: 3 # 3 failures in a row → restart (3 is the default)readinessProbe: # fails → LEAVES the load balancerhttpGet:path: /ready # good practice: a separate endpointport: 80periodSeconds: 5startupProbe: # holds the others off until the app is uphttpGet:path: /startupport: 80initialDelaySeconds: 120 # slow app: wait 2 minutesperiodSeconds: 30
💡 This YAML is illustrative: the example:latest image is fictional. The runnable experiment is in the next tab.
Essential commands for this stage
Hands-on
- 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.
- 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.
- 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/v1kind: Deploymentmetadata:name: nginx-hpaspec:replicas: 1 # start with 1; the HPA takes it from hereselector:matchLabels:app: nginx-hpatemplate:metadata:labels:app: nginx-hpaspec:containers:- name: nginximage: nginx:1.14.2resources: # WITHOUT requests the HPA can't compute %requests:cpu: "100m" # 100 millicores = 0.1 CPUlimits:cpu: "200m"
Essential commands for this stage
Hands-on
The weekend's final quick lap, with every sector coming together at once:
- 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).
- 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.
- 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).
- 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% freeInteractive 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/kubernetesPlay with Kubernetes
100% freeA 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.comKube by Example
100% freeShort 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.comIntroduction to Kubernetes (LFS158)
100% freeThe 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.orgKubernetes the Hard Way
100% freeKelsey 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/kelseyhightowerKodeKloud
Free tier + paidGuided, 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.comiximiuz Labs
Free tier + paidContainer 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.comSuggested 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.