Problem
In a development sandbox (a local kind cluster) the haproxy deployment is configured with a Horizontal Pod Autoscaler (HPA) that should react to both CPU utilization and a custom request‑latency metric. During load testing of an AI model serving pipeline the following symptoms appear:
- Average request latency climbs from ~30 ms to >500 ms.
- CPU usage on each
haproxypod spikes to 85 % (target is 70 %). - The HPA reports
desiredReplicas: 1even though the metrics exceed the thresholds. - No new
haproxypods appear;kubectl get hpashows a stable replica count.
Root Cause
The HPA relies on two metric sources:
- Resource metrics – CPU usage reported by the
metrics-server. - Custom metrics – request latency exported by a
PrometheusAdapterashaproxy_request_duration_seconds.
Investigation revealed two independent failures:
| Component | Issue | Why it mattered |
|---|---|---|
| metrics‑server | Running version 0.5.0 with --kubelet-insecure-tls flag disabled. |
CPU metrics from the kubelet could not be fetched, causing the HPA to see 0 CPU usage. |
| Prometheus Adapter | Metric name mismatch – HPA requested haproxy_request_latency_seconds while the adapter exposed haproxy_request_duration_seconds. |
The custom metric query returned an empty series, so the HPA ignored latency entirely. |
Because both metric sources returned 0, the HPA calculation never crossed the targetAverageUtilization or targetValue thresholds, leaving the replica count unchanged.
Debug
Step‑by‑step diagnostics that reproduced the failure:
- Check HPA status:
kubectl get hpa haproxy -o yaml
status: currentMetrics: - resource: name: cpu currentAverageUtilization: 0 type: Resource - type: External external: metric: name: haproxy_request_latency_seconds currentValue: 0 desiredReplicas: 1 currentReplicas: 1
metrics-server:kubectl top pod -l app=haproxy
NAME CPU(cores) MEMORY(bytes) haproxy-6d5f7b9c9b-8k2zv 0m 50Mi haproxy-6d5f7b9c9b-9l3wq 0m 48Mi
Zero CPU indicates the metrics API is not returning data.
metrics-server logs for TLS errors:kubectl logs -n kube-system deploy/metrics-server
error: failed to get node metrics: Get "https://10.96.0.1:10250/stats/summary": dial tcp 10.96.0.1:10250: i/o timeout
curl -s "http://prometheus:9090/api/v1/query?query=haproxy_request_latency_seconds"
{"status":"success","data":{"result":[]}}
No series returned – metric name mismatch.
Solution
Two corrective actions were required.
1. Fix metrics‑server TLS configuration
Update the metrics-server deployment to allow insecure kubelet connections (acceptable in a sandbox) or provide the proper CA bundle.
# Before
apiVersion: apps/v1
kind: Deployment
metadata:
name: metrics-server
namespace: kube-system
spec:
template:
spec:
containers:
- name: metrics-server
image: k8s.gcr.io/metrics-server/metrics-server:v0.5.0
args:
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
# missing --kubelet-insecure-tls
# After
apiVersion: apps/v1
kind: Deployment
metadata:
name: metrics-server
namespace: kube-system
spec:
template:
spec:
containers:
- name: metrics-server
image: k8s.gcr.io/metrics-server/metrics-server:v0.5.0
args:
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP
- --kubelet-insecure-tls
Apply the manifest and restart the pod:
kubectl apply -f metrics-server.yaml
kubectl rollout restart deployment/metrics-server -n kube-system
2. Align custom metric name with Prometheus adapter
Update the HPA definition to request the correct metric name (haproxy_request_duration_seconds) and ensure the adapter mapping is present.
# Before (HPA)
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: haproxy
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: haproxy
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: External
external:
metric:
name: haproxy_request_latency_seconds
target:
type: Value
value: 200ms
# After (HPA)
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: haproxy
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: haproxy
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: External
external:
metric:
name: haproxy_request_duration_seconds
target:
type: Value
value: 200ms
Redeploy the HPA:
kubectl apply -f haproxy-hpa.yaml
Verify
Confirm that both metric sources now report non‑zero values and that the HPA reacts.
- Generate load (e.g.,
heyorwrk) to push CPU >70 % and latency >200 ms. - Watch the HPA:
kubectl get hpa haproxy -w
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE haproxy Deployment/haproxy 85%/70%, 350ms/200ms 1 5 1 2m haproxy Deployment/haproxy 85%/70%, 350ms/200ms 1 5 3 3m
CPU and latency metrics now appear, and the replica count scales to 3.
Validate that the new pods are serving traffic:
kubectl get pods -l app=haproxy -o wide
Optionally, query Prometheus for the latency series to ensure values are being scraped:
curl -s "http://prometheus:9090/api/v1/query?query=haproxy_request_duration_seconds"
Prevent
- Metrics‑server health checks: Deploy a
PodDisruptionBudgetand aReadinessProbethat verifies/metricsendpoint returns data. Alert onmetrics-serverpod restarts or scrape failures. - Custom metric contract: Keep a single source of truth (e.g., a ConfigMap) that lists metric names used by HPAs and adapters. Include a CI lint step that validates the HPA YAML against the adapter’s
rulesconfig. - Sandbox isolation: In local clusters, explicitly enable
--kubelet-insecure-tlsor provide the node’s CA bundle to avoid silent metric loss. - Observability guardrails: Add a Grafana dashboard that plots HPA desired replicas vs. underlying metrics. Set alerts when
desiredReplicasstays atminReplicaswhile any metric exceeds its target for >5 minutes.
Related Topic Hub: Distributed Systems Troubleshooting Hub
FAQ
- Why does the HPA show
currentMetricswith zero values instead of the actual CPU usage?
Because themetrics-servercould not reach the kubelet over TLS. Enabling--kubelet-insecure-tls(or providing the correct CA) restores the CPU metric stream. - Can I use a Prometheus query directly in the HPA without an adapter?
No. The HPA only consumes metrics exposed via the Kubernetes custom‑metrics API. The Prometheus Adapter translates PromQL queries into that API. - What if the custom metric name is correct but the HPA still ignores it?
Check the adapter’srulesConfigMap for a matchingseriesQuery. A mismatch in label selectors or metric type will cause the adapter to drop the series. - Is it safe to run
--kubelet-insecure-tlsin production?
Generally not. In production you should mount the kubelet’s CA bundle into themetrics-serverpod and configure--kubelet-certificate-authorityinstead. - How do I debug a situation where the HPA scales down unexpectedly?
Inspect thestatus.currentMetricsfields, verify that both resource and external metrics are still above their targets, and ensure no conflicting HPAs or PodDisruptionBudgets are forcing a lower replica count.