This is the last lesson of the course and it works differently from every other one: you do not read it, you do it. It is a complete hands-on exam, with fifteen timed tasks in the style of the certifications, on the Rutas Norte platform you have built across the previous eleven modules. It has its scoring, its solutions and a self-assessment table that turns your result into a clear verdict: ready to book a date, revise two specific domains, or go back to certain modules.
The most important rule: do the mock exam before reading the solutions. The value of this lesson is not in the solution set —it is in discovering, with a timer running, what you can do unaided and what you cannot. Reading it first turns a diagnosis into a pleasant but useless read.
Get the cluster ready, set the timer to 120 minutes and start.
Contents
- Mock exam instructions
- Preparing the environment
- The fifteen tasks
- Time allocation and strategy
- Mock exam instructions
1.1 Rules
These rules replicate the real exam conditions. Respecting them is what makes the result mean something.
| Rule | Detail |
|---|---|
| Total time | 120 minutes, timed from the first task |
| No breaks | Do not stop the clock. Go to the toilet first. |
| Documentation | Only kubernetes.io/docs. One tab. Nothing else. |
| Forbidden | Search engines, forums, GitHub, your own notes, this lesson, AI tools |
| Editor | vim or nano in the terminal. Not a graphical IDE. |
| Phone | In another room |
| Scoring | 100 points spread across the 15 tasks |
| Marking | At the end, with the solutions. Never during. |
1.2 How to score
Each task states its acceptance criterion. Score it like this:
- Full marks if the whole criterion is met.
- Partial marks (half) if a substantial part is met but some requirement is missing.
- Zero if the object does not exist, is in the wrong namespace or does not work.
Be honest with yourself. Inflating the score only harms you.
1.3 What to note down as you work
Keep a notes file open (or paper, which is fine here) and record for each task:
T1 5p ✔ 3 min
T2 7p ½ 9 min - could not get the Ingress right first time
T3 6p ✔ 4 min
T4 8p ⏸ skipped - PVC Pending and I could not work out why
...This information is as valuable as the final score: it tells you where your time goes.
- Preparing the environment
2.1 Recommended option: kind with three nodes
kind brings up a multi-node cluster in a minute and supports almost everything the mock exam needs. You need Docker or Podman installed.
# kind-mock.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30080
hostPort: 30080
- role: worker
- role: worker
networking:
disableDefaultCNI: true # we will install Calico for NetworkPolicieskind create cluster --name mock --config kind-mock.yaml
# A CNI with NetworkPolicy support (essential for task 9)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml
# Wait for every node to be Ready
kubectl wait --for=condition=Ready nodes --all --timeout=300s
kubectl get nodesNAME STATUS ROLES AGE VERSION
mock-control-plane Ready control-plane 2m v1.30.0
mock-worker Ready <none> 2m v1.30.0
mock-worker2 Ready <none> 2m v1.30.02.2 Alternative: minikube
minikube start --nodes=3 --cni=calico --kubernetes-version=v1.30.0
minikube addons enable ingress
minikube addons enable metrics-serverMinikube has the advantage of shipping the Ingress controller and the metrics-server as addons, which on kind have to be installed separately.
2.3 Additional components
So that every task is solvable, install this before starting the timer:
# Ingress controller (needed for task 2)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.2/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx --for=condition=ready pod \
--selector=app.kubernetes.io/component=controller --timeout=180s
# metrics-server (needed for task 11)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'2.4 Preparation script (run it before the timer)
This script creates the namespaces, the starting workloads and the three faults for the troubleshooting tasks. Save it as prepare-mock.sh and run it. Do not read it closely: the faults must be a surprise.
#!/bin/bash
set -e
for ns in rutas-norte-dev rutas-norte-pre rutas-norte-pro; do
kubectl create namespace $ns --dry-run=client -o yaml | kubectl apply -f -
done
# Node labels for task 5
kubectl label node mock-worker2 type=batch --overwrite
kubectl taint node mock-worker2 dedicated=batch:NoSchedule --overwrite
# Starting workloads for tasks 7, 11 and 12
kubectl create deployment bookings-api --image=nginx:1.27-alpine \
--replicas=2 -n rutas-norte-pre
kubectl create deployment web-store --image=nginx:1.27-alpine \
--replicas=3 -n rutas-norte-pro
# --- FAULT 1 (task 13) ---
kubectl create deployment notifications-worker --image=busybox:1.36 \
-n rutas-norte-dev
kubectl set env deployment/notifications-worker -n rutas-norte-dev \
--from=secret/queue-credentials --prefix=QUEUE_ 2>/dev/null || \
kubectl patch deployment notifications-worker -n rutas-norte-dev --type='json' \
-p='[{"op":"add","path":"/spec/template/spec/containers/0/envFrom",
"value":[{"secretRef":{"name":"queue-credentials"}}]}]'
# --- FAULT 2 (task 14) ---
kubectl create deployment redis-cache --image=redis:7-alpine -n rutas-norte-pre
kubectl expose deployment redis-cache --name=redis-cache-svc --port=6379 \
-n rutas-norte-pre
kubectl patch svc redis-cache-svc -n rutas-norte-pre \
-p '{"spec":{"selector":{"app":"redis"}}}'
# --- FAULT 3 (task 15) ---
kubectl run occupancy-reports --image=busybox:1.36 -n rutas-norte-pro \
--overrides='{"spec":{"nodeSelector":{"disk":"nvme"}}}' \
--command -- sleep 3600
echo "Environment ready. Start the timer."2.5 Now for real: start the timer
Before reading task 1, type your start-up block (lesson 12-04):
alias k=kubectl
export do='--dry-run=client -o yaml'
export now='--force --grace-period=0'
source <(kubectl completion bash)
complete -o default -F __start_kubectl k
cat <<'EOF' > ~/.vimrc
set number
set expandtab
set tabstop=2
set shiftwidth=2
set softtabstop=2
set autoindent
set paste
EOF120 minutes. Go.
- The fifteen tasks
Task 1 — Basic deployment and scaling
Namespace: rutas-norte-dev · Weight: 5 points · Target: 4 min
Create a Deployment called web-store with the image nginx:1.27-alpine and 4 replicas. All of its pods must carry the label environment=dev in addition to whatever the controller adds. Then scale it to 6 replicas.
Acceptance criterion: kubectl get deploy web-store -n rutas-norte-dev shows 6/6 and every pod carries the label environment=dev.
Task 2 — Exposure: Service and Ingress
Namespace: rutas-norte-dev · Weight: 7 points · Target: 8 min
Expose the web-store Deployment from the previous task with a ClusterIP Service called web-store-svc on port 80. Also create an Ingress store-ingress with the IngressClass nginx that routes the host www.rutasnorte.es, path /, to that Service on port 80.
Acceptance criterion: the Service has populated endpoints and kubectl describe ingress store-ingress shows the rule with the correct backend.
Task 3 — Configuration and secrets
Namespace: rutas-norte-dev · Weight: 6 points · Target: 6 min
Create:
- A ConfigMap
api-configwith the keyslog_level=infoandmax_connections=100. - A Secret
bookings-postgres-credentialswithusername=bookingsandpassword=N0rt3-2026.
Create a Pod bookings-api with the image nginx:1.27-alpine that receives all the ConfigMap keys as environment variables and mounts the Secret as files at /etc/secrets in read-only mode.
Acceptance criterion: kubectl exec bookings-api -- env | grep log_level returns log_level=info and kubectl exec bookings-api -- cat /etc/secrets/username returns bookings.
Task 4 — Persistent storage
Namespace: rutas-norte-pre · Weight: 8 points · Target: 9 min
Create a PersistentVolume pv-bookings of 3Gi, access mode ReadWriteOnce, storageClassName: manual, reclaim policy Retain, with hostPath at /mnt/bookings-data.
Create a PersistentVolumeClaim pvc-bookings of 2Gi that binds to that PV, and a Pod bookings-postgres with the image busybox:1.36 that runs sleep 3600 and mounts the volume at /var/lib/data.
Acceptance criterion: the PVC shows as Bound to the PV pv-bookings and the pod is Running with the volume mounted.
Task 5 — Scheduling with taints and node labels
Namespace: rutas-norte-pro · Weight: 6 points · Target: 6 min
Node mock-worker2 has the taint dedicated=batch:NoSchedule and the label type=batch.
Create a Deployment notifications-worker with the image busybox:1.36, command sleep 3600, 2 replicas, that runs exclusively on that node.
Acceptance criterion: both pods are Running on mock-worker2 (check with -o wide).
Task 6 — Scheduled job
Namespace: rutas-norte-pro · Weight: 5 points · Target: 5 min
Create a CronJob nightly-reports that runs every day at 2:30, with the image busybox:1.36 and the command echo occupancy report generated. Requirements: it must not allow overlapping runs, each Job may retry at most 2 times, and only 3 successful Jobs are kept in the history.
Acceptance criterion: the CronJob exists with schedule: "30 2 * * *", concurrencyPolicy: Forbid, backoffLimit: 2 and successfulJobsHistoryLimit: 3. A manual run produces the expected output.
Task 7 — Probes and quality of service
Namespace: rutas-norte-pre · Weight: 7 points · Target: 7 min
The bookings-api Deployment already exists. Modify it so that:
- It receives no traffic until it answers
GET /on port 80 (readinessProbe). - It restarts if
GET /fails 3 times in a row (livenessProbe). - It has QoS class
Guaranteedwith 200m of CPU and 256Mi of memory.
Acceptance criterion: kubectl describe pod -l app=bookings-api -n rutas-norte-pre shows QoS Class: Guaranteed and both probes configured.
Task 8 — RBAC and ServiceAccount
Namespace: rutas-norte-pro · Weight: 7 points · Target: 7 min
Create the ServiceAccount support-l1 in rutas-norte-pro. Grant it permission to list and view pods and read their logs, in that namespace only. It must not be able to delete anything or reach other namespaces.
Assign that ServiceAccount to the web-store Deployment in rutas-norte-pro.
Acceptance criterion: kubectl auth can-i list pods --as=system:serviceaccount:rutas-norte-pro:support-l1 -n rutas-norte-pro returns yes; the same query with delete or in another namespace returns no.
Task 9 — Network policy
Namespace: rutas-norte-pro · Weight: 7 points · Target: 8 min
Create in rutas-norte-pro a NetworkPolicy called deny-all-ingress that denies all inbound traffic to every pod in the namespace.
Add a second policy allow-store-from-ingress that allows inbound traffic to port 80 of the pods labelled app=web-store only from pods in the ingress-nginx namespace.
Acceptance criterion: both policies exist; a test pod inside rutas-norte-pro cannot reach web-store on port 80.
Task 10 — Hardening and admission
Namespace: rutas-norte-pre · Weight: 7 points · Target: 7 min
Label the rutas-norte-pre namespace so that it applies the restricted Pod Security Admission standard in enforce mode, with version v1.30.
Then create a Pod secure-auditor (image busybox:1.36, command sleep 3600) that complies with that standard: non-root user, no privilege escalation, all capabilities dropped and the default seccomp profile.
Acceptance criterion: the namespace carries the enforce label; the secure-auditor pod is Running; a kubectl run insecure --image=nginx -n rutas-norte-pre is rejected.
Task 11 — Autoscaling
Namespace: rutas-norte-pre · Weight: 5 points · Target: 4 min
Create a HorizontalPodAutoscaler for the bookings-api Deployment that keeps between 2 and 8 replicas, with a CPU utilisation target of 65 %.
Acceptance criterion: kubectl get hpa -n rutas-norte-pre shows the HPA with MINPODS 2, MAXPODS 8 and the 65 % target, and with metrics being read (not <unknown>).
Task 12 — Update and rollback
Namespace: rutas-norte-pro · Weight: 6 points · Target: 6 min
On the web-store Deployment in rutas-norte-pro:
- Configure the strategy so that no pod goes unavailable during the update (
maxUnavailable: 0,maxSurge: 1). - Update the image to
nginx:1.27.2-alpineand record the change cause asbump to 1.27.2. - Wait for the rollout to finish and then roll back to the previous revision.
Acceptance criterion: kubectl rollout history shows at least two revisions with the recorded cause, and the final image is nginx:1.27-alpine again.
Task 13 — Troubleshooting: the worker that will not start
Namespace: rutas-norte-dev · Weight: 8 points · Target: 8 min
The notifications-worker Deployment in rutas-norte-dev cannot get a single pod started. Find the cause, fix it without changing the image or the container configuration, and leave the Deployment with at least one pod Running.
Write a line explaining the root cause into /opt/diagnosis-13.txt.
Acceptance criterion: the Deployment has 1/1 pods ready and the diagnosis file exists and is not empty.
Task 14 — Troubleshooting: the service that does not answer
Namespace: rutas-norte-pre · Weight: 8 points · Target: 8 min
The redis-cache-svc Service in rutas-norte-pre is not serving traffic: applications calling it get connection refused, even though the redis-cache pods are Running. Find the cause and fix it without modifying the pods.
Write a line explaining the root cause into /opt/diagnosis-14.txt.
Acceptance criterion: kubectl get endpoints redis-cache-svc -n rutas-norte-pre shows at least one IP address.
Task 15 — Troubleshooting: the pod that never gets scheduled
Namespace: rutas-norte-pro · Weight: 8 points · Target: 8 min
The occupancy-reports Pod in rutas-norte-pro has spent minutes in Pending. Work out why and get it into Running. You may either fix the pod or adapt the cluster; justify your decision.
Write a line explaining the root cause and the chosen solution into /opt/diagnosis-15.txt.
Acceptance criterion: the occupancy-reports pod is Running.
- Time allocation and strategy
4.1 Target times
| Task | Topic | Points | Target | Cumulative |
|---|---|---|---|---|
| 1 | Deployment and scaling | 5 | 4 min | 4 |
| 2 | Service and Ingress | 7 | 8 min | 12 |
| 3 | ConfigMap and Secret | 6 | 6 min | 18 |
| 4 | PV, PVC and Pod | 8 | 9 min | 27 |
| 5 | Taints and nodeSelector | 6 | 6 min | 33 |
| 6 | CronJob | 5 | 5 min | 38 |
| 7 | Probes and QoS | 7 | 7 min | 45 |
| 8 | RBAC | 7 | 7 min | 52 |
| 9 | NetworkPolicy | 7 | 8 min | 60 |
| 10 | Pod Security | 7 | 7 min | 67 |
| 11 | HPA | 5 | 4 min | 71 |
| 12 | Rollout and rollback | 6 | 6 min | 77 |
| 13 | TS: worker down | 8 | 8 min | 85 |
| 14 | TS: Service with no endpoints | 8 | 8 min | 93 |
| 15 | TS: Pending pod | 8 | 8 min | 101 |
| — | Final review | — | ~19 min | 120 |
Total: 100 points in 101 minutes of work, with 19 to spare. That margin is deliberate: in the real exam it gets eaten by hesitation, tasks that overrun and the review.
4.2 Suggested strategy
Applying what you learned in lesson 12-04:
Min 0-4 Start-up block + reading the 15 tasks
Min 4-30 Phase 1 (cheap and quick): T1, T11, T6, T3, T12 → 27 points
Min 30-85 Phase 2 (central block by weight): T4, T13, T14, T15, T9, T8, T10, T7, T2, T5
Min 85-105 Phase 3: outstanding and half-finished ones
Min 105-120 Review: context, namespace and behaviour of every objectSticking to numerical order works too, but note that tasks 11 and 6 are worth 10 points between them and are solved in 9 minutes: they are the best return in the mock exam.
4.3 A reminder before you begin
- Set the namespace at the start of each task, or use
-non every command. - Verify each task before moving to the next.
- If a task overruns its target by more than 50 %, flag it and skip.
- Never leave a task completely empty: there are partial marks.
When you finish, do not read the solutions yet. First do the integration exercises in the next section if you still have energy, or rest and come back later.
Common Mistakes and Tips
These are the errors that recur most in this particular mock exam.
| Mistake | Task affected | How to avoid it |
|---|---|---|
| Creating the Deployment without the requested label on the pods (only on the Deployment) | 1 | The label goes in spec.template.metadata.labels |
Ingress with pathType: Exact instead of Prefix |
2 | Use --rule="host/*=svc:80" with the asterisk |
Confusing envFrom with env |
3 | "All the keys" → envFrom; "one key" → valueFrom |
A different storageClassName on the PV and the PVC |
4 | They must match exactly, or the PVC stays Pending |
Adding only the toleration and forgetting the nodeSelector |
5 | A toleration permits, it does not compel: you need both |
backoffLimit at the wrong level of the CronJob |
6 | It goes in jobTemplate.spec, not in CronJob.spec |
Burstable QoS instead of Guaranteed |
7 | Identical requests and limits for CPU and memory |
Forgetting the pods/log subresource |
8 | Without it, kubectl logs fails even though you can list pods |
NetworkPolicy with no policyTypes |
9 | Without policyTypes: [Ingress] there is no effective deny |
Applying restricted and forgetting seccompProfile |
10 | The restricted standard requires it explicitly |
HPA showing <unknown> for metrics |
11 | The metrics-server is missing or the Deployment has no CPU requests |
rollout undo without checking the resulting image |
12 | Verify the final image with jsonpath |
| Diagnosing without looking at the events | 13, 14, 15 | kubectl describe and kubectl get events --sort-by=.lastTimestamp |
| Deleting a Deployment's pods to "fix them" | 13 | They are recreated identically: you have to fix the cause |
Three final tips for the mock exam:
- Treat the three troubleshooting tasks the way the real exam treats them: they are worth 24 of the 100 points, almost a quarter. If you are short on time, prioritise these over the creation tasks.
- If an object ends up
PendingorCrashLoopBackOff, the task is not done. It is not enough for the YAML to have been applied. - Note the real time each task took. The later analysis of where your time went is worth as much as the score.
Exercises
These three integration challenges go deeper than the mock exam tasks: each one brings together several modules of the course in a single objective. Do them after the mock exam, with no hard time limit but measuring it.
Exercise 1 — A complete component from scratch
Starting from an empty namespace rutas-norte-qa, get the full bookings-api component running and production-ready:
- A ConfigMap
api-configand a Secretbookings-postgres-credentials. - A Deployment
bookings-apiwith 3 replicas, imagenginx:1.27-alpine, consuming the ConfigMap through variables and the Secret through a file. - Readiness and liveness probes, and
GuaranteedQoS. - Its own ServiceAccount
sa-apiwith permission to read only the ConfigMapapi-config, and with no automatic token mounting. - A ClusterIP Service and an Ingress with host
qa.rutasnorte.es. - An HPA between 3 and 9 replicas at 70 % CPU.
- A PodDisruptionBudget guaranteeing at least 2 available pods.
- A default-deny ingress NetworkPolicy, with an exception for the
ingress-nginxnamespace.
Target: 30 minutes. Criterion: everything Running, populated endpoints, correct auth can-i and the pod with no token mounted.
Exercise 2 — Recovering a downed service
Deliberately cause this situation in rutas-norte-pro and then recover from it as if it were a real incident (use the mental runbook from module 11):
The symptom you are given: "users are getting 503s when they open the store; the Ingress responds but nothing reaches it from behind".
Setting up the fault (have someone else do it, or do it yourself and wait a day to forget the details):
kubectl scale deployment web-store --replicas=0 -n rutas-norte-pro
kubectl patch svc web-store-svc -n rutas-norte-pro \
-p '{"spec":{"ports":[{"port":80,"targetPort":8080}]}}'
kubectl set image deployment/web-store web-store=nginx:9.9-nonexistent -n rutas-norte-proWhat you are asked for: diagnose in order (Ingress → Service → endpoints → pods → container), document each finding, fix the three causes and leave the service running with 3 replicas. Write a short incident report with a timeline, root cause and preventive action.
Exercise 3 — Zero-downtime migration
In rutas-norte-pro, the web-store Deployment is serving traffic. Migrate its configuration storage from a ConfigMap mounted as a file to a different ConfigMap with new content, without any pod going unavailable at any point and without serving inconsistent content.
Steps you must work out: create the new ConfigMap, adjust the Deployment strategy, pause the rollout to group changes, apply the modification, resume, verify at each phase that there are at least 3 ready pods, and have the rollout undo ready in case something fails.
Prove with a request loop that there was not a single failure during the migration.
Solutions
Solution to Exercise 1
k create namespace rutas-norte-qa
k config set-context --current --namespace=rutas-norte-qa
# 1. Configuration
k create configmap api-config --from-literal=log_level=info --from-literal=max_connections=100
k create secret generic bookings-postgres-credentials \
--from-literal=username=bookings --from-literal=password='N0rt3-2026'
# 4a. ServiceAccount and minimal RBAC
k create serviceaccount sa-api
k patch serviceaccount sa-api -p '{"automountServiceAccountToken": false}'
k create role config-reader --verb=get --resource=configmaps --resource-name=api-config
k create rolebinding config-reader-b --role=config-reader \
--serviceaccount=rutas-norte-qa:sa-apiThe Deployment with everything wired in:
apiVersion: apps/v1
kind: Deployment
metadata:
name: bookings-api
namespace: rutas-norte-qa
spec:
replicas: 3
selector:
matchLabels: { app: bookings-api }
template:
metadata:
labels: { app: bookings-api }
spec:
serviceAccountName: sa-api
automountServiceAccountToken: false
containers:
- name: api
image: nginx:1.27-alpine
ports:
- containerPort: 80
envFrom:
- configMapRef: { name: api-config }
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
resources:
requests: { cpu: 200m, memory: 256Mi }
limits: { cpu: 200m, memory: 256Mi }
readinessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet: { path: /, port: 80 }
periodSeconds: 15
failureThreshold: 3
volumes:
- name: secrets
secret: { secretName: bookings-postgres-credentials }k apply -f api-qa.yaml
# 5. Exposure
k expose deployment bookings-api --name=bookings-api-svc --port=80 --target-port=80
k create ingress api-ingress --class=nginx --rule="qa.rutasnorte.es/*=bookings-api-svc:80"
# 6. HPA
k autoscale deployment bookings-api --min=3 --max=9 --cpu-percent=70
# 7. PDB
k create poddisruptionbudget api-pdb --selector=app=bookings-api --min-available=2# 8. NetworkPolicies
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: deny-ingress, namespace: rutas-norte-qa }
spec:
podSelector: {}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-from-ingress, namespace: rutas-norte-qa }
spec:
podSelector:
matchLabels: { app: bookings-api }
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: ingress-nginx }
ports:
- protocol: TCP
port: 80Full verification:
k get deploy,svc,ing,hpa,pdb,netpol -n rutas-norte-qa
k describe pod -l app=bookings-api -n rutas-norte-qa | grep "QoS Class" # Guaranteed
k exec deploy/bookings-api -n rutas-norte-qa -- ls /var/run/secrets/kubernetes.io/serviceaccount
# No such file or directory ← the token is NOT mounted
k auth can-i get configmap/api-config --as=system:serviceaccount:rutas-norte-qa:sa-api -n rutas-norte-qa # yes
k auth can-i list configmaps --as=system:serviceaccount:rutas-norte-qa:sa-api -n rutas-norte-qa # noThe two traps in this exercise: automountServiceAccountToken: false has to go on the pod (or the SA), and the --resource-name=api-config on the Role is what turns "read a ConfigMap" into a genuinely minimal permission.
Solution to Exercise 2
Diagnosis in the right order, from the outside in:
# 1. The Ingress: does it have a backend?
k describe ingress store-ingress -n rutas-norte-pro | grep -A5 RulesHost Path Backends
www.rutasnorte.es / web-store-svc:80 (<error: endpoints "web-store-svc" not found>)# 3. Are there any pods?
k get pods -l app=web-store -n rutas-norte-pro
k get deploy web-store -n rutas-norte-proFinding 1: the Deployment is scaled to 0.
k scale deployment web-store --replicas=3 -n rutas-norte-pro
k get pods -l app=web-store -n rutas-norte-proFinding 2: nonexistent image.
k describe pod -l app=web-store -n rutas-norte-pro | grep -A3 Events
# Failed to pull image "nginx:9.9-nonexistent": manifest unknown
k set image deployment/web-store web-store=nginx:1.27-alpine -n rutas-norte-pro
k rollout status deployment/web-store -n rutas-norte-pro
k get endpoints web-store-svc -n rutas-norte-proThe pods are Running but the Service still has no endpoints: there is a third cause.
Finding 3: the wrong targetPort.
k patch svc web-store-svc -n rutas-norte-pro \
-p '{"spec":{"ports":[{"port":80,"targetPort":80,"protocol":"TCP"}]}}'
k get endpoints web-store-svc -n rutas-norte-proIncident report:
INCIDENT: 503s on www.rutasnorte.es
Impact: web-store unavailable, 42 minutes.
Timeline:
T+0 503 alert from the Ingress
T+2 Ingress OK, Service with no endpoints
T+4 Deployment scaled to 0 -> scaled back to 3
T+6 Pods in ImagePullBackOff (nginx:9.9-nonexistent) -> image corrected
T+9 Pods Running but Service still without endpoints
T+11 targetPort 8080 against the real port 80 -> corrected
T+12 Service restored
Root cause: three unreviewed manual changes on production.
Prevention: manage rutas-norte-pro through GitOps (module 10), block manual
changes with RBAC, and alert on "Service with no endpoints" in Prometheus.The lesson of this exercise: one symptom can hide several chained causes. Fixing the first one and declaring the incident closed is the classic mistake. End-to-end verification (populated endpoints) is the only thing that proves it is resolved.
Solution to Exercise 3
# 0. Check loop in another terminal
while true; do
k run probe-$RANDOM --rm -q --image=busybox:1.36 --restart=Never -n rutas-norte-pro \
-- wget -qO- --timeout=2 web-store-svc >/dev/null 2>&1 \
&& echo -n "." || echo -n "X"
sleep 1
done# 1. New ConfigMap
k create configmap web-config-v2 \
--from-literal=index.html='<h1>Rutas Norte v2</h1>' -n rutas-norte-pro
# 2. Zero-downtime strategy
k patch deployment web-store -n rutas-norte-pro -p '
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0'
# 3. Pause to group changes
k rollout pause deployment/web-store -n rutas-norte-pro
# 4. Apply the changes (none of them roll out yet)
k patch deployment web-store -n rutas-norte-pro --type='json' -p='[
{"op":"replace","path":"/spec/template/spec/volumes/0/configMap/name","value":"web-config-v2"}
]'
k annotate deployment web-store -n rutas-norte-pro \
kubernetes.io/change-cause="migration to web-config-v2" --overwrite
# 5. Resume: now, and only now, a single rollout
k rollout resume deployment/web-store -n rutas-norte-pro
k rollout status deployment/web-store -n rutas-norte-pro --timeout=120sVerification during the process (in a third terminal):
watch -n1 'kubectl get deploy web-store -n rutas-norte-pro; \
kubectl get endpoints web-store-svc -n rutas-norte-pro'The request loop must show only dots, without a single X.
Rollback plan prepared before you start:
k rollout undo deployment/web-store -n rutas-norte-pro
k rollout status deployment/web-store -n rutas-norte-proThe three keys to this exercise:
maxUnavailable: 0is what guarantees pods are never missing;maxSurge: 1is what lets the rollout progress (with both at 0 it would deadlock).rollout pause/resumeavoids two consecutive rollouts when there are several changes: without pausing, the volumepatchand the annotation would trigger two waves of replacement.- The
readinessProbeis essential: without it, Kubernetes considers a pod "available" while it still cannot serve, and there would be downtime even withmaxUnavailableat 0.
Mock Exam Solutions
Now for it. Mark it task by task and note your score.
Task 1 (5 points)
k create deployment web-store --image=nginx:1.27-alpine --replicas=4 \
-n rutas-norte-dev $do > t1.yamlAdd the label in the pod template:
Check:
NAME READY UP-TO-DATE AVAILABLE
web-store 6/6 6 6
web-store-6c9d... 1/1 Running app=web-store,environment=dev,pod-template-hash=...The trap: putting environment=dev only in the Deployment's metadata.labels. The pods do not inherit it: it goes in spec.template.metadata.labels. A valid and quicker alternative: create the Deployment and then run k label pods -l app=web-store environment=dev, but then the new pods from the scale-up would not carry it.
Task 2 (7 points)
k expose deployment web-store --name=web-store-svc \
--port=80 --target-port=80 -n rutas-norte-dev
k create ingress store-ingress -n rutas-norte-dev --class=nginx \
--rule="www.rutasnorte.es/*=web-store-svc:80"Check:
k get endpoints web-store-svc -n rutas-norte-dev
k describe ingress store-ingress -n rutas-norte-dev | grep -A4 RulesNAME ENDPOINTS
web-store-svc 10.244.1.5:80,10.244.1.6:80,... (6 addresses)
Rules:
Host Path Backends
www.rutasnorte.es / web-store-svc:80 (10.244.1.5:80,...)The trap: the /* generates pathType: Prefix. Without the asterisk you would get Exact, which only matches the literal root. And if the describe shows <error: endpoints not found>, the Service is wrong.
Task 3 (6 points)
k create configmap api-config --from-literal=log_level=info \
--from-literal=max_connections=100 -n rutas-norte-dev
k create secret generic bookings-postgres-credentials \
--from-literal=username=bookings --from-literal=password='N0rt3-2026' -n rutas-norte-dev
k run bookings-api --image=nginx:1.27-alpine -n rutas-norte-dev $do > t3.yamlspec:
containers:
- name: bookings-api
image: nginx:1.27-alpine
envFrom:
- configMapRef:
name: api-config
volumeMounts:
- name: secrets
mountPath: /etc/secrets
readOnly: true
volumes:
- name: secrets
secret:
secretName: bookings-postgres-credentialsCheck:
k exec bookings-api -n rutas-norte-dev -- env | grep -E 'log_level|max_connections'
k exec bookings-api -n rutas-norte-dev -- cat /etc/secrets/usernameThe trap: envFrom is a sibling of env, not a child. And "mount the Secret as files" means a volume, not secretKeyRef.
Task 4 (8 points)
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-bookings
spec:
capacity: { storage: 3Gi }
accessModes: [ReadWriteOnce]
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath: { path: /mnt/bookings-data }
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-bookings
namespace: rutas-norte-pre
spec:
accessModes: [ReadWriteOnce]
storageClassName: manual
resources:
requests: { storage: 2Gi }
---
apiVersion: v1
kind: Pod
metadata:
name: bookings-postgres
namespace: rutas-norte-pre
spec:
containers:
- name: postgres
image: busybox:1.36
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /var/lib/data
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-bookingsCheck:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
pv-bookings 3Gi RWO Retain Bound rutas-norte-pre/pvc-bookings
NAME STATUS VOLUME CAPACITY
persistentvolumeclaim/pvc-bookings Bound pv-bookings 3GiThe double trap: the PV is cluster-scoped (no namespace), and storageClassName: manual must be on both. If you leave it out of the PVC, the default dynamic provisioner would try to create another volume and the PVC would not bind to yours.
Task 5 (6 points)
apiVersion: apps/v1
kind: Deployment
metadata:
name: notifications-worker
namespace: rutas-norte-pro
spec:
replicas: 2
selector:
matchLabels: { app: notifications-worker }
template:
metadata:
labels: { app: notifications-worker }
spec:
nodeSelector:
type: batch
tolerations:
- key: dedicated
operator: Equal
value: batch
effect: NoSchedule
containers:
- name: worker
image: busybox:1.36
command: ["sleep", "3600"]Check:
NAME READY STATUS NODE
notifications-worker-x 1/1 Running mock-worker2
notifications-worker-y 1/1 Running mock-worker2The trap: you need both pieces. The toleration only permits the pod to land on that node; the nodeSelector is what forces it to go there. With the toleration alone, the pods would go to any free node.
Task 6 (5 points)
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-reports
namespace: rutas-norte-pro
spec:
schedule: "30 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: reports
image: busybox:1.36
command: ["/bin/sh", "-c", "echo occupancy report generated"]Check:
k get cronjob nightly-reports -n rutas-norte-pro
k create job test-t6 --from=cronjob/nightly-reports -n rutas-norte-pro
k logs job/test-t6 -n rutas-norte-proThe trap: backoffLimit goes in jobTemplate.spec; concurrencyPolicy and successfulJobsHistoryLimit in CronJob.spec. And restartPolicy must be OnFailure or Never: Always makes the API reject the manifest.
Task 7 (7 points)
containers:
- name: nginx
image: nginx:1.27-alpine
resources:
requests: { cpu: 200m, memory: 256Mi }
limits: { cpu: 200m, memory: 256Mi }
readinessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet: { path: /, port: 80 }
periodSeconds: 10
failureThreshold: 3Check:
k rollout status deployment/bookings-api -n rutas-norte-pre
k describe pod -l app=bookings-api -n rutas-norte-pre | grep "QoS Class"The trap: Guaranteed demands requests identical to limits for CPU and memory and on every container. Setting only limits also works (Kubernetes copies the requests across), but setting requests different from limits gives Burstable and the task scores half.
Task 8 (7 points)
k create serviceaccount support-l1 -n rutas-norte-pro
k create role support-read --verb=get,list,watch \
--resource=pods,pods/log -n rutas-norte-pro
k create rolebinding support-read-b --role=support-read \
--serviceaccount=rutas-norte-pro:support-l1 -n rutas-norte-pro
k set serviceaccount deployment/web-store support-l1 -n rutas-norte-proCheck:
SA=system:serviceaccount:rutas-norte-pro:support-l1
k auth can-i list pods --as=$SA -n rutas-norte-pro # yes
k auth can-i get pods/log --as=$SA -n rutas-norte-pro # yes
k auth can-i delete pods --as=$SA -n rutas-norte-pro # no
k auth can-i list pods --as=$SA -n rutas-norte-dev # no
k get deploy web-store -n rutas-norte-pro \
-o jsonpath='{.spec.template.spec.serviceAccountName}' # support-l1The trap: pods/log is a separate resource. Without it, kubectl logs returns Forbidden even though you can list pods. And using ClusterRole+ClusterRoleBinding would grant access to every namespace: that breaks the criterion.
Task 9 (7 points)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
namespace: rutas-norte-pro
spec:
podSelector: {}
policyTypes: [Ingress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-store-from-ingress
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
app: web-store
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
ports:
- protocol: TCP
port: 80Check:
k get netpol -n rutas-norte-pro
# From inside the namespace: it must FAIL
k run test --rm -it --image=busybox:1.36 --restart=Never -n rutas-norte-pro \
-- wget -qO- --timeout=3 web-store-svcThe trap: without policyTypes: [Ingress] on the first policy, the object exists but denies nothing. And kubernetes.io/metadata.name is a label Kubernetes puts on every namespace automatically since 1.21: it is the reliable way to select one by name.
Task 10 (7 points)
k label namespace rutas-norte-pre \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.30 --overwriteapiVersion: v1
kind: Pod
metadata:
name: secure-auditor
namespace: rutas-norte-pre
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: auditor
image: busybox:1.36
command: ["sleep", "3600"]
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]Check:
k get ns rutas-norte-pre --show-labels
k get pod secure-auditor -n rutas-norte-pre
k run insecure --image=nginx -n rutas-norte-preError from server (Forbidden): pods "insecure" is forbidden:
violates PodSecurity "restricted:v1.30": allowPrivilegeEscalation != false,
unrestricted capabilities, runAsNonRoot != true, seccompProfileThe trap: the restricted standard demands four things at once and people forget seccompProfile: RuntimeDefault. The error message lists them all: use it as a checklist.
A side effect to bear in mind: when you apply enforce on rutas-norte-pre, the bookings-api Deployment from task 7 will keep running (PSA does not evict existing pods), but it will not be able to create new pods. If you do task 10 before task 7 or 11, the rollout will be blocked. It is exactly the kind of interaction between tasks that shows up in the real exam.
Task 11 (5 points)
Check:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS
bookings-api Deployment/bookings-api cpu: 0%/65% 2 8 2The trap: if TARGETS shows <unknown>/65%, there are two possible causes: the metrics-server is not installed or is not responding, or the Deployment has no CPU requests (the HPA calculates the percentage against the request). If you did task 7 first, the requests are already there and this works; if not, you have to add them.
Task 12 (6 points)
k patch deployment web-store -n rutas-norte-pro -p '
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0'
k set image deployment/web-store nginx=nginx:1.27.2-alpine -n rutas-norte-pro
k annotate deployment web-store -n rutas-norte-pro \
kubernetes.io/change-cause="bump to 1.27.2" --overwrite
k rollout status deployment/web-store -n rutas-norte-pro
k rollout history deployment/web-store -n rutas-norte-pro
k rollout undo deployment/web-store -n rutas-norte-proCheck:
k rollout status deployment/web-store -n rutas-norte-pro
k get deploy web-store -n rutas-norte-pro \
-o jsonpath='{.spec.template.spec.containers[0].image}'The trap: the container name in set image is not free-form. Check it first:
If set image cannot find the container, the command says error: unable to find container named ... and does nothing.
Task 13 (8 points) — Troubleshooting
Root cause: the Deployment references, through envFrom, a Secret that does not exist in the namespace.
k create secret generic queue-credentials \
--from-literal=host=queue.rutasnorte.es --from-literal=token=abc123 \
-n rutas-norte-dev
k get pods -n rutas-norte-dev -w
echo "The Deployment referenced through envFrom the Secret queue-credentials, absent from rutas-norte-dev" \
> /opt/diagnosis-13.txtCheck:
The trap: CreateContainerConfigError always points to a missing ConfigMap or Secret, or to a key that does not exist inside one that does. Do not confuse it with CrashLoopBackOff (the process starts and dies) or with ImagePullBackOff (an image problem). And the statement forbids changing the container configuration: the solution is to create the Secret, not to remove the envFrom.
Task 14 (8 points) — Troubleshooting
pod/redis-cache-6f8b...-xyz 1/1 Running
service/redis-cache-svc ClusterIP 10.96.87.4 6379/TCP
endpoints/redis-cache-svc <none> ← the symptomThe pods are fine but the Service cannot see them. That has only one explanation: the selector does not match the labels.
k get svc redis-cache-svc -n rutas-norte-pre -o jsonpath='{.spec.selector}'
k get pods -l app=redis-cache -n rutas-norte-pre --show-labelsRoot cause: the Service selects app=redis, but the pods carry app=redis-cache.
k patch svc redis-cache-svc -n rutas-norte-pre \
-p '{"spec":{"selector":{"app":"redis-cache"}}}'
echo "The Service selector was app=redis while the pods carry app=redis-cache" \
> /opt/diagnosis-14.txtCheck:
The trap: the statement forbids touching the pods, so changing their labels does not count (and it would also break the ReplicaSet, which would recreate them). You have to fix the Service. Remember the rule: a Service with no endpoints = a selector that does not match in 90 % of cases; the remaining 10 % are pods that are not Ready because a readinessProbe is failing.
Task 15 (8 points) — Troubleshooting
k get pod occupancy-reports -n rutas-norte-pro
k describe pod occupancy-reports -n rutas-norte-pro | tail -6NAME READY STATUS RESTARTS AGE
occupancy-reports 0/1 Pending 0 14m
Events:
Warning FailedScheduling 2m (x8 over 14m) default-scheduler
0/3 nodes are available: 1 node(s) had untolerated taint
{node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match
Pod's node affinity/selector.Root cause: the pod has nodeSelector: disk=nvme and no node carries that label.
k get pod occupancy-reports -n rutas-norte-pro -o jsonpath='{.spec.nodeSelector}'
k get nodes --show-labels | grep disk || echo "no node carries the disk label"Two valid solutions. The better one is to label the node, because the nodeSelector expresses a real requirement (a fast disk for generating reports) and the pod is immutable in that field:
The alternative —recreating the pod without the nodeSelector— also leaves the pod Running, but it loses the design intent:
k get pod occupancy-reports -n rutas-norte-pro -o yaml > t15.yaml
# remove the nodeSelector section
k delete pod occupancy-reports -n rutas-norte-pro
k apply -f t15.yamlecho "nodeSelector disk=nvme with no labelled node; solution: label mock-worker with disk=nvme to honour the scheduling requirement" \
> /opt/diagnosis-15.txtThe trap: the FailedScheduling message is the complete answer and you have to know how to read it. didn't match Pod's node affinity/selector is nodeSelector or affinity; untolerated taint is a taint with no toleration; Insufficient cpu/memory is resources; pod has unbound immediate PersistentVolumeClaims is storage. Four messages, four different causes.
Self-assessment
Add up the points you scored and find your band.
| Score | Verdict | What to do |
|---|---|---|
| 90-100 | Ready to sit it. Your level is clearly above the pass mark, with room for a bad day. | Book the date now. Spend the days beforehand keeping your speed up, not studying new topics. |
| 75-89 | Practically ready. You would pass, but with no cushion. | Identify the 2-3 tasks you got wrong, revise their lessons and repeat the mock exam in a week. Book the date for 2-3 weeks' time. |
| 60-74 | Borderline. This is the "could go either way" band. | Revise the domains of your failures with the mapping tables in lessons 12-01, 12-02 or 12-03. Practise for 2 more weeks and repeat the mock exam. |
| 40-59 | Revise specific domains. The knowledge is there, but there are gaps and speed is lacking. | Go back to the lessons of the modules you failed (see the next table). Work for 3-4 weeks before repeating. |
| 0-39 | Back to the modules. Fundamentals are missing, not just practice. | Redo the course from the relevant module, with the cluster in front of you and doing all the exercises. |
Diagnosis by failed task
Each task points to a specific module. If you failed one, you know exactly where to go back to.
| Failed task | Domain | Go back to |
|---|---|---|
| 1 | Workloads | 02-03-deployments |
| 2 | Services and networking | 04-02-service-types, 04-04-ingress-controllers |
| 3 | Configuration | 03-01-configmaps, 03-02-secrets, 03-03-environment-variables |
| 4 | Storage | 05-02-persistent-volumes, 05-03-persistent-volume-claims |
| 5 | Scheduling | 06-05-scheduling-affinity-taints-and-tolerations |
| 6 | Batch workloads | 06-03-jobs-and-cronjobs |
| 7 | Observability and resources | 07-01-health-checks-and-probes, 03-05-limitranges-and-qos-classes |
| 8 | Security and access | 08-01-role-based-access-control, 03-06-serviceaccounts-and-api-access |
| 9 | Network security | 04-06-network-policies, 08-04-network-security |
| 10 | Hardening | 08-02-security-contexts-and-hardening, 08-03-pod-security-policies-and-standards |
| 11 | Scaling | 09-01-horizontal-pod-autoscaling |
| 12 | Deployments | 02-04-updates-rollbacks-and-strategies |
| 13, 14, 15 | Troubleshooting | 07-06-debugging-and-cluster-events, 11-06-production-operations |
Time analysis
Beyond the score, review the times you noted:
| Situation | What it means | Remedy |
|---|---|---|
| High score but did not finish in time | You know how to do it, but slowly | Aliases, $do, copying from the documentation (lesson 12-04) |
| Finished early with a low score | You are fast but have gaps | Revise the modules in the previous table |
| Stuck more than 15 min on one task | Time discipline is lacking | Train the hard limit and skipping without regret |
| Failed because of namespace or context | A process problem, not a knowledge one | Train the context → solve → verify cycle |
| Failed all three troubleshooting tasks | The heaviest CKA domain | Break your cluster on purpose and fix it, over and over |
Conclusion
This is where the course ends.
You started, back in module 1, throwing a stand-alone Pod at a freshly built cluster and wondering why on earth so much machinery was needed to run a container. You finish by solving a fifteen-task exam against the clock on a complete production platform.
Between those two points lies the journey of Rutas Norte. You gave it shape with Deployments and Services, configured it without putting passwords in the code, published it with Ingress and TLS, gave it memory with persistent volumes and backups, taught it to start in the right order with initContainers and to survive node maintenance with taints and PodDisruptionBudgets. You gave it eyes with Prometheus and Grafana, locked it down with RBAC, NetworkPolicies, Pod Security and image signing, made it grow on its own with HPA and KEDA, packaged it with Helm and Kustomize, deployed it by itself from Git with Argo CD, and genuinely operated it: with real incidents, runbooks, canary deployments and a cost bill that had to be justified.
What you can do now, said plainly:
- Design and deploy a complete application on Kubernetes, from the manifest to the public URL with a certificate.
- Choose the right primitive for each problem: Deployment or StatefulSet, Job or CronJob, initContainer or sidecar, readiness or liveness.
- Manage configuration and secrets without exposing them, and control the resource consumption of everything that runs.
- Build, upgrade and repair a cluster: nodes, control plane, etcd, certificates.
- Diagnose. And this is what will set you apart most: faced with a symptom —a 503, a
Pendingpod, a Service with no endpoints— you know where to start, which command to run and how to reach the root cause in minutes. - Secure what you deploy, across all three phases: build, deployment and runtime.
- Scale, measure and tune, with data rather than hunches.
- Operate in production like a professional: with runbooks, with tested rollback procedures and with an awareness of cost.
And if you sit a certification, you also know how the exam works, which domains it covers, where each objective was studied and how to manage two hours of terminal against the clock.
Kubernetes will keep changing. New APIs will appear, others will be retired, the Gateway API will end up displacing Ingress, and tools that are standard today will be replaced. What does not change is what you have really learned: the declarative model, the reconciliation loop, the separation between desire and reality, and the method for working out why reality does not match the desire. That transfers to any version and any tool that comes next.
Build a cluster of your own and do not switch it off. Break things on purpose. Deploy your projects there even when you do not need to. The only way to keep this from rusting is to use it.
Safe travels.
Kubernetes Course
Module 1: Introduction to Kubernetes
- What Is Kubernetes?
- Kubernetes Architecture
- Key Concepts and Terminology
- Setting Up a Kubernetes Cluster
- The Kubernetes CLI: kubectl
- Objects, YAML Manifests and the Declarative Model
- The Course Project: the Rutas Norte Platform
Module 2: Core Kubernetes Components
- Pods
- ReplicaSets
- Deployments
- Updates, Rollbacks and Deployment Strategies
- Services
- Namespaces
- Labels, Selectors and Annotations
Module 3: Configuration and Secret Management
- ConfigMaps
- Secrets
- Environment Variables
- Resource Quotas and Limits
- LimitRanges and Quality of Service (QoS) Classes
- ServiceAccounts and API Access from Pods
Module 4: Networking in Kubernetes
- Cluster Networking
- Service Types
- Internal DNS and Service Discovery
- Ingress Controllers
- TLS and Certificate Management with cert-manager
- Network Policies
Module 5: Storage in Kubernetes
- Volumes
- Persistent Volumes
- Persistent Volume Claims
- Storage Classes
- Dynamic Provisioning, Expansion and Snapshots
- Backup and Restore of Persistent Data
Module 6: Advanced Kubernetes Concepts
- StatefulSets
- DaemonSets
- Jobs and CronJobs
- Init Containers, Sidecars and Multi-Container Patterns
- Scheduling: Affinity, Taints and Tolerations
- Custom Resource Definitions (CRDs)
- Operators and the Controller Pattern
Module 7: Monitoring and Logging
- Health Checks and Probes
- Metrics Server and kubectl top
- Monitoring with Prometheus
- Visualization and Alerting with Grafana and Alertmanager
- Centralized Logging with Elasticsearch, Fluentd and Kibana (EFK)
- Application Debugging and Cluster Events
Module 8: Kubernetes Security
- Role-Based Access Control (RBAC)
- Security Contexts and Container Hardening
- Pod Security Policies and Pod Security Standards
- Network Security
- Image Security
- Auditing, Scanning and Vulnerability Management
Module 9: Scaling and Performance
- Horizontal Pod Autoscaling
- Vertical Pod Autoscaling
- Cluster Autoscaling
- Event-Driven and Custom-Metric Scaling with KEDA
- High Availability: PodDisruptionBudgets and Topology
- Performance Tuning
Module 10: Kubernetes Ecosystem and Tooling
- Minikube and Local Environments with kind
- Kubeadm
- Helm
- Kustomize
- GitOps with Argo CD and Flux
- Managed Kubernetes: EKS, AKS and GKE
Module 11: Case Studies and Real-World Applications
- Deploying a Web Application
- Running Stateful Applications
- CI/CD with Kubernetes
- Deployment Strategies: Blue-Green and Canary
- Multi-Cluster Management
- Production Operations: Incidents, Runbooks and Costs
