Skip to content

5. Measure It

Kubernetes promises elasticity and self-healing. Put numbers on both. As always, run each experiment twice and report the second run; numbers in "expected shape" boxes are illustrative (they come from one run on a laptop, and yours will differ).

# Experiment Knob What you record
A fixed replicas vs HPA HPA deleted vs applied p95, p99, errors, max replicas, time to scale
B rolling update under load with vs without readiness probe + preStop failed requests during the rollout
C self-healing delete a pod / stop a node time until capacity is back (MTTR)

A. Fixed replicas vs HPA

# before: two fixed replicas
kubectl -n boxoffice delete hpa catalog
kubectl -n boxoffice scale deployment catalog --replicas=2
k6 run -e PEAK=1000 load/catalog-hpa.js

# after: the HPA
kubectl apply -k k8s/overlays/local
mkdir -p results && sh scripts/watch-hpa.sh > results/lab10-hpa.csv &
k6 run -e PEAK=1000 load/catalog-hpa.js
kubectl -n boxoffice get hpa catalog -w  # until REPLICAS is 2 again (minutes after k6 ends), then Ctrl+C
kill %1                                  # stop the watcher

From results/lab10-hpa.csv read: the time the CPU first crossed 60%, the time desired first increased (reaction time), the time ready reached the maximum (time to capacity), and the maximum number of replicas, and the time desired was back at 2 (scale-down). Plot cpu, desired and ready over time β€” a spreadsheet is enough β€” and add the chart to your results.

Expected shape (illustrative)

One run with PEAK=1000 on a laptop (kind, 3 nodes sharing 8 CPUs), starting from warm pods, sampled from the CSV:

t (min:s) CPU (% of request) desired current ready
0:00 3 2 2 2
1:48 63 2 2 2
2:03 79 3 2 2
2:34 123 5 3 3
3:33 247 8 6 4
4:05 109 8 8 3
5:37 131 8 8 5
7:31 220 8 8 8
10:26 13 7 8 7
13:00 4 2 3 2

The two warm pods crossed 60% 1:48 into the ramp (about 600 req/s); the HPA asked for a third pod 15 s later and for eight at 3:33, but the eighth pod was ready only at 7:31, when the hold was already over. On a laptop the new JVMs start on the same CPUs that serve the load and run k6: each took more than a minute to start and, still cold, failed its readiness probe on and off for a while (ready goes down as well as up). k6 reported p95 of 5–7 s, p99 β‰ˆ 19 s, 0.3% errors and dropped over 40% of the arrivals. The same ramp with two fixed replicas gave p95 β‰ˆ 34 ms, p99 β‰ˆ 180 ms and no errors: the two warm pods used about 300m each, far below their 1-CPU limit, so on this laptop the scale-out cost more CPU than it added. After the load dropped (8:00), pods left one at a time after the 2-minute stabilization window, back at 2 replicas at 13:00. On a real cluster nodes are not shared with the load generator and new nodes bring their own CPUs, but the shape β€” a lag between demand and ready capacity β€” is the same; that lag is what pre-scaling removes.

B. Rolling update under load

Keep a steady load running (the k6 script of lab 8 works as is) and restart the Deployment in the middle:

k6 run -e RATE=100 -e DURATION=2m load/catalog-otel.js &
sleep 30 && kubectl -n boxoffice rollout restart deployment/catalog
wait                                     # until k6 ends; note http_req_failed

Now remove the readiness probe and the preStop pause, wait for that change to roll out, and repeat:

kubectl -n boxoffice patch deployment catalog --type=json -p '[
  {"op": "remove", "path": "/spec/template/spec/containers/0/readinessProbe"},
  {"op": "remove", "path": "/spec/template/spec/containers/0/lifecycle"}]'
kubectl -n boxoffice rollout status deployment/catalog
k6 run -e RATE=100 -e DURATION=2m load/catalog-otel.js &
sleep 30 && kubectl -n boxoffice rollout restart deployment/catalog
wait                                     # until k6 ends; note http_req_failed
kubectl apply -k k8s/overlays/local      # restore the probes and the preStop pause

The startup probe still keeps a new pod out of the Service until the JVM answers; what changes is the other end: without the preStop pause, old pods stop accepting connections while they are still in Traefik's endpoint lists. Count the failed requests (http_req_failed) in both runs. In a test run: 0 failed requests with the probes, about 140 of 11,900 (1.2%, Traefik 502 and 504) without.

C. Self-healing

kubectl -n boxoffice delete pod -l app=catalog --wait=false; date +%T
kubectl -n boxoffice get pods -l app=catalog -w        # note the time both are 1/1 again

Repeat with docker stop boxoffice-worker2 (a node failure) and compare: what dominates each recovery time β€” detection, scheduling or JVM startup?

Results

Copy into results/lab10.md and fill in.

Metric Before After
A Β· Throughput at peak (req/s) β€” fixed 2 vs HPA
A Β· p50 (ms)
A Β· p95 (ms)
A Β· p99 (ms)
A Β· Error rate (%)
A Β· Max replicas / time to first scale-up (s) / time to full capacity (s) β€”
A Β· Time from load end to back at 2 replicas (s) β€”
B Β· Failed requests during a rollout β€” no probes vs probes + preStop
C Β· Pod deleted β†’ ready again (s) β€”
C Β· Node stopped β†’ pods ready elsewhere (s) β€”

Below the table: for the BoxOffice flash sale at 10:00, would you rely on the HPA, pre-scale, or both? Use your reaction-time numbers to justify it.

Back to the overview Quiz