All ArticlesSystem Architecture•October 28, 2026•9 min read

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.

Close-up of server racks in a data center highlighting modern technology infrastructure.Photo by panumas nikhomkhai on Pexels

The 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.
ConfigurationDaily compute costp99 during 20x surgeScale-to-zero cold startSpot interruption recovery
HPA + Cluster Autoscaler$1,180520 msN/A4m 10s
HPA + Karpenter$820240 msN/A2m 05s
KEDA + Karpenter$690210 ms18 s1m 50s
HPA + KEDA + Karpenter + pause pods$650175 ms8 s warm buffer55 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.

yaml
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-cluster

Aggressive 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.

yaml
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: 65

Pitfalls 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.

yaml
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-credentials

Scale-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.

python
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

2 views•0 comments

Discussion (0)

Join the conversation

Loading comments...

Continue Reading

More production-grade engineering deep dives and architectural guides

Explore all articles