Extended Lab A - Deploy KubeQuiz
Extended Lab A - Deploy KubeQuiz
Infochallenge optional / after the course
Chapters: CRDs and operators, Gateway API, Persistent storage Cheat sheet: kubectl
This lab lives in a git repository, together with the application it deploys and a script that grades your work. Everything runs on support01:
sudo apt-get update && sudo apt-get install -y git
git clone https://github.com/ecaha/kubequiz.git ~/kubequiz
cd ~/kubequiz
./labs/verify.sh a0
A challenge lab gives you the task and the result, not the commands.
Each stage ends with a Show one answer box - open it after you have tried, not
before. ./labs/verify.sh <stage> tells you PASS or FAIL per criterion, so you
never have to wait for anybody.
Goal: put a stateful, highly available application on Kubernetes, then attack it and keep it running.
You are on your own cluster and you work alone. Nothing here depends on anybody else, and you can stop and resume at any stage.
This lab assumes nothing. If you skipped earlier labs, or your environment was rebuilt, Stages 0 and 1 put everything back. If you did those labs, both stages take two minutes to confirm and you move on.
Check your work at any time - ./labs/verify.sh a0 for one stage,
./labs/verify.sh a for all of them.
What you are building:
browser ─▶ support01:30080 ─▶ worker01:30080 ─▶ Gateway data plane
│
┌──────────────────────┴────────────┐
▼ ▼
kubequiz-web (nginx) kubequiz-api
Blazor WebAssembly ASP.NET Core
│
kubequiz-db-rw
│
CloudNativePG: 3 PostgreSQL
instances, 1 primary
Stage 0 - Your workstation
Everything in this lab runs from support01. Your laptop only ever holds an SSH session and a browser.
Task. Get onto support01 with a working kubectl, helm and git, and put
this repository in ~/kubequiz.
Done when
ssh student@192.168.56.101works (passwordlinux)kubectl get nodeslists 4 nodes, allReadyhelm versionandgit --versionanswer~/kubequiz/labs/verify.shexists
Check: ./labs/verify.sh a0
Think about it. ~/.kube/config contains a client certificate that is
effectively cluster-admin. What are the consequences of it sitting in a home
directory on a shared jump host?
Stage 1 - Cluster prerequisites
KubeQuiz needs three things this cluster does not have out of the box: a way in from outside (Gateway API plus a controller that implements it), somewhere to store data (PersistentVolumes - there is no dynamic provisioning here), and CPU metrics for the autoscaling stage.
Task. Make all three available, and create the namespace you will work in.
Done when
kubectl get gatewayclasslistsnginx- the NGINX Gateway Fabric controller is running in
nginx-gateway - nothing else already owns NodePort 30080
- three PersistentVolumes exist and are
Available kubectl top nodesreturns numbers- namespace
kubequizexists and is your default
Check: ./labs/verify.sh a1
Think about it. A PersistentVolume that is Released rather than
Available will never be reused, even though nothing is using it. Why does
Kubernetes refuse to recycle it automatically?
Stage 2 - The operator and the database
Task. Install CloudNativePG, then create a three-instance PostgreSQL cluster
named kubequiz-db from gitops/base/db/cluster.yaml. It must commit
synchronously to at least one replica.
Done when
- the CloudNativePG controller is
Runningincnpg-system kubectl api-resources --api-group=postgresql.cnpg.iolistsclusterskubectl get cluster kubequiz-dbreports 3 ready instances- exactly one Pod carries
role=primary - three PVCs are
Bound - Services
kubequiz-db-rw,-roand-rexist select version();runs inside the primary
Check: ./labs/verify.sh a2
Think about it. You installed a Deployment and some CRDs, then wrote thirty lines of YAML. Count the objects that appeared. Which of them would you have got wrong writing them by hand?
Stage 3 - Credentials
Task. The application expects the database connection as five environment
variables: PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD. The
operator has already generated a password - find it and build a Secret named
kubequiz-db-env from it.
Do not invent a password, do not read one from a file in this repository, and do not commit one anywhere.
Done when
- Secret
kubequiz-db-envhas all five keys PGPASSWORDequals the password in the operator’skubequiz-db-appSecretPGHOSTpoints at the read-write Service, not at a Pod
Check: ./labs/verify.sh a3
Think about it. Why kubequiz-db-rw and not kubequiz-db-1? What breaks in
Stage 7 if you get this wrong - and would you notice before then?
Stage 4 - Deploy the application
Task. Deploy the API and the frontend from gitops/overlays/lab. The
database schema must be created and seeded before the API starts serving.
The images are already published at ghcr.io/ecaha/kubequiz-api and
ghcr.io/ecaha/kubequiz-web, so there is nothing to build. Your nodes need to
be able to reach ghcr.io.
Done when
- Job
kubequiz-db-initisCompleted - Deployments
kubequiz-apiandkubequiz-webare Ready GET /api/whoamireturns 200 through the Service and names the PodGET /api/questions/activereturns a question with four options
Check: ./labs/verify.sh a4
If it does not come up: kubectl describe pod, then kubectl logs. The two
most common failures here are an image that cannot be pulled, and a Secret key
spelled differently from what the container expects.
Stage 5 - Reach it from your browser
Your laptop can route to 192.168.56.101 and nothing else. The Gateway’s
data plane listens on NodePort 30080 on the cluster nodes, which are on
192.168.128.0/24. Bridging that gap is your job.
Task. Publish the app at kubequiz.k8s.lab, make support01 relay TCP 30080
to a cluster node, and load the page in the browser on your laptop. Vote once.
Done when
- Gateway
kubequiz-gwreportsPROGRAMMED=True - an HTTPRoute sends
/apiand/hubtokubequiz-apiand/tokubequiz-web - on support01:
curl -H 'Host: kubequiz.k8s.lab' http://worker01:30080/api/aboutworks - the same request against
http://192.168.56.101:30080also works http://kubequiz.k8s.lab:30080renders in your laptop’s browser, and a vote registers on the Results page
Check: ./labs/verify.sh a5
Think about it. The relay points at worker01. Does the site break if the
Gateway’s data-plane Pod happens to run on worker03? Explain it in terms of
what a NodePort actually is.
Stage 6 - Probes
The API can be told to fail either health endpoint on demand: touch /tmp/unready makes readiness fail, touch /tmp/unhealthy makes liveness fail.
Task. Scale the API to 3 replicas. Using only those marker files, first take one Pod out of service without restarting it, then cause a different Pod to be restarted.
Done when
- you can show a Pod that is
Running,0restarts, and absent fromkubectl get endpoints kubequiz-api - you can show a Pod whose restart count went up, and quote the event that explains why
- all Pods are healthy and back in Endpoints at the end
Check: ./labs/verify.sh a6
Think about it. A Pod that is slow to start and a Pod that has hung look identical to a liveness probe. Which third probe exists to tell them apart, and what would happen here without it?
Stage 7 - Survive a database failover
One person cannot fill a room, so the API generates its own load:
POST /api/simulate?votes=5 casts five random votes.
Task. With votes arriving continuously, delete the primary PostgreSQL Pod. Report three numbers: how long writes failed, how many votes were lost, and which instance is primary afterwards.
Done when
tools/voters.shis running and printing200- you deleted the Pod labelled
role=primary - a replica was promoted and the old primary rejoined as a replica
- the vote total after the failover is greater than or equal to the total before it - nothing was lost
- you can state the outage window in seconds from the voter output
Check: ./labs/verify.sh a7
Do it again, this time deleting a replica. The service does not notice. Why is that the correct behaviour, and what did the operator do in the background?
Optional. Remove spec.postgresql.synchronous from the Cluster and repeat
the first experiment. The outage gets shorter. What did you trade away for it?
Stage 8 - Scale out, and find the bug
Task. Make the API scale automatically under load. Then open the Results page in two browser tabs, vote, and watch carefully.
Done when
- the HPA scales
kubequiz-apibeyond its minimum while/api/loadis hit - replicas come back down afterwards
- with more than one API replica you can demonstrate that one of the two Results tabs stops updating live
- you can explain the cause, and fix it with
gitops/overlays/prod
Check: ./labs/verify.sh a8
Think about it. Kubernetes scaled a broken application perfectly happily. Whose responsibility was the bug, and what does that tell you about “just add replicas”?
Stage 9 - Survive node maintenance
Task. Take a worker node out for maintenance without the application ever becoming unreachable.
Done when
- a request loop against the Gateway runs throughout with no failures
kubectl draincompletes on the node hosting most of the API Pods- you can show the PodDisruptionBudget that made the eviction wait
- the node is back in service and Pods have rescheduled onto it
Check: ./labs/verify.sh a9
Then: scale the API to 1 replica and drain its node. What does the PDB do now, and is that the behaviour you want at 3am?
Stage 10 - Lock it down
Task. Only the API may reach PostgreSQL. Prove it both ways.
Done when
- a throwaway Pod in the same namespace cannot open TCP 5432 to
kubequiz-db-rw - the API Pod still can, and the site still works
- you can name the object that enforces this and the field that selects its targets
Check: ./labs/verify.sh a10
Finished
./labs/verify.sh a
Leave the cluster running. Extended Lab B picks up exactly here and
replaces ghcr.io/ecaha/... with images you built yourself.