How to Write a Kubernetes HPA for a Custom Metric
CPU-based autoscaling is blunt for request-routing workloads - a pod can be pegged on CPU while actually handling very few requests, or idle on CPU while drowning in traffic. An autoscaling/v2 HPA with a custom Pods metric lets you scale directly on throughput. Here's how to write the manifest.
Why custom metrics instead of CPU
autoscaling/v1 only knew about CPU. autoscaling/v2 (the current API version)
adds three metric source types: Resource (CPU/memory), Pods (per-pod metric
averaged across replicas), and External (cluster-external metrics like SQS depth).
For a web service, scaling on http_requests_per_second is almost always more
accurate than CPU - request rate is what matters to users, and it responds to
traffic faster than CPU does.
The autoscaling/v2 HPA manifest
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "100"
A few key points:
type: Podsaverages the metric value across all pods matched by the HPA'sscaleTargetRef. The HPA scales up when the per-pod average exceedsaverageValue.averageValue: "100"means "keep each pod handling no more than 100 requests per second on average." Above that, the HPA adds pods; below it, the HPA removes them (down tominReplicas).minReplicas: 2is a production baseline - never drop to a single replica, so a restart doesn't mean downtime.
How the metric gets into Kubernetes
The HPA reads metrics from the Kubernetes metrics API. For custom metrics like
http_requests_per_second, the pipeline is:
- Your app exposes a Prometheus-format
/metricsendpoint. - Prometheus scrapes it.
- The Prometheus adapter (
k8s-prometheus-adapter) translates Prometheus queries into thecustom.metrics.k8s.ioAPI that the HPA reads.
The HPA manifest is only half the story - the adapter needs a rule that maps the metric name to a PromQL query:
# prometheus-adapter ConfigMap (rules section)
rules:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^http_requests_total$"
as: "http_requests_per_second"
metricsQuery: 'rate(http_requests_total{<<.LabelMatchers>>}[2m])'
Once the adapter is running, kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1
lists the available custom metrics and you can verify the HPA picks them up.
Apply and inspect
kubectl apply -f hpa.yaml
kubectl get hpa web-hpa
kubectl describe hpa web-hpa
kubectl describe hpa shows the current metric value, the target, and why the
HPA is or is not scaling. TARGETS will show <unknown>/100 until the adapter
is running and the metric is flowing.
KEDA: the modern alternative
KEDA (Kubernetes Event-Driven Autoscaling) is the preferred autoscaler for external metrics in newer clusters - it can scale on SQS depth, Kafka lag, Datadog dashboards, or any Prometheus query without a separate adapter. The mental model is the same: declarative manifest, named metric, target value. Worth knowing both.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Writing an autoscaling/v2 HPA with a Pods-type custom metric and an averageValue target
- Setting minReplicas/maxReplicas and wiring the scaleTargetRef to a Deployment
- Understanding the Prometheus adapter metric pipeline that feeds the HPA
FAQ
What is the difference between autoscaling/v1 and autoscaling/v2 in Kubernetes?
autoscaling/v1 only supports CPU. autoscaling/v2 adds Resource (CPU/memory), Pods (per-pod custom metrics), and External (cluster-external metrics) sources, letting you scale on application-level signals like requests per second.
What does type: Pods mean in an HPA metrics block?
The Pods metric type averages the named metric value across all pods targeted by the HPA. With averageValue: 100, the HPA adds pods when the per-pod average exceeds 100 and removes them when it falls below it.
How do custom metrics reach the Kubernetes HPA?
Custom metrics are exposed via the custom.metrics.k8s.io API. The Prometheus adapter translates Prometheus queries into that API so the HPA can read them. KEDA is a newer alternative that does the same job with a simpler configuration surface.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →