Kubernetes Autoscaling with HPA, VPA, and KEDA
Master Kubernetes autoscaling using HPA for horizontal scaling, VPA for right-sizing, and KEDA for event-driven workloads.
What you'll learn
- ✓How HPA scales pods based on CPU and custom metrics
- ✓How VPA right-sizes container resource requests
- ✓When to use KEDA for event-driven autoscaling
- ✓Practical YAML configurations for each approach
- ✓Common pitfalls and how to avoid them
Prerequisites
None — this post is self-contained.
Kubernetes does not automatically scale your workloads. It keeps the number of pods you asked for, but it does not know whether that number is right. Autoscaling bridges the gap between the capacity you provisioned and the capacity you actually need. Kubernetes offers three complementary autoscaling mechanisms, and understanding when to use each one is the difference between wasted resources and a system that adapts to demand.
Horizontal Pod Autoscaler (HPA)
HPA adds or removes pod replicas based on observed metrics. The most common trigger is CPU utilization, but HPA v2 supports memory, custom metrics, and external metrics.
Basic CPU-Based HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
This configuration scales the api Deployment between 2 and 20 replicas, adding pods when average CPU utilization exceeds 70 percent and removing them when it drops below.
HPA requires that your pods have CPU resource requests defined. Without requests, HPA cannot calculate utilization percentages:
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
Custom Metrics HPA
CPU is a lagging indicator. By the time CPU spikes, requests are already queuing. A better signal is often the application-level metric like requests per second or queue depth. Using the Prometheus adapter:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 2
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: 100
behavior:
scaleUp:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
The behavior block controls scaling speed. Scale up quickly (double capacity within 60 seconds) but scale down slowly (10 percent per minute with a 5-minute stabilization window). This prevents flapping during traffic fluctuations.
Vertical Pod Autoscaler (VPA)
VPA adjusts the CPU and memory requests of your containers rather than changing the number of replicas. It solves a different problem: right-sizing. Most teams guess at resource requests and either waste capacity with over-provisioning or cause OOMKills with under-provisioning.
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api
updatePolicy:
updateMode: Auto
resourcePolicy:
containerPolicies:
- containerName: api
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 2
memory: 2Gi
VPA observes actual resource usage over time and adjusts requests accordingly. In Auto mode, it evicts pods and recreates them with updated requests. In Off mode, it only provides recommendations without making changes, which is useful when you want to review suggestions before applying them.
VPA and HPA Together
Running VPA and HPA on the same metric (CPU) causes conflicts. VPA increases CPU requests, which changes the utilization percentage HPA uses, leading to unpredictable behavior.
The safe combination is HPA scaling on a custom metric (like requests per second) while VPA handles CPU and memory right-sizing. Alternatively, use VPA in recommendation-only mode and apply its suggestions manually during periodic reviews.
KEDA: Event-Driven Autoscaling
KEDA (Kubernetes Event-Driven Autoscaler) extends HPA with event-driven triggers. While HPA polls metrics on a fixed interval, KEDA can scale based on queue lengths, stream lag, cron schedules, and dozens of other event sources.
Scaling Based on Queue Depth
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-processor
spec:
scaleTargetRef:
name: order-processor
minReplicaCount: 0
maxReplicaCount: 50
triggers:
- type: rabbitmq
metadata:
host: amqp://rabbitmq.default.svc:5672
queueName: orders
queueLength: "10"
This scales the order-processor Deployment based on the RabbitMQ queue length. When the queue has 100 messages and the target is 10 messages per replica, KEDA requests 10 replicas. When the queue is empty, it scales to zero.
Scale to Zero
KEDA’s signature feature is scaling to zero. Standard HPA requires at least one replica. KEDA can scale a Deployment to zero replicas when there is no work, saving resources for batch processors, event handlers, and other bursty workloads:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: report-generator
spec:
scaleTargetRef:
name: report-generator
minReplicaCount: 0
maxReplicaCount: 10
cooldownPeriod: 300
triggers:
- type: cron
metadata:
timezone: America/New_York
start: 0 9 * * 1-5
end: 0 18 * * 1-5
desiredReplicas: "3"
- type: prometheus
metadata:
serverAddress: http://prometheus:9090
metricName: pending_reports
query: pending_reports_total
threshold: "5"
This example combines a cron trigger (scale up during business hours) with a Prometheus metric trigger (scale based on pending reports).
Choosing the Right Approach
| Scenario | Autoscaler |
|---|---|
| Web API with variable traffic | HPA on CPU or request rate |
| Batch job processing a queue | KEDA with queue trigger |
| Right-sizing over-provisioned pods | VPA in recommendation mode |
| Event-driven functions that idle often | KEDA with scale-to-zero |
| Stateless service with predictable patterns | HPA with cron-based min replicas |
Common Pitfalls
Missing resource requests. HPA cannot calculate CPU utilization without requests. Always define them.
Scaling on memory for leak-prone apps. If your application has a memory leak, HPA will scale out indefinitely because memory never decreases. Fix the leak; do not autoscale around it.
Too-aggressive scale-down. Scaling down fast causes request failures during traffic oscillations. Use stabilization windows of at least 5 minutes for scale-down.
Ignoring pod startup time. If your pod takes 60 seconds to start and your traffic spike lasts 30 seconds, autoscaling will not help. Optimize startup time or use proactive scaling based on leading indicators.
Wrap-up
HPA handles the common case of scaling replicas based on load. VPA right-sizes individual containers so you stop guessing at resource requests. KEDA adds event-driven triggers and scale-to-zero for bursty workloads. Use them together, carefully, and your cluster will adapt to demand instead of burning money on idle pods.
Related articles
- Kubernetes Kubernetes Cost Optimization: Right-Size Your Cluster
Reduce Kubernetes costs with right-sizing, autoscaling, spot instances, resource quotas, and monitoring. Practical strategies with real savings.
- Kubernetes Kubernetes Horizontal Pod Autoscaler Explained
Understand how HPA decides when to add or remove pods, the metrics it can scale on, and the tuning knobs that prevent flapping and runaway scaling.
- DevOps Container Orchestration: Docker Swarm vs Kubernetes
Compare Docker Swarm and Kubernetes for container orchestration. Learn the architecture, setup, scaling, and networking of both platforms with practical examples.
- DevOps GitOps with ArgoCD: Deploy Kubernetes Apps Automatically
Learn GitOps principles and deploy Kubernetes applications with ArgoCD. Covers installation, app configuration, sync policies, multi-environment setups, and rollbacks.