Kubernetes Autoscaling with HPA and Prometheus: What to Validate
Posted on April 23, 2020
Updated
Kubernetes Horizontal Pod Autoscaling changes replica counts; node autoscaling provides capacity for those replicas. CPU scaling uses the resource metrics API, while Prometheus-based custom scaling needs an adapter that exposes the required metrics API. Validate each layer before interpreting an HPA target.
Start with a stable HPA object
This API example targets a Deployment called web in a disposable namespace. It requires that Deployment, CPU requests on its containers and a working resource metrics API. Save as hpa.yaml.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: autoscale-lab
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 1
maxReplicas: 5
behavior:
scaleDown:
stabilizationWindowSeconds: 300
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60kubectl create namespace autoscale-lab
kubectl apply --dry-run=server -f hpa.yaml
kubectl get --raw /apis/metrics.k8s.io/v1beta1
# Apply only once the target Deployment and metrics API are ready:
# kubectl apply -f hpa.yaml
# kubectl -n autoscale-lab describe hpa webA successful dry-run checks the API shape. It does not prove that metrics are available or that a load test scales correctly. CPU utilization is relative to requests; it is not a rule that always adds exactly one Pod above the threshold.
Expose a Prometheus metric through an adapter
For custom metrics, define an adapter rule matching real metric names, labels and units from your installed exporters. Verify the corresponding custom.metrics.k8s.io response for every selected Pod and namespace. Current labels often differ from older examples using pod_name. Do not copy a historical PromQL expression without querying its output.
Lessons from our Magento deployment
Response time looked attractive as a scaling signal, but database or Redis overload could make requests slower without making application CPU busy. Adding PHP workers could then increase pressure on the dependency. Smoothing CPU over a longer window helped with spikes in that installation, while also delaying response to growth. Those observations describe that workload, not a universal best metric.
Validate scale-out, scale-in and failure
- Establish baseline resource requests, latency and error rate.
- Increase a representative load and observe HPA conditions and replicas.
- Confirm Pending Pods can obtain capacity within scheduling and quota limits.
- Reduce load and inspect stabilization and disruption.
- Simulate an overloaded dependency and confirm scaling does not worsen it.
Prometheus scrape annotations work only when discovery honors them. Operator installations may use ServiceMonitor or PodMonitor resources. A node autoscaler such as Karpenter has a separate lifecycle and budget.
Clean up the API lab
kubectl delete namespace autoscale-labThe CPU example above was API-validated. A separate live custom-metric run on EKS exercised Prometheus discovery, the adapter API, scale-out under load, scale-in, and a failed dependency. It used a successful-request rate, not the historical Magento exporter queries. Read Prometheus filtering before dropping metrics used by an HPA.
Sources and further reading
What the live custom-metric test verified
Each test Pod exposed successful_requests_total with its namespace and Pod name. Prometheus scraped it every five seconds with a four-second timeout. The exporter returned Content-Type: text/plain; version=0.0.4; charset=utf-8; Prometheus 3 rejected the original test endpoint when that header was missing.
This adapter rule translated the counter to a per-Pod rate. Use it only with an exporter that actually exposes these names and labels.
rules:
default: false
custom:
- seriesQuery: 'successful_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: namespace}
pod: {resource: pod}
name:
matches: successful_requests_total
as: successful_requests_per_second
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[30s])) by (<<.GroupBy>>)'The live HPA used a Pods metric, a target of five successful requests per second per Pod, a minimum of one and maximum of four replicas, and a short scale-down window for the test. Traffic increased replicas; after traffic stopped the deployment returned to one. Failed dependency responses did not increase the successful-work metric. That result validates this policy and fixture; it is not proof that the same metric fits another application.
metrics:
- type: Pods
pods:
metric:
name: successful_requests_per_second
target:
type: AverageValue
averageValue: '5'