Stage 5 · Platform
Workloads & Scheduling
Horizontal & Cluster Autoscaling
HPA, VPA, CA, KEDA, and custom metrics.
Horizontal Pod Autoscaler
HPA automatically scales the number of pod replicas based on metrics. It watches metrics every 15 seconds (configurable) and adjusts replica count using the formula: desiredReplicas = ceil(currentReplicas * (currentMetric / targetMetric)). HPA works with Deployments, StatefulSets, and ReplicationControllers.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
- type: Pods
value: 4
periodSeconds: 15
selectPolicy: MaxscaleDown stabilizationWindowSeconds prevents thrashing by averaging metrics over 5 minutes. scaleUp policies allow aggressive scaling (double or +4 pods every 15s). selectPolicy: Max picks the policy that scales the most.
HPA Metric Types
HPA supports three metric types: Resource (CPU/memory), Pods (per-pod custom metrics), and Object (per-object metrics like Ingress requests). For queue-based scaling, use External metrics to scale based on queue depth from external systems.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: worker-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: worker
minReplicas: 0 # Scale to zero!
maxReplicas: 50
metrics:
- type: External
external:
metric:
name: sqs_queue_depth
selector:
matchLabels:
queue: my-queue
target:
type: AverageValue
averageValue: "10"
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 10
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 100
periodSeconds: 60External metrics come from external systems (SQS, Kafka, Prometheus). The averageValue target means one pod per 10 messages in the queue. Scale to zero is supported with minReplicas: 0.
HPA supports minReplicas: 0 since Kubernetes 1.23. This enables true serverless behavior. However, scaling from zero takes longer because HPA must wait for a ready pod. Combine with KEDA for faster cold-start scaling.
KEDA Event-Driven Scaling
KEDA (Kubernetes Event-Driven Autoscaler) extends HPA with 60+ scalers for external systems: Kafka, RabbitMQ, Prometheus, Cron, and more. It provides richer scaling logic, scale-to-zero, and event source authentication.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer
spec:
scaleTargetRef:
name: kafka-worker
pollingInterval: 15
cooldownPeriod: 300
minReplicaCount: 0
maxReplicaCount: 100
triggers:
- type: kafka
metadata:
bootstrapServers: kafka:9092
consumerGroup: my-group
topic: events
lagThreshold: "50"
offsetResetPolicy: latest
authenticationRef:
name: kafka-authKEDA watches Kafka consumer lag. When lag exceeds 50, it scales up. When lag drops, it scales down to zero after cooldownPeriod. authenticationRef provides credentials for the scaler.
Cluster Autoscaler
Cluster Autoscaler adjusts the number of nodes based on pending pods. If a pod cannot be scheduled due to insufficient resources, CA adds a node. If nodes are underutilized for 10+ minutes, CA drains and removes them. It works with cloud provider node groups.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
containers:
- name: cluster-autoscaler
image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.30.0
command:
- ./cluster-autoscaler
- --v=4
- --stderrthreshold=info
- --cloud-provider=azure
- --skip-nodes-with-local-storage=false
- --expander=least-waste
- --node-group-auto-discovery=acs:tag=k8s.io/cluster-autoscaler/enabled
- --balance-similar-node-groups
- --skip-nodes-with-system-pods=falseexpander=least-waste picks the node group that results in the least unused resources after scaling. balance-similar-node-groups distributes nodes across node groups with similar configs.
Custom Metrics Pipeline
To scale on custom metrics (e.g., requests per second, queue depth, error rate), you need: a metrics source (Prometheus), an adapter (Prometheus Adapter or KEDA), and the metrics server registered with the API server.
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-adapter-config
namespace: kube-system
data:
config.yaml: |
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 rule converts http_requests_total (counter) to http_requests_per_second (rate). The adapter exposes this as a custom metric that HPA can target. The 2m window smooths out spikes.
Scaling Best Practices
- Set resource requests — HPA calculates utilization from requests, not limits
- Use multiple metrics — scale on CPU for cost, custom metrics for business logic
- Configure scale-down stabilization to prevent thrashing
- Use PDBs to prevent scaling from causing downtime
- Monitor HPA status with kubectl get hpa -w to watch actual vs desired
- Test scaling by generating load and observing replica count changes
Run kubectl describe hpa to see current metrics, desired replicas, and limiting metrics. The Recommendation section shows which metric is driving the scaling decision. If currentReplicas equals minReplicas, the metric isn't high enough to scale up.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.