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.

Kubernetes Engineerkuberneteshpaautoscaling

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:

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:

  1. Your app exposes a Prometheus-format /metrics endpoint.
  2. Prometheus scrapes it.
  3. The Prometheus adapter (k8s-prometheus-adapter) translates Prometheus queries into the custom.metrics.k8s.io API 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

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

Fix a Kubernetes CrashLoopBackOffKubernetes projectHeal a Kubernetes Service With No EndpointsKubernetes projectFix a Kubernetes ImagePullBackOffKubernetes projectKubernetes roadmapStep by step to hiredKubernetes interview questionsSTAR answersAll Kubernetes projectsProjects hub

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 →