Companion code for "Deploying Spring Boot 4 on Kubernetes: Probes, Graceful Shutdown, Limits and JVM Ergonomics". A dependency outage under three probe-group setups, a rolling restart under load four ways (three runs each), the JVM's ergonomic choices for nine pod shapes, one GC-heavy load under five CPU limits with throttling counters, and an HPA driven by a Micrometer gauge through prometheus-adapter. Measured on k3s v1.36.4. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C3TETMrqVUWeFkNtz3Jbo3
3.6 KiB
3.6 KiB
6. HPA on a custom Micrometer metric
← 5. Graceful shutdown · Index
CPU is a poor scaling signal for a JVM service: startup and JIT compilation burn CPU while serving nothing, GC competes with requests for the same quota (chapter 4), and a service waiting on I/O can be saturated at 20 % CPU. What you usually want to scale on is work in progress. The demo scales on in-flight requests.
The pipeline
- Micrometer.
WorkControllerregisters a gaugeapp.inflight.requests. The Prometheus registry renames it: dots become underscores, a gauge gets no suffix (a counter would get_total). - Prometheus scrapes
/actuator/prometheusevery 5 s (k8s/hpa/prometheus.yaml) and attachesnamespaceandpodlabels from Kubernetes service discovery. - prometheus-adapter turns the series into the
custom.metrics.k8s.ioAPI with one rule (k8s/hpa/prometheus-adapter.yaml). Theresources.overridesblock is what maps thepodlabel to a Kubernetes Pod - without it the metric exists in Prometheus and never appears in the API. - The HPA (
k8s/hpa/hpa.yaml) targets an average of 5 per pod.
What each end sees (hpa-custom-metrics-api.txt):
app_inflight_requests{application="orders"} 0.0
"metricName": "app_inflight_requests",
"timestamp": "2026-09-11T16:33:28Z",
"value": "0",
The run
2 clients, then 30 from t=20 s to t=140 s, then none; each request holds for 500 ms
(hpa-custom-metric.txt):
t clients metric desired ready pods
0s 2 0 1 1
22s 30 333m 1 1
32s 30 6333m 2 2
43s 30 10333m 4 2
54s 30 10333m 4 4
64s 30 7475m 4 4
148s 0 6416m 4 4
169s 0 2499m 4 4
179s 0 0 2 2
200s 0 0 1 1
- Reaction: ~12 s from load to the first scale-up - a 5 s scrape, the adapter's query, and the HPA controller's 15 s sync period.
- It overshoots on purpose. At t=43 the metric was 10.3 against a target of 5 on 2 pods, so the
HPA asked for 5 and was capped at
maxReplicas: 4. 30 clients over 4 pods settles at ~7.5 - still above target, which is what a ceiling looks like. - Scale-down took 40-60 s after the load stopped, because the adapter rule averages over 30 s and the HPA's scale-down stabilization window (shortened to 30 s here; the default is 300 s) holds the highest recommendation it saw.
- 7,209 requests, 0 failures through two scale-downs - the
preStopsleep from chapter 5 doing its job.
Choices in the rule worth knowing about
metricsQuery: avg_over_time(<<.Series>>{<<.LabelMatchers>>}[30s])smooths a gauge that jumps with every request. Without it the HPA chases noise; with too long a window it reacts late.- For a counter (say
http_server_requests_seconds_count) the query must be arate(...), and the adapter's documented examples rename..._totalseries to..._per_second- so the name the HPA asks for is not the name Micrometer exported. - prometheus-adapter 0.12.0 (May 2024) is still its latest release. KEDA's Prometheus scaler is the common alternative: it ships its own metrics API server, reads PromQL directly, and can scale to zero.