HPA / Autoscaling
HorizontalPodAutoscaler watches a metric (CPU, memory, or a custom one) and scales a Deployments replica count up or down. Configure it with sensible min/max bounds, a target metric, and the stabilisation windows that prevent flapping. Get this right and your stack absorbs load without paging anyone.
CPU + custom metric HPA with sensible bounds
EXAMPLE
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: shop-api
namespace: shop
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: shop-api
# 1) Bounds — protect against runaway scaling AND keep a minimum capacity
minReplicas: 3
maxReplicas: 50
# 2) Metrics
metrics:
# CPU at 60% target lets new traffic land without queueing
- type: Resource
resource:
name: cpu
target: { type: Utilization, averageUtilization: 60 }
# Memory if your app is memory-bound (rarely the right primary signal)
- type: Resource
resource:
name: memory
target: { type: Utilization, averageUtilization: 75 }
# Custom metric (from Prometheus Adapter etc.)
- type: Pods
pods:
metric: { name: 'requests_per_second' }
target: { type: AverageValue, averageValue: '200' }
# 3) Behaviour — flap protection
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- { type: Percent, value: 100, periodSeconds: 30 } # double or less per 30s
- { type: Pods, value: 4, periodSeconds: 30 } # OR +4 pods per 30s
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300 # wait 5m before shrinking
policies:
- { type: Percent, value: 10, periodSeconds: 60 } # at most -10%/min
selectPolicy: Min # the gentler of the two
---
# 4) Required: resource requests on the Deployment
# Without 'requests', the autoscaler has nothing to compute utilisation against.
# spec:
# template:
# spec:
# containers:
# - name: api
# resources:
# requests: { cpu: '100m', memory: '256Mi' }
# limits: { cpu: '500m', memory: '512Mi' }
---
# 5) Metrics server must be running on the cluster
# kubectl get apiservice v1beta1.metrics.k8s.io
# If missing, install:
# kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
---
# 6) For CUSTOM metrics, install Prometheus Adapter (or KEDA for event-driven scaling)
# https://github.com/kubernetes-sigs/prometheus-adapter
# 7) Operate the HPA
# kubectl get hpa -n shop
# kubectl describe hpa shop-api -n shop
# kubectl get hpa shop-api -n shop -w
# 8) Inspect why it is NOT scaling
# kubectl describe hpa shop-api -n shop -> 'ScalingActive' condition
# Check: metrics-server running, resource requests set, target metric reachable
# 9) Decision tree for the target metric
# - Stateless HTTP API: CPU @ 50-70%
# - Long-poll / WS heavy: in-flight connections (custom metric)
# - Worker pulling from a queue: queue depth via KEDA
# - Memory-bound (JVM, Python): memory at 60-75%
# - Latency target? consider custom metric + percent of SLA budget
# 10) Common pitfalls
# - Forgetting resources.requests -> HPA cannot compute utilisation
# - minReplicas=1 in prod -> a single pod restart causes hard outage
# - maxReplicas too low -> caps capacity below incident peak
# - stabilizationWindowSeconds=0 -> oscillates around the target every 15s
# - HPA fighting Cluster Autoscaler -> pods pending, no node added -> right-size requests
Why it matters
Pair HPA with `minReplicas >= 3` + a 300-second scaleDown stabilisation window. Three replicas survive a single node loss; the 5-minute downscale window prevents the post-spike "we shrank to 5 pods then traffic returned" oscillation that drives PagerDuty in the 3am hour.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
kind: HorizontalPodAutoscaler
spec: { minReplicas: 2, maxReplicas: 10, targetCPUUtilizationPercentage: 70, scaleTargetRef: { kind: Deployment, name: api } }
Try it Yourself »
Discussion
Loading…