A deployment with a fixed replica count is a deployment with a fixed failure mode. Set it too low and your application collapses under peak traffic. Set it too high and your cloud bill silently climbs while most pods sit idle. Kubernetes ships with two native autoscaling mechanisms — the Horizontal Pod Autoscaler and the cluster autoscaler — and the ecosystem has produced a third in KEDA, which extends autoscaling to event-driven workloads. This guide explains how each mechanism works, where they overlap, and how to combine them into an autoscaling strategy that keeps your workloads responsive without burning budget.
## Why One Autoscaler Is Not Enough
Horizontal Pod Autoscaler, Vertical Pod Autoscaler, KEDA, and cluster autoscaler solve different parts of the same problem. HPA increases or decreases pod replicas based on CPU, memory, or custom metrics. VPA adjusts the resource requests of individual pods. KEDA scales pods to zero and drives scaling from external event sources such as message queues. The cluster autoscaler adds and removes nodes when pods cannot be scheduled due to resource shortages or when nodes are underutilised. None of them alone solves the full problem, and combining them incorrectly — for instance, running HPA and VPA on the same workload — causes conflicting scaling decisions that destabilise the deployment. Understanding the domain of each controller before wiring them together is the difference between an autoscaling stack that works and one that fights itself.
## Horizontal Pod Autoscaler
HPA is the most widely deployed Kubernetes autoscaler and the natural starting point for most workloads. It watches a metric — CPU utilisation, memory utilisation, or a custom metric exposed through the metrics API — and adjusts the replica count of a Deployment or StatefulSet to maintain a target utilisation level. The default target of 80% CPU means HPA adds replicas when average CPU exceeds that threshold and removes them when it drops below. The Kubernetes documentation details the full algorithm, including the stabilisation window that prevents rapid scale-down after a brief spike, which defaults to five minutes and is tuneable through the behaviour field introduced in the autoscaling/v2 API.
A typical HPA configuration for a web service that targets 70% CPU utilisation with a minimum of 2 and a maximum of 20 replicas looks like this:
```yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: web-service-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: web-service minReplicas: 2 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 behavior: scaleDown: stabilizationWindowSeconds: 300 ```
### HPA Limitations
HPA solves the pod-count problem but not the pod-sizing problem. If every pod requests 500m CPU but the application actually needs 200m under normal load, HPA scales correctly but leaves every pod over-provisioned — you are paying for wasted capacity at every replica. HPA also cannot scale a Deployment to zero, because the metric-based controller needs a running pod to collect metrics from. Workloads that should sleep between requests — background workers, batch processors, event consumers — need KEDA to bridge this gap. Finally, HPA relies on the cluster autoscaler to provision nodes when the scaled-up pods cannot be scheduled, but HPA itself has no visibility into node capacity. A surge in replicas that fills every node without triggering the cluster autoscaler fast enough results in Pending pods and a degraded service.
## Vertical Pod Autoscaler
VPA takes the opposite approach: instead of adjusting replica count, it adjusts the resource requests of individual pods based on historical and current usage. VPA operates in four modes. Off mode runs the recommender but takes no action — useful for collecting data before committing to automated changes. Initial mode sets resource requests at pod creation time but does not evict running pods. Auto mode evicts and recreates pods with updated resource requests when the recommendation changes significantly. Recreate mode acts like Auto but evicts pods regardless of the PodDisruptionBudget, making it suitable for workloads where short downtime is acceptable. The Kubernetes Vertical Pod Autoscaler project provides the controller, recommender, and admission plugin as separate components that you install into the cluster.
### When VPA Works Best
VPA excels with stateful workloads — databases, caches, message brokers — where replica counts are fixed by topology constraints and the only knob is per-pod resource allocation. It also works well for workloads whose resource needs drift over time: a Java service that gradually accumulates heap pressure, or a data pipeline whose memory profile changes with input size. The critical rule is to never run VPA in Auto or Recreate mode on a workload that also uses HPA. VPA evicts pods to apply new resource requests, and HPA interprets those evictions as a drop in replica count, triggering a scale-up. The two controllers chase each other, creating unnecessary churn. If a workload needs both horizontal and vertical scaling, use VPA in Off or Initial mode alongside HPA, and let the platform team periodically review the recommendations to right-size the resource requests manually.
## KEDA: Event-Driven Autoscaling
KEDA — Kubernetes Event-Driven Autoscaling — extends the HPA model by adding event sources beyond CPU and memory. It can scale a Deployment based on the length of an Azure Service Bus queue, the number of unprocessed messages in a Kafka topic, a Prometheus metric threshold, or a cron schedule. KEDA is a CNCF Graduated project and adds two components to a cluster: the KEDA operator that manages the scaling, and a metrics server that exposes event data to the HPA. Under the hood, KEDA creates a ScaledObject custom resource that generates an HPA and feeds it event-driven metrics, so the actual scaling is still performed by the native Kubernetes HPA controller.
Deployments scaled by KEDA can also scale to zero when no events are present — a capability that HPA alone cannot provide. This makes KEDA the right choice for workloads such as order processors, email dispatchers, and data ingestion pipelines that should consume resources only when there is work to do. A KEDA ScaledObject for a Kafka consumer might look like this:
```yaml apiVersion: keda.sh/v1alpha1 kind: ScaledObject metadata: name: kafka-consumer-scaler spec: scaleTargetRef: name: kafka-consumer minReplicaCount: 0 maxReplicaCount: 15 triggers: - type: kafka metadata: bootstrapServers: kafka-broker:9092 consumerGroup: order-processor topic: orders lagThreshold: "50" ```
### KEDA vs HPA: When to Use Which
The decision between KEDA and a plain HPA comes down to the workload's trigger domain. Use HPA when the scaling signal is a resource metric — CPU, memory — on a workload that should never scale to zero. Use KEDA when the scaling signal is external to the pod — a queue depth, a message count, a cron expression — or when scaling to zero is a requirement. Many clusters run both: HPA for always-on HTTP services and KEDA for event-driven background workers. The two controllers coexist cleanly because KEDA delegates the heavy lifting to the HPA controller, and the cluster autoscaler handles node provisioning for both.
## Cluster Autoscaler: Scaling the Infrastructure
Scaling pods is only half the equation. When HPA or KEDA increases replicas beyond the available node capacity, the new pods enter a Pending state and wait. The cluster autoscaler watches for unschedulable pods and provisions additional nodes from the underlying infrastructure — Compute Engine managed instance groups on GCP, Auto Scaling Groups on AWS, or VM Scale Sets on Azure. It also removes underutilised nodes after pods have been rescheduled elsewhere, subject to configurable thresholds and cooldown periods. On GKE, the cluster autoscaler is enabled through a single flag during cluster creation or update, and it integrates with GKE node pools to manage capacity per pool independently.
The cluster autoscaler is the infrastructure counterpart to pod-level autoscaling, and aligning their configuration is essential. A cluster with an aggressive HPA that can triple replica count in 30 seconds needs a cluster autoscaler with node pools large enough to absorb that growth without waiting for new nodes to boot. Pre-provisioning a buffer of spare capacity — through overprovisioning pods or a dedicated placeholder node pool — smooths the transition and is a common pattern in production clusters. Autoscaling that saves money on cloud resources but causes degraded service during every traffic spike is not saving money; it is deferring the cost to incident response and lost revenue.
## Autoscaling Strategy Checklist
Use the following checklist to validate your autoscaling configuration before the next traffic spike proves you missed a step:
- HPA is configured on every stateless Deployment with a defined minReplicas and maxReplicas, and the scale-down stabilisation window is at least 300 seconds. - VPA is running in Off or Initial mode on any workload that also uses HPA, so recommendations are collected without conflicting evictions. - KEDA is deployed for event-driven workloads that need to scale from zero or respond to queue depth, message count, or cron triggers. - Cluster autoscaler is enabled on every node pool, with a maximum node count that reflects your budget and a minimum that covers baseline load. - Resource requests are defined on every container in every namespace — the cluster autoscaler relies on accurate requests to decide which nodes are schedulable. - PodDisruptionBudgets are set on every Deployment with more than one replica so that the cluster autoscaler does not evict critical pods during scale-down. - Node pool sizes and instance types are reviewed quarterly against actual utilisation data to avoid paying for instance families that no workload uses. - Autoscaling parameters are stored in version control alongside the workload manifest, not configured ad hoc through the cloud console.
Autoscaling is a continuous tuning exercise, not a one-time configuration task. Workload patterns change, new services are deployed, and the metrics that drove last quarter's HPA targets may no longer apply. If your team wants a second pair of eyes on your autoscaling setup — including HPA and VPA interaction, KEDA integration for event-driven workloads, and a review of how your cluster autoscaler and node pool configuration affect your <a href="/blog/gke-cost-optimization-guide/">GKE cost profile</a> — Secpros can audit your clusters and return a prioritised action plan. Getting this right before the next peak saves more than cloud budget; it keeps your <a href="/blog/kubernetes-incident-response-playbook/">incident response playbook</a> from getting a workout.
## Sources
Details of the Horizontal Pod Autoscaler algorithm, API versions, and stabilisation behaviour are documented in the [Kubernetes Horizontal Pod Autoscaling](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/) guide. KEDA architecture, scaler types, and ScaledObject configuration are documented in the [KEDA documentation](https://keda.sh/docs/). The cluster autoscaler behaviour on Google Kubernetes Engine is described in the [GKE cluster autoscaler documentation](https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler).