🎓 Mission Debrief: HPA Can't Scale¶
What Happened¶
Your HorizontalPodAutoscaler (HPA) was configured correctly, but it couldn't scale because metrics-server was not installed.
Without metrics-server, Kubernetes has no way to know the CPU/memory usage of pods, so HPA can't make scaling decisions.
This is one of the most common HPA issues in new clusters!
How Kubernetes Behaved¶
HPA dependency chain:
HPA wants to scale
↓
Needs current CPU/memory metrics
↓
Queries Metrics API
↓
Metrics API served by metrics-server
↓
❌ metrics-server not installed
↓
HPA shows "<unknown>/50%"
↓
Cannot make scaling decisions
What metrics-server does:
metrics-server runs as a deployment in kube-system namespace
↓
Collects resource metrics from kubelet on each node
↓
Aggregates metrics (CPU, memory usage)
↓
Exposes them via Kubernetes Metrics API
↓
HPA, kubectl top, and other tools consume these metrics
The Correct Mental Model¶
Kubernetes Metrics Architecture¶
┌─────────────────────────────────────────────────┐
│ kubectl top │
│ HPA │
│ Dashboard / Monitoring │
└────────────────────┬────────────────────────────┘
│ Query metrics
↓
┌───────────────────────┐
│ Metrics API │
│ (metrics.k8s.io/v1) │
└──────────┬────────────┘
│ Implemented by
↓
┌───────────────────────┐
│ metrics-server │
│ (kube-system ns) │
└──────────┬────────────┘
│ Scrapes metrics
↓
┌───────────────────────────────────┐
│ kubelet on each node │
│ (cAdvisor provides container │
│ CPU/memory stats) │
└───────────────────────────────────┘
HPA Scaling Logic¶
# HPA configuration
spec:
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50 # Target: 50% CPU
How HPA decides to scale:
Current state:
- Deployment has 2 pods
- Pod 1: 80% CPU
- Pod 2: 70% CPU
- Average: 75% CPU
Target: 50% CPU
HPA calculation:
desiredReplicas = ceil(currentReplicas × (currentMetric / targetMetric))
desiredReplicas = ceil(2 × (75 / 50))
desiredReplicas = ceil(2 × 1.5)
desiredReplicas = ceil(3)
desiredReplicas = 3
Action: Scale up from 2 to 3 replicas
Scaling behavior:
- Scale up: Immediate (when CPU > target)
- Scale down: 5-minute stabilization window (prevent flapping)
- Cooldown: 3 minutes between scale-up events, 5 minutes for scale-down
metrics-server Configuration¶
Standard installation:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
For local development (kind, Docker Desktop, minikube):
# Add --kubelet-insecure-tls flag
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
Why --kubelet-insecure-tls?
- Production clusters have proper TLS certificates
- Local clusters often use self-signed certs
- metrics-server can't verify them without this flag
- Only use in development, never in production!
Real-World Incident Example¶
Company: Mobile gaming company (10M daily active users) Impact: 2-hour outage during product launch, 100% traffic loss Cost: $3.5M in lost revenue + $2M in refunds
What happened:
The team prepared for a big game launch with 10x expected traffic. They configured HPA to handle the load:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: game-server-hpa
spec:
scaleTargetRef:
kind: Deployment
name: game-server
minReplicas: 10
maxReplicas: 500
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
They tested in staging (which had metrics-server installed). Everything worked perfectly!
The failure (Launch day):
10:00 - Game launches
10:00 - Traffic starts ramping up (50K → 100K → 200K users/min)
10:05 - CPU on 10 pods hits 90%
10:05 - HPA should scale to 13 pods... but doesn't
10:08 - CPU hits 100%, pods start crashing
10:10 - All 10 pods restarting in loop
10:10 - Game completely down
10:10 - Team paged urgently
10:15 - Check HPA: "unable to get metrics for resource cpu"
10:20 - Check metrics-server: NOT FOUND
10:20 - Realize: Production cluster doesn't have metrics-server!
10:25 - Start installing metrics-server
10:30 - metrics-server running
10:35 - HPA starts working
10:40 - Scales to 180 pods
10:50 - Service stabilizes
12:00 - Fully recovered (2 hours of downtime)
Root cause: - Infrastructure as Code not enforced - Staging had metrics-server, prod didn't - No deployment checklist - Nobody verified metrics-server existed - Testing in wrong environment - Staging ≠ production - No monitoring - No alert for "HPA unable to scale"
The fix implemented:
-
Terraform module for cluster setup:
-
Pre-deployment validation:
-
HPA monitoring:
Lessons learned: 1. Verify dependencies exist before deploying features that need them 2. Staging must match production infrastructure 3. Monitor HPA health - alert on scaling failures 4. Load test with actual scaling - not just fixed pod count
Commands You Mastered¶
# Check HPA status
kubectl get hpa -n <namespace>
# Look at TARGETS - should show "X%/50%", not "<unknown>/50%"
# Describe HPA (see detailed status)
kubectl describe hpa <name> -n <namespace>
# Check if metrics-server is installed
kubectl get deployment metrics-server -n kube-system
# Install metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# For local clusters, add insecure TLS flag
kubectl patch deployment metrics-server -n kube-system --type='json' \
-p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--kubelet-insecure-tls"}]'
# Wait for metrics-server to be ready
kubectl wait --for=condition=available --timeout=60s deployment/metrics-server -n kube-system
# Test if metrics work
kubectl top nodes # Node CPU/memory
kubectl top pods -n <ns> # Pod CPU/memory
# Watch HPA scale in real-time
kubectl get hpa -n <namespace> -w
# Generate load to trigger scaling (testing)
kubectl run -it --rm load-generator --image=busybox --restart=Never -- /bin/sh -c "while true; do wget -q -O- http://service-name; done"
Best Practices for HPA¶
✅ DO:¶
-
Always install metrics-server:
-
Set resource requests (HPA needs them):
-
Use reasonable target utilization:
-
Set min/max replicas appropriately:
-
Monitor HPA health:
❌ DON'T:¶
-
Don't forget resource requests:
-
Don't use HPA with Deployment that has no requests:
-
Don't set minReplicas: 1 for critical services:
-
Don't use very aggressive scaling:
Advanced HPA Configurations¶
Multi-metric HPA¶
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: multi-metric-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 10
metrics:
# Scale on CPU
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# AND memory
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# AND custom metric (requires custom metrics adapter)
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
Behavior Configuration (Kubernetes 1.18+)¶
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # 5 min stabilization
policies:
- type: Percent
value: 50 # Max 50% scale down at once
periodSeconds: 60
- type: Pods
value: 2 # Max 2 pods removed per minute
periodSeconds: 60
selectPolicy: Min # Use most conservative policy
scaleUp:
stabilizationWindowSeconds: 0 # Immediate scale up
policies:
- type: Percent
value: 100 # Can double pod count
periodSeconds: 15
- type: Pods
value: 4 # Max 4 pods added per 15s
periodSeconds: 15
selectPolicy: Max # Use most aggressive policy
What's Next?¶
You've learned how to configure HPA and install the required metrics-server dependency.
Next level: Rollout strategies! You'll learn how misconfigured rolling update parameters can cause downtime.
Key takeaway: HPA requires metrics-server. Always verify it's installed and working before deploying HPAs to production!