Horizontal Pod Autoscaling in Kubernetes

Khimananda Oli 7 min read Virtualization
Horizontal Pod Autoscaling in Kubernetes

By Khimananda Oli | Last reviewed: August 2026

Horizontal Pod Autoscaling in Kubernetes is the primary mechanism for dynamically adjusting application capacity to match real-time demand without manual intervention. When traffic spikes hit your Nepal-based e-commerce platform during a festival sale or a global SaaS product experiences viral growth, static replica counts either waste money or cause outages. This guide provides the exact configuration patterns, metric strategies, and tuning parameters I use in production environments to ensure Kubernetes autoscaling actually works when it matters most.

Metrics ServerCPU / Memory / CustomAggregated APIHPA ControllerReconcile Loop (15s)Scale DecisionDeploymentReplicaSetPods (min-max)Fetch MetricsPatch Replicas
Horizontal Pod Autoscaling in Kubernetes operates as a closed-loop control system fetching metrics and adjusting replicas

How does Horizontal Pod Autoscaling in Kubernetes calculate replica counts?

The HPA controller runs inside the kube-controller-manager and executes a reconciliation loop, typically every 15 seconds. During each cycle, it queries the Metrics API for current utilization values and applies a straightforward proportional algorithm. Understanding this math prevents the most common misconfiguration errors I see in audit engagements.

The core scaling formula

The controller uses this formula to determine desired replicas:

desiredReplicas = ceil[currentReplicas * (currentMetricValue / desiredMetricValue)]

If you have 4 pods averaging 80% CPU against a 50% target, the calculation becomes ceil[4 * (80/50)] = ceil[6.4] = 7. The controller patches the Deployment's replica count to 7. Note the ceiling function — Kubernetes always rounds up to avoid under-provisioning. This also means small fluctuations near the threshold can trigger scaling events, which is why stabilization windows matter.

Metric types and sources

  • Resource metrics: CPU and memory utilization from the Metrics Server. These are available by default in most managed clusters (EKS, AKS, GKE) and require no additional infrastructure.
  • Custom metrics: Application-specific values like requests-per-second, queue depth, or active connections. Requires Prometheus Adapter or similar to expose the custom.metrics.k8s.io API.
  • External metrics: Cloud provider metrics such as SQS queue length, Pub/Sub backlog, or CloudWatch alarms. Requires the external.metrics.k8s.io API and appropriate adapter.

In practice, I recommend starting with CPU for stateless web services and reserving custom metrics for worker pools where CPU correlates poorly with actual load. Memory-based scaling is risky for applications with lazy garbage collection or connection pooling, as memory often doesn't release promptly after load drops.

How do you configure Horizontal Pod Autoscaling in Kubernetes with YAML?

Modern clusters use the autoscaling/v2 API exclusively. The legacy v1 API supports only CPU and lacks multi-metric support. Below is a production-grade manifest I deploy for Laravel API backends handling variable traffic across Nepal and international regions.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-backend-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api-backend
  minReplicas: 3
  maxReplicas: 20
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 4
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Pods
          value: 2
          periodSeconds: 120
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 65
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 75

Critical configuration details

  1. Always set resource requests on containers. HPA calculates utilization as currentUsage / requestedAmount. Without requests, percentage-based targets fail silently and the HPA reports <unknown> metrics. This is the number one issue I troubleshoot.
  2. Set minReplicas ≥ 2 for production. Single-replica minimums create availability gaps during node failures or rolling updates. For SOC 2 compliance, I enforce minReplicas ≥ 3 across all customer-facing services.
  3. Define explicit scaling behavior. Default stabilization windows (300s down, 0s up) are too aggressive for scale-up and too slow for scale-down in many workloads. The example above allows faster scale-up (60s window, max 4 pods/min) while preventing flapping on scale-down (300s window, max 2 pods removed per 2 minutes).
  4. Use multiple metrics conservatively. The HPA picks whichever metric demands the highest replica count. Combining CPU and memory provides safety, but ensure both thresholds reflect genuine saturation points rather than arbitrary numbers.
ReplicasTime →60s Up300s DownBaselinePeak LoadStabilized
HPA stabilization windows prevent rapid flapping during transient load changes in Horizontal Pod Autoscaling in Kubernetes

When should you use custom metrics instead of CPU for Horizontal Pod Autoscaling in Kubernetes?

CPU utilization is a lagging indicator for many modern workloads. I switch to custom metrics when any of these conditions apply:

Workload TypeCPU CorrelationBetter MetricImplementation
API gateways / reverse proxiesPoor (I/O bound)Requests/sec, latency p95Prometheus + Adapter
Queue workersVery poorQueue depth, message ageSQS/RabbitMQ exporter
WebSocket / gRPC streamingModerateActive connectionsApp instrumentation
ML inference serversVariableGPU utilization, batch queueDCGM exporter
Database connection poolersMisleadingPool utilization %, wait timePgBouncer exporter

Setting up Prometheus Adapter for custom metrics

For teams already running Prometheus monitoring, the Prometheus Adapter translates PromQL queries into the custom.metrics.k8s.io API. A minimal adapter configuration mapping HTTP request rate looks like:

prometheusAdapter:
  rules:
    - seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
      resources:
        overrides:
          namespace: {resource: "namespace"}
          pod: {resource: "pod"}
      name:
        matches: "^(.*)_total$"
        as: "${1}_per_second"
      metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'

This exposes http_requests_per_second as a namespaced pod metric usable directly in HPA specs. Verify with kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second" before wiring it to autoscaling.

What are common pitfalls and debugging steps for Horizontal Pod Autoscaling in Kubernetes?

After years of operating clusters for fintech and SaaS clients, these are the failure modes I encounter repeatedly:

HPA shows <unknown> or fails to scale

  • Missing resource requests: Run kubectl describe deployment <name> and verify every container has resources.requests.cpu and memory defined. Percentage targets cannot function without them.
  • Metrics Server unavailable: Check kubectl top nodes. If it errors, the Metrics Server pod may be crashed, RBAC may be misconfigured, or the API service registration is stale. On self-managed clusters, reinstall with official manifests rather than outdated Helm charts.
  • Namespace mismatch: The HPA must reside in the same namespace as its target Deployment. Cross-namespace references are not supported.

Scaling is too slow or too aggressive

If pods take too long to start, the bottleneck isn't HPA — it's image pull time, readiness probe duration, or application startup. Profile your cold start with kubectl get events and optimize the container image using multi-stage builds. For overly aggressive scaling, increase the scaleUp stabilization window and add rate-limiting policies. Remember that each new pod consumes cluster resources; unbounded maxReplicas can exhaust nodes and trigger cascading failures.

Flapping between scale-up and scale-down

This occurs when the target threshold sits too close to normal operating variance. If your app idles at 45% CPU and targets 50%, minor jitter triggers constant scaling. Either raise the target to 65–70% or implement wider stabilization windows. I also recommend anomaly detection on metrics to distinguish genuine load shifts from noise before they reach the HPA.

Scaling Need DetectedHPAAdjust pod countWithin existing nodesSeconds-level responseVPAAdjust pod resourcesRight-size requests/limitsRequires restartCluster AutoscalerAdd/remove nodesWhen pods unschedulableMinutes-level responseProduction Best PracticeCombine HPA (horizontal) + Cluster Autoscaler (infrastructure)Use VPA in recommendation mode only alongside HPA
Understanding the distinct roles of HPA, VPA, and Cluster Autoscaler prevents conflicting configurations in Kubernetes

Implementing Horizontal Pod Autoscaling in Kubernetes for Production Reliability

Horizontal Pod Autoscaling in Kubernetes delivers genuine operational leverage only when configured with discipline. Start with conservative CPU targets and explicit behavior policies, validate metrics availability before going live, and layer custom metrics only after proving CPU insufficient. Pair HPA with Cluster Autoscaler to ensure underlying capacity keeps pace, and never treat autoscaling as a substitute for proper capacity planning or performance optimization. If your team needs help designing compliant, audit-ready autoscaling architectures that survive real traffic and regulatory review, reach out to discuss your infrastructure.

Frequently Asked Questions

It automatically adjusts pod replica counts based on observed CPU, memory, or custom metrics to match demand.

Ensure metrics-server is deployed and running, then apply an autoscaling/v2 HorizontalPodAutoscaler manifest targeting your deployment.

Use autoscaling/v2 as it supports multiple metrics, container resources, and external/custom metric sources reliably.

Yes, by integrating Prometheus Adapter or KEDA to expose custom metrics via the Kubernetes External Metrics API.

Check metrics-server availability, verify metric names match exactly, and ensure resource requests are defined on containers.

HPA changes replica count horizontally while VPA adjusts CPU/memory limits vertically without adding new pods.

Scale-up defaults to zero seconds and scale-down to five minutes to prevent flapping during transient spikes.

Yes, spec.minReplicas and spec.maxReplicas fields enforce hard limits preventing over-provisioning or complete service outage.

Use kubectl top pods to verify metrics flow and simulate load with tools like k6 or Locust in staging.

Yes, HPA triggers pod creation which signals Cluster Autoscaler to provision nodes when capacity is insufficient.

HPA stops receiving updates and maintains current replica count until metrics recovery occurs or manual intervention happens.

Configure aggressive scale-down policies and right-size resource requests so HPA removes excess pods during low traffic.

Prefer CPU for stateless services and memory for cache-heavy workloads since memory pressure causes OOM kills unlike CPU throttling.

Technically yes but avoid it unless pods are truly stateless since ordered scaling may break data consistency guarantees.

Query kube-state-metrics for hpa_status_current_replicas and events via kubectl get events to audit scaling actions.