Autoscaling with HPA and VPA

KubernetesDevOps
Share on LinkedIn

Traffic spiked every weekday at nine; CPU stayed at forty percent while request latency doubled—threads blocked on JDBC pool size, not CPU. HPA on CPU added pods that contended for the same database. VPA recommendations showed requests set to 100m CPU but actual use at 800m; HPA on a custom http_requests_in_flight metric with right-sized requests from VPA fixed both problems.

Kubernetes offers Horizontal Pod Autoscaler (HPA) for replica count and Vertical Pod Autoscaler (VPA) for per-container CPU/memory requests. Node scaling (Cluster Autoscaler, Karpenter) sits below; these sit at the workload layer.

HPA on CPU

Requires metrics-server:

kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: checkout-api
  namespace: checkout
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout-api
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 100
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60

behavior prevents flapping—slow scale-down, cautious scale-up.

Ensure requests are set—HPA utilization is usage/requests, not limits.

HPA on custom metrics

Prometheus adapter exposes metrics:

metrics:
  - type: Pods
    pods:
      metric:
        name: http_requests_per_second
      target:
        type: AverageValue
        averageValue: "1000"

Install prometheus-adapter with rules mapping PromQL to custom.metrics.k8s.io.

Queue depth via external metrics:

  - type: External
    external:
      metric:
        name: sqs_queue_length
        selector:
          matchLabels:
            queue: orders
      target:
        type: AverageValue
        averageValue: "30"

VPA installation and modes

kubectl apply -f https://github.com/kubernetes/autoscaler/releases/latest/download/vertical-pod-autoscaler-release.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: checkout-api-vpa
  namespace: checkout
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout-api
  updatePolicy:
    updateMode: "Auto"  # Off | Initial | Recreate | Auto
  resourcePolicy:
    containerPolicies:
      - containerName: api
        minAllowed:
          cpu: 100m
          memory: 256Mi
        maxAllowed:
          cpu: 2
          memory: 2Gi

Off — recommendations only in VPA object status.

Initial — apply at pod creation.

Auto — evict and recreate pods with new resources (disruptive).

Combined strategy

Recommended production pattern:

  1. VPA in Off mode for two weeks—collect recommendations
  2. Apply recommendations to Deployment requests manually or switch to Initial
  3. HPA on CPU or custom metric for replica scaling
  4. PDB ensures minimum availability during VPA evictions

Avoid HPA CPU + VPA Auto on identical Deployment without resourcePolicy exclusions—race conditions on pod spec.

KEDA for event-driven scale

KEDA scales from zero based on Kafka lag, cron, Prometheus queries:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-processor
spec:
  scaleTargetRef:
    name: order-processor
  minReplicaCount: 0
  maxReplicaCount: 50
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: kafka:9092
        consumerGroup: order-processor
        topic: orders
        lagThreshold: "100"

Complements HPA for batch and queue workers.

Observability

Alert on HPA hitting maxReplicas, VPA recommendations far above requests (cost risk), and scaling loop oscillation. Dashboard: desired vs current replicas, metric values, VPA recommendation vs actual.

Cooldown during deploys

HPA may scale up during rolling update when old and new pods coexist briefly—raise minReplicas during deploy or use Flagger/Argo Rollouts for controlled traffic shift.

Memory-based HPA caution

Memory utilization HPA lags JVM heap growth—custom metrics (GC pause, pool wait time) often scale better than raw memory percent.

What to measure after rollout

Track error rates, tail latency, and resource utilization for two weeks after changes land—most regressions appear under real traffic mixes, not in staging smoke tests. Keep a rollback path documented: feature flags, Helm revision, or Git revert with known good digest. Review on-call pages tied to the topic quarterly; delete alerts that never fire and add thresholds that would have caught your last incident.

Run a short blameless postmortem if production surprised you, even for minor issues. The goal is updating this runbook section with one concrete lesson per quarter so the next engineer inherits context, not just configuration snippets.

Documentation your team should maintain

Maintain a one-page runbook link from your main service README: prerequisites, owner rotation, last drill date, and known sharp edges. Link to vendor docs in the Resources section below but capture org-specific decisions (CIDR ranges, cluster names, approval gates) in internal docs that stay current. New hires should deploy a safe canary within a week using only that runbook—if they cannot, the doc is incomplete.

Pre-production checklist

Before promoting to production, walk through this list with someone who was not the primary author—fresh eyes catch assumptions.

If any item is "we will do that later," treat it as a release blocker for tier-1 services.

Common questions from reviewers

Reviewers and auditors often ask whether this approach scales with team growth and whether it fails safely. Answer explicitly in your design doc: what happens when dependencies are down, when credentials expire, and when traffic doubles overnight. Prefer defaults that deny or degrade gracefully over defaults that fail open. Document known limits (throughput ceilings, supported versions, regions) in the same place operators look during incidents—avoid scattering critical constraints across Slack threads.

Resources

Frequently asked questions

Can I use HPA and VPA on the same Deployment?

Historically they conflicted when both adjusted the same containers. Modern guidance: use VPA in recommendation-only mode with HPA, or HPA on custom/external metrics while VPA adjusts requests on Deployments without HPA. Never let both mutate pod spec simultaneously without careful configuration.

What metrics can HPA use besides CPU?

Memory, custom metrics from Prometheus adapter, external metrics (queue depth, cloud monitoring), and object metrics (Ingress requests per second). CPU remains default but application-specific metrics often scale better for IO-bound services.

Does VPA restart pods when changing resources?

Yes, in Auto mode VPA evicts pods to apply new requests/limits. Use updateMode Off for recommendations only, or Initial to set resources only at pod creation. Plan disruption budgets before enabling Auto on production.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →