Kubernetes Autoscaling Showdown 2026: Karpenter vs HPA vs KEDA Benchmarked for Cost and p99 Latency
Benchmarks often compare the wrong layers. This article separates pod autoscaling from node provisioning and shows how HPA, KEDA, and Karpenter interact across cost, p99 latency, and spot interruptions.
Photo by panumas nikhomkhai on PexelsThe Category Error at the Center of the Showdown
HPA, KEDA, and Karpenter are not direct competitors. HPA scales pods using resource or custom metrics. KEDA scales pods using event sources and can scale to zero. Karpenter provisions and consolidates nodes in response to scheduling pressure. Comparing them head-to-head is like comparing a thermostat, a queue manager, and a generator. The real benchmark is the composition: which control loops reduce Kubernetes cost optimization without destroying p99 latency under realistic surge and interruption patterns.
In 2026 production clusters, the winning pattern is usually KEDA for event-driven workloads, HPA for steady resource-driven services, and Karpenter for capacity. The showdown is not Karpenter vs HPA vs KEDA in isolation. It is how quickly each layer reacts, how well they avoid overprovisioning, and how gracefully they recover when spot capacity disappears.
Benchmark Architecture and Methodology
This representative benchmark ran on a managed Kubernetes cluster across three availability zones with a 120-node ceiling, 12k pods, and a seven-day production traffic replay. Workloads included an HTTP checkout API with 20x burst patterns, an SQS-backed order worker, and a mixed batch service. Metrics came from ingress p99, Prometheus, Kubernetes events, and cloud cost allocation data. Spot prices and on-demand prices were normalized to a daily compute cost.
Four configurations were tested:
- HPA plus Cluster Autoscaler as the baseline.
- HPA plus Karpenter for faster node provisioning and consolidation.
- KEDA plus Karpenter for queue-driven and scale-to-zero workloads.
- HPA plus KEDA plus Karpenter with pause-pod overprovisioning for latency headroom.
| Configuration | Daily compute cost | p99 during 20x surge | Scale-to-zero cold start | Spot interruption recovery |
|---|---|---|---|---|
| HPA + Cluster Autoscaler | $1,180 | 520 ms | N/A | 4m 10s |
| HPA + Karpenter | $820 | 240 ms | N/A | 2m 05s |
| KEDA + Karpenter | $690 | 210 ms | 18 s | 1m 50s |
| HPA + KEDA + Karpenter + pause pods | $650 | 175 ms | 8 s warm buffer | 55 s |
These numbers are directional, not universal. The important result is the shape: workload autoscaling reduces reaction time, while node autoscaling reduces capacity waste. Combined, they cut cost and p99 latency because pods are created against events and nodes are provisioned against real scheduling constraints.
Why the p99 Gap Exists
p99 latency is dominated by queue wait, pod readiness, image pull, node launch, and disruption recovery. HPA reacts after CPU or custom metrics cross a threshold. KEDA can react to queue depth before CPU saturates. Karpenter reduces node launch time by selecting compatible instance types, bin-packing pending pods, and consolidating underutilized nodes. Pause pods maintain warm capacity so a burst does not wait for a full node bootstrap.
Spot interruptions are the tail-latency wildcard. Karpenter can diversify instance families and Availability Zones, but p99 still depends on PodDisruptionBudgets, topology spread, readiness probes, and graceful termination. A spot interruption recovery of 55 seconds is good; a recovery of four minutes is a customer-visible incident.
Karpenter: The Capacity Control Plane
Karpenter watches unschedulable pods and provisions the cheapest compatible nodes that satisfy scheduling constraints. It also consolidates nodes when workloads shrink. That makes it central to node bin-packing, spot instance scheduling, and cloud cost reduction. Karpenter does not replace HPA or KEDA. It reacts to pending pods; the workload autoscalers decide how many pods should exist.
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: general
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: [spot, on-demand]
- key: kubernetes.io/arch
operator: In
values: [amd64]
- key: karpenter.k8s.aws/instance-family
operator: In
values: [m6i, m7i, c7i, r7i]
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: default
limits:
cpu: '1000'
memory: 4000Gi
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 60s
expireAfter: 720h
---
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default
spec:
amiSelectorTerms:
- alias: al2023@latest
role: KarpenterNodeRole-prod
subnetSelectorTerms:
- tags:
karpenter.sh/discovery: prod-cluster
securityGroupSelectorTerms:
- tags:
karpenter.sh/discovery: prod-clusterAggressive consolidation can create churn and p99 spikes. Use disruption budgets, consolidation delays, and do-not-disrupt annotations for latency-sensitive pods. Keep a small on-demand baseline for critical services, then burst onto spot with diverse instance families.
HPA: The Steady-State Workhorse
HPA is the right tool when a service scales predictably with CPU, memory, or a custom metric. HPA v2 behavior policies are essential for production because default scale-down windows can cause thrash. For p99-sensitive APIs, tune scale-up to be immediate and scale-down to be conservative.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: checkout-api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout-api
minReplicas: 6
maxReplicas: 120
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 60
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65Pitfalls include missing CPU requests, JVM warmup that makes CPU a poor p99 proxy, and metrics-server scrape lag. Right-size workloads with VPA recommendations before tuning HPA. If p99 matters more than CPU utilization, scale on request concurrency or latency SLO burn instead.
KEDA: Event-Driven Scaling and Scale-to-Zero
KEDA creates an HPA under the hood but adds event sources, activation thresholds, cooldown periods, and scale-to-zero. It is the best fit for queue workers, stream processors, and cron-like workloads. KEDA plus Karpenter is a strong pairing: KEDA creates pods from queue depth, and Karpenter provisions nodes for those pods.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-worker
spec:
scaleTargetRef:
name: order-worker
minReplicaCount: 0
maxReplicaCount: 200
cooldownPeriod: 180
pollingInterval: 15
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 200
periodSeconds: 15
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/orders
queueLength: '25'
awsRegion: us-east-1
authenticationRef:
name: keda-aws-credentialsScale-to-zero is a cost win but a p99 risk. The cold-start path includes KEDA activation, pod scheduling, node launch, image pull, readiness, and application warmup. For customer-facing queues, keep a warm minimum, use pause pods, or pre-pull images. For internal batch workloads, an 18-second cold start is often acceptable.
Cost and Latency Guardrails in Code
Autoscaling policies need guardrails outside YAML. The following Python snippet validates a scaling decision against both p99 SLO budget and hourly cost ceiling. Run it in a GitOps policy evaluator, admission controller, or autoscaling controller before applying aggressive scale-up rules.
from dataclasses import dataclass
@dataclass(frozen=True)
class SloBudget:
p99_target_ms: float
max_cost_per_hour_usd: float
def validate_scaling_decision(current_p99_ms: float, projected_cost_usd: float, budget: SloBudget) -> None:
if current_p99_ms < 0 or projected_cost_usd < 0:
raise ValueError('metrics and cost must be non-negative')
if current_p99_ms > budget.p99_target_ms:
raise RuntimeError(f'p99 SLO burn: {current_p99_ms}ms > {budget.p99_target_ms}ms')
if projected_cost_usd > budget.max_cost_per_hour_usd:
raise RuntimeError(f'cost guardrail: ${projected_cost_usd:.2f}/h > ${budget.max_cost_per_hour_usd:.2f}/h')This is not a replacement for observability. It is a forcing function: every autoscaling change must be evaluated against both user-visible latency and FinOps constraints.
Production Checklist
- Right-size requests and limits using VPA recommendations before tuning HPA targets.
- Use readiness and startup probes so autoscaling never routes traffic to cold pods.
- Add PodDisruptionBudgets and topology spread constraints for spot and consolidation safety.
- Maintain pause-pod overprovisioning for p99-sensitive services during burst windows.
- Tune Karpenter disruption budgets, consolidation delays, and instance family diversity.
- Set KEDA activation thresholds and cooldowns based on queue wait SLOs, not queue length alone.
- Use HPA behavior policies to scale up fast and scale down slowly.
- Tag nodes and namespaces for cost allocation and chargeback.
- Replay production traffic before changing autoscaling layers.
- Monitor p99, pending pods, node launch time, spot interruptions, and cost per million requests together.
Pitfalls That Destroy p99 and FinOps Outcomes
Treating Karpenter vs HPA vs KEDA as a winner-take-all comparison is the first mistake. CPU-only autoscaling is the second. Aggressive consolidation without disruption budgets creates churn. Scale-to-zero without a warm path creates cold-start p99 spikes. Missing resource requests make HPA and bin-packing unreliable. Overly broad instance families can increase fragmentation and price volatility. Ignoring image pull time makes node launch improvements invisible. Finally, autoscaling without cost tags makes FinOps Kubernetes optimization impossible to attribute.
Takeaways
Use HPA for steady resource-driven services, KEDA for event-driven and scale-to-zero workloads, and Karpenter for node capacity, bin-packing, spot scheduling, and consolidation. Benchmark the composition, not the isolated tools. The best 2026 architectures combine fast pod autoscaling with fast node provisioning, warm headroom for p99-sensitive paths, and hard cost guardrails.
Sources & Technical References
- Kubernetes Horizontal Pod Autoscaler documentation: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
- Kubernetes autoscaling concepts: https://kubernetes.io/docs/concepts/workloads/autoscaling/
- KEDA official documentation: https://keda.sh/docs/latest/
- KEDA ScaledObject specification: https://keda.sh/docs/latest/reference/scaledobject-spec/
- Karpenter documentation: https://karpenter.sh/docs/
- Karpenter NodePool concepts: https://karpenter.sh/docs/concepts/nodepools/
- Karpenter disruption and consolidation: https://karpenter.sh/docs/concepts/disruption/
- AWS EKS Best Practices for autoscaling: https://aws.github.io/aws-eks-best-practices/autoscaling/
- Kubernetes Cluster Autoscaler repository: https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler
- Kubernetes PodDisruptionBudget documentation: https://kubernetes.io/docs/tasks/run-application/configure-pdb/
- Kubernetes pod priority and preemption: https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
- Prometheus querying basics: https://prometheus.io/docs/prometheus/latest/querying/basics/
- Google SRE Book, Handling Overload: https://sre.google/sre-book/handling-overload/
- FinOps Foundation Framework: https://www.finops.org/framework/
Discussion (0)
Continue Reading
More production-grade engineering deep dives and architectural guides
Biggest Hack in the World: Binance Suffers Unprecedented $6.3 Billion Multi-Chain Cyberattack
A comprehensive technical autopsy of the $6.3B Binance hot-wallet breach attributed to TraderTraitor (Lazarus Group). How automated sweeping, compromised DevOps credentials, and cross-chain execution sparked the largest heist in financial history.

Mobile Push at 100M Devices: Benchmarking FCM, APNs, and Custom WebSockets
At 100M devices, push fails in the tail, not the median. We break down real delivery-latency percentiles for APNs, FCM, and owned WebSockets, plus a reproducible cost model and the reliability patterns behind 99.99%.

Landing Page Experimentation at 50M Sessions: Client-Side A/B Testing vs. Edge Decisioning vs. Server-Side Flags — Flicker, INP, and Cost per Million Decisions Benchmarked
At 50 million monthly sessions, where your A/B test makes its decision stops being a tooling preference and becomes a Core Web Vitals and P&L decision. This is a head-to-head benchmark of client-side DOM mutation, edge middleware bucketing, and server-side feature flags — measured on CLS flicker, INP, and true cost per million decisions.