← Back to all topics
$ kubectl get pods -A

Kubernetes for DevOps BONUS

Beyond the official syllabus — the container orchestrator that runs the world's largest systems

01
Why Kubernetes? — Beyond Docker Compose
When Docker Compose breaks down and what K8s solves that Compose cannot

The problem Docker Compose doesn't solve

Docker Compose is excellent for running a multi-container app on a single machine. But what happens when that one machine's CPU hits 100%? Or when you need to run 10 replicas of your API across 5 servers? Or when a container crashes at 3am and needs to be automatically restarted on a healthy node? Compose has no answer. Kubernetes does.

Kubernetes (K8s) is a container orchestrator: it schedules containers across a cluster of machines, self-heals crashed pods, rolls out updates without downtime, and scales workloads up or down based on CPU/memory demand. Every major cloud provider (AWS EKS, GCP GKE, Azure AKS) offers managed K8s — it's the industry standard for running containerised production workloads at scale.

bash — compose-vs-k8s.sh
### DOCKER COMPOSE — what it does well
✅ Local development — one command for entire stack
✅ Small teams, single server deployments
✅ Simple mental model — services, volumes, networks

### DOCKER COMPOSE — where it breaks down
❌ No automatic scaling across multiple machines
❌ No self-healing (crashed container stays down until manual restart)
❌ No zero-downtime rolling updates built-in
❌ No load balancing across replicas on different hosts

### KUBERNETES — what it adds
✅ Schedules containers across a cluster of nodes
✅ Restarts crashed pods automatically (self-healing)
✅ Rolling updates — new pods up before old pods down
✅ Horizontal autoscaling on CPU/memory
✅ Built-in service discovery and load balancing

# WHEN to use K8s vs Compose
Compose: ≤ 3 services, 1 server, hobby/small-team, capstone dev
K8s: multi-node, auto-scaling, production SLA, >5 services
🔄
Self-healing
If a Pod crashes, the Deployment controller notices and schedules a replacement — no human required.
📈
Horizontal scaling
Scale from 2 → 20 replicas with one command or automatically via HPA (Horizontal Pod Autoscaler).
🔀
Rolling updates
New version rolls out one Pod at a time — zero downtime, automatic rollback on failure.
☁️
Cloud-native
AWS EKS, GKE, AKS — every major cloud offers managed K8s. CNCF-standard, no vendor lock-in.
kubernetes orchestration scaling self-healing

🎯 Practice Questions

Q1.
Your capstone app is running on a single EC2 with Docker Compose and starts getting 10× the traffic during a product launch. What specific limitations of Docker Compose will you hit? Name 3 and explain what breaks.
Show Answer
1. No horizontal scaling: Compose can run multiple replicas on one host, but they all compete for the same CPU/RAM. You cannot spread load across multiple EC2 instances automatically. 2. No automatic failover: If the single EC2 goes down (hardware failure, OOM), the app is fully down — Compose has no concept of rescheduling to another host. 3. No zero-downtime deploys: docker compose up -d --no-deps app causes a brief window where the old container is stopped before the new one is ready — at 10× traffic, this is noticeable.
Q2.
What is the difference between a "container" and a "Pod" in Kubernetes? Can a Pod contain more than one container? Give a real-world example of when it should.
Q3.
A startup asks: "We have 3 microservices on one EC2, should we move to Kubernetes?" What questions do you ask before recommending K8s? What's the cost/complexity trade-off?
02
Pods, Deployments & Services
The three core building blocks you'll use in every Kubernetes workload

The K8s building blocks

Everything in Kubernetes is declared in YAML manifests. The three objects you'll use constantly: Pod — the smallest deployable unit, wraps one or more containers; Deployment — manages a set of identical Pods, handles rolling updates and rollbacks; Service — gives Pods a stable DNS name and IP so other services can reach them even as Pods are replaced.

You almost never create a bare Pod. You create a Deployment (which creates a ReplicaSet, which creates Pods). Then you expose it via a Service.

yaml — deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: capstone-api
spec:
replicas: 3
selector:
matchLabels:
app: capstone-api
template:
metadata:
labels:
app: capstone-api
spec:
containers:
- name: api
image: 123456789.dkr.ecr.ap-southeast-1.amazonaws.com/capstone:v1.2.0
ports:
- containerPort: 3000
resources:
requests: { cpu: "100m", memory: "128Mi" }
limits: { cpu: "250m", memory: "256Mi" }

--- # separator between documents in one file

apiVersion: v1
kind: Service
metadata:
name: capstone-api-svc
spec:
selector:
app: capstone-api # matches Deployment label
ports:
- port: 80 # Service port
targetPort: 3000 # container port
type: ClusterIP # internal only (use LoadBalancer for public)
bash — apply-and-check.sh
$ kubectl apply -f deployment.yaml
deployment.apps/capstone-api created
service/capstone-api-svc created

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
capstone-api-7d4b9f8c5-4xkpq 1/1 Running 0 12s
capstone-api-7d4b9f8c5-l9mnr 1/1 Running 0 12s
capstone-api-7d4b9f8c5-zqwvt 1/1 Running 0 12s

# Scale up to 5 replicas live
$ kubectl scale deployment capstone-api --replicas=5
deployment.apps/capstone-api scaled

# Rolling update — new image version
$ kubectl set image deployment/capstone-api api=$ECR_URL:v1.3.0
deployment.apps/capstone-api image updated
$ kubectl rollout status deployment/capstone-api
Waiting for deployment "capstone-api" rollout to finish: 2 out of 5 new replicas updated...
deployment "capstone-api" successfully rolled out

# Rollback if the new version is broken
$ kubectl rollout undo deployment/capstone-api
deployment.apps/capstone-api rolled back
📦
Pod
Smallest unit. Wraps 1+ containers that share a network namespace and storage. Ephemeral — Pods come and go.
🔁
Deployment
Manages replicas. Handles rolling updates + rollbacks. The object you interact with — not bare Pods.
🌐
Service
Stable DNS + IP for a group of Pods. ClusterIP (internal), NodePort (port on each node), LoadBalancer (cloud LB).
🏷️
Labels & Selectors
Labels on Pods; selectors on Services/Deployments. The glue that connects K8s objects to each other.
pods deployments services yaml-manifests rolling-update

🎯 Practice Questions

Q1.
Write a Deployment manifest for a Python Flask app running on port 5000 with 2 replicas. Include resource requests/limits of 100m CPU and 64Mi memory.
Show Answer
apiVersion: apps/v1 kind: Deployment metadata: name: flask-api spec: replicas: 2 selector: matchLabels: app: flask-api template: metadata: labels: app: flask-api spec: containers: - name: flask image: your-ecr-url/flask-app:latest ports: - containerPort: 5000 resources: requests: { cpu: "100m", memory: "64Mi" } limits: { cpu: "200m", memory: "128Mi" }
Q2.
What is the difference between kubectl apply and kubectl create? Which should you use in a CI/CD pipeline and why?
Q3.
Run kubectl rollout undo deployment/capstone-api after a bad deploy. What does K8s actually do under the hood — which object does it modify?
Q4.
What happens to traffic when a Deployment rolls out a new version? Does the Service stop routing requests during the rollout? Explain the relationship between readiness probes and rolling updates.
03
ConfigMaps & Secrets
Separating configuration from container images — the Kubernetes way

Configuration management in K8s

ConfigMaps store non-sensitive configuration: environment variables, config files, feature flags. Secrets store sensitive data: passwords, API keys, TLS certificates — stored base64-encoded in etcd (and encrypted at rest if the cluster is configured correctly). Both are injected into Pods as environment variables or mounted as files.

Important caveat: base64 is not encryption. Kubernetes Secrets require proper RBAC + encryption at rest + external secret management (AWS Secrets Manager via External Secrets Operator) for production. For learning purposes, Secrets + RBAC is the standard starting point.

yaml — config-and-secrets.yaml
# ConfigMap — non-sensitive config
apiVersion: v1
kind: ConfigMap
metadata:
name: capstone-config
data:
NODE_ENV: production
PORT: "3000"
LOG_LEVEL: info

---
# Secret — sensitive data (base64 encoded)
apiVersion: v1
kind: Secret
metadata:
name: capstone-secrets
type: Opaque
data:
DB_PASSWORD: bXlzZWNyZXRwYXNz # base64: mysecretpass
JWT_SECRET: c3VwZXJzZWNyZXQ= # base64: supersecret

# Reference in Deployment — envFrom injects all keys
containers:
- name: api
image: capstone:latest
envFrom:
- configMapRef:
name: capstone-config
- secretRef:
name: capstone-secrets

# Create secret imperatively (never in YAML for real passwords)
$ kubectl create secret generic capstone-secrets \
--from-literal=DB_PASSWORD=mysecretpass \
--from-literal=JWT_SECRET=supersecret
secret/capstone-secrets created
⚙️
ConfigMap
Non-sensitive config. Version-controlled in YAML. Change it without rebuilding the image.
🔐
Secret
base64-encoded, not encrypted. Never commit Secret YAML to git. Use kubectl create secret imperatively.
💉
envFrom
Injects all keys from a ConfigMap or Secret as environment variables — cleaner than listing each one separately.
🔒
Production secrets
For real production: use External Secrets Operator + AWS Secrets Manager to sync secrets into K8s automatically.
configmaps secrets env-vars security

🎯 Practice Questions

Q1.
Encode the string my-db-password in base64 using the terminal. Then decode it back. Explain why this demonstrates that Kubernetes Secrets are NOT encrypted by default.
Show Answer
$ echo -n "my-db-password" | base64 bXktZGItcGFzc3dvcmQ= $ echo "bXktZGItcGFzc3dvcmQ=" | base64 -d my-db-password

This shows that base64 is reversible encoding, not encryption. Anyone with access to the Secret object in etcd (or via kubectl get secret -o yaml) can decode the value in one command. Encryption at rest + strict RBAC on the secrets resource is required for production.
Q2.
What is the difference between envFrom: secretRef and env: valueFrom: secretKeyRef? When would you use each?
Q3.
You update a ConfigMap. Do running Pods automatically see the new values? What must you do to apply the updated config to a running Deployment?
04
kubectl Essentials
The 15 kubectl commands you'll use every day as a DevOps engineer

Your K8s toolbelt

kubectl is the CLI for all Kubernetes interactions — the equivalent of docker but for clusters. You'll use a core set of commands for 90% of day-to-day work: get, describe, logs, exec, apply, delete, scale, rollout. The rest you look up when needed.

bash — kubectl-cheatsheet.sh
# INSPECT
$ kubectl get pods -A # all pods, all namespaces
$ kubectl get pods -n production -o wide # with node + IP
$ kubectl describe pod capstone-api-7d4b-xyz # events, limits, probes
$ kubectl get events --sort-by=.lastTimestamp # recent cluster events

# LOGS
$ kubectl logs capstone-api-7d4b-xyz # pod logs
$ kubectl logs -f deploy/capstone-api # follow (tail -f)
$ kubectl logs -l app=capstone-api --tail=50 # all pods by label

# EXEC — run commands inside a pod
$ kubectl exec -it capstone-api-7d4b-xyz -- sh # interactive shell
$ kubectl exec deploy/capstone-api -- node --version

# APPLY / DELETE
$ kubectl apply -f k8s/ # apply whole directory
$ kubectl delete -f k8s/deployment.yaml

# SCALE / ROLLOUT
$ kubectl scale deployment capstone-api --replicas=0 # stop all pods
$ kubectl rollout restart deployment/capstone-api # restart all pods
$ kubectl rollout history deployment/capstone-api # revision history

# PORT FORWARD — access a pod locally without a Service
$ kubectl port-forward pod/capstone-api-7d4b-xyz 3000:3000
Forwarding from 127.0.0.1:3000 -> 3000
🔍
describe > get
kubectl describe pod X shows Events — which is where 90% of "why is my pod not starting" answers live.
📋
logs -l (by label)
Stream logs from all replicas at once with -l app=name — much better than picking one pod by name.
🔌
port-forward
Debug any pod locally without changing its Service. Works for databases, Prometheus, Grafana — anything.
🔁
rollout restart
The K8s equivalent of "restart the container" — triggers a rolling restart of all Pods in a Deployment.
kubectl cli debugging logs port-forward

🎯 Practice Questions

Q1.
A Pod is stuck in CrashLoopBackOff. List the exact kubectl commands you'd run (in order) to diagnose why it's crashing.
Show Answer
1. kubectl get pods — confirm status and how many restarts
2. kubectl describe pod <pod-name> — check Events section for OOMKilled, image pull errors, liveness probe failures
3. kubectl logs <pod-name> — see the last stdout/stderr before crash
4. kubectl logs <pod-name> --previous — logs from the previous (crashed) container instance
5. If you can't tell from logs: kubectl exec -it <pod-name> -- sh to inspect the container environment (may not work if it's crashing too fast)
Q2.
What does kubectl get pods -o wide show that kubectl get pods doesn't? Give a real debugging scenario where the extra columns matter.
Q3.
Use kubectl port-forward to access the Prometheus metrics endpoint of your capstone app running inside minikube. Write the exact command and explain the port numbers.
05
Local K8s with minikube & kind
Run a real Kubernetes cluster on your laptop — zero cloud cost, full kubectl experience

Local cluster options

minikube is the most popular tool for running a single-node K8s cluster locally — it starts a VM (or Docker container) with a full K8s control plane. kind (Kubernetes in Docker) runs K8s nodes as Docker containers — faster startup, great for CI testing, supports multi-node clusters on one machine. Both give you a real kubectl experience without any cloud cost.

bash — local-k8s-setup.sh
### MINIKUBE SETUP (macOS / Linux)
$ brew install minikube
$ minikube start --cpus=2 --memory=4096 --driver=docker
Done! kubectl is now configured to use "minikube" cluster

$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
minikube Ready control-plane 30s v1.29.0

# Load local Docker image into minikube (skip ECR for local dev)
$ docker build -t capstone:local .
$ minikube image load capstone:local

# Apply your manifests
$ kubectl apply -f k8s/
deployment.apps/capstone-api created
service/capstone-api-svc created

# Open the app in browser (minikube creates a tunnel)
$ minikube service capstone-api-svc
Opening service default/capstone-api-svc in default browser...

### KIND — alternative (faster, no VM overhead)
$ brew install kind
$ kind create cluster --name capstone
Cluster "capstone" created.
$ kind load docker-image capstone:local --name capstone

# Cleanup
$ minikube delete # or: kind delete cluster --name capstone
🖥️
minikube
Runs a single-node cluster in Docker or a VM. Has addons (ingress, dashboard, metrics-server). Best for beginners.
kind
Fastest startup. Nodes are Docker containers. Supports multi-node clusters for testing node affinity/taints.
📦
Load local images
Use minikube image load or kind load docker-image to avoid needing ECR for local testing.
🎛️
minikube addons
minikube addons enable ingress adds NGINX Ingress; metrics-server enables HPA — real cluster features locally.
minikube kind local-dev kubectl

🎯 Practice Questions

Q1.
Start a minikube cluster, deploy your capstone app using the Deployment + Service YAML from Module 2, and access it in a browser. Record each command you ran and confirm the app responds correctly.
Q2.
Enable the metrics-server addon in minikube (minikube addons enable metrics-server). Then run kubectl top pods. What does this output show and how is it related to the HPA (Horizontal Pod Autoscaler)?
Q3.
What is the key difference between using imagePullPolicy: Never in your Deployment YAML when testing locally with minikube vs. using the default policy? When does this matter?
06
Project: Deploy Your Capstone App to K8s
Migrate your Docker Compose capstone app to a full Kubernetes deployment on minikube

Project overview

Take the Docker Compose stack from your capstone project and re-deploy it to a local minikube cluster using Kubernetes manifests. This is the same mental shift that happens in industry when teams move from Compose-based dev to a production K8s cluster — the containers don't change, the orchestration layer does.

bash — k8s-project-structure.sh
# Target directory layout
$ tree k8s/
k8s/
├── namespace.yaml # isolate in 'capstone' namespace
├── configmap.yaml # NODE_ENV, PORT, LOG_LEVEL
├── secret.yaml # DB_PASSWORD (created imperatively, not committed)
├── api-deployment.yaml # 2 replicas, resource limits, envFrom
├── api-service.yaml # ClusterIP for internal, NodePort for minikube access
└── postgres-deployment.yaml # StatefulSet + PersistentVolumeClaim

# Deploy everything
$ kubectl apply -f k8s/namespace.yaml
$ kubectl create secret generic capstone-secrets -n capstone --from-literal=DB_PASSWORD=devpass
$ kubectl apply -f k8s/ -n capstone
configmap/capstone-config created
deployment.apps/capstone-api created
service/capstone-api-svc created
statefulset.apps/postgres created

$ kubectl get all -n capstone
NAME READY STATUS RESTARTS
pod/capstone-api-7d4b-abc 1/1 Running 0
pod/capstone-api-7d4b-def 1/1 Running 0
pod/postgres-0 1/1 Running 0

$ minikube service capstone-api-svc -n capstone
Opening http://127.0.0.1:62348 in your browser...

# Trigger a rolling update
$ kubectl set image deployment/capstone-api api=capstone:v2 -n capstone
$ kubectl rollout status deployment/capstone-api -n capstone
deployment "capstone-api" successfully rolled out
📁
Namespace isolation
Run everything in a capstone namespace — keeps your work separate from kube-system and easy to delete cleanly.
💾
StatefulSet for Postgres
Databases need stable pod names and persistent storage — StatefulSet gives both. Don't use a plain Deployment for DBs.
🔄
Same containers, new orchestrator
Your Docker images don't change — only the YAML manifests that describe how to run them change.
kubectl get all
One command to see Pods, Deployments, ReplicaSets, Services in a namespace. Good "is everything running?" check.

📋 Project Deliverables

  • A k8s/ directory in your capstone repo containing: namespace.yaml, configmap.yaml, api-deployment.yaml (2 replicas, resource limits), api-service.yaml (NodePort for minikube access)
  • Screenshot of kubectl get all -n capstone with all Pods in Running state
  • Demonstrate a rolling update: change the image tag, run kubectl rollout status, and show the app still responding during the rollout (use curl in a loop)
  • Demonstrate a rollback: kubectl rollout undo and confirm the previous version is serving traffic
  • Update your capstone README with a "Kubernetes deployment" section documenting the commands to spin up the K8s stack from scratch

🎯 Practice Questions

Q1.
Why should you use a StatefulSet for PostgreSQL instead of a regular Deployment? What specific guarantees does StatefulSet provide that Deployment does not?
Show Answer
StatefulSet provides: (1) Stable pod namespostgres-0, postgres-1 instead of random hashes — so the app can connect to a known hostname. (2) Ordered startup/shutdown — pod-0 starts before pod-1 (important for primary/replica replication). (3) Stable persistent storage — each pod gets its own PersistentVolumeClaim that survives pod rescheduling. A Deployment would give a new random PVC on every reschedule, losing all data.
Q2.
After you've deployed the capstone to minikube, run curl http://$(minikube ip):<nodePort>/health in a loop while triggering a rolling update. Observe whether any requests fail. What would you add to your Deployment to prevent failures during rollout?
💡 Hint: look up readinessProbe and minReadySeconds in Deployment spec.
Q3.
What's the difference between Kubernetes and AWS EKS? If you wanted to move your minikube deployment to production, what additional steps would be required?