New Case Study:   How Kitabisa Scales Unpredictable Donation Traffic Reliably with Kedify Arrow icon

Kubernetes Autoscaling Playbook - Download Free
back button All Posts

Warm Nodes on Schedule: Scaling Karpenter Capacity Buffers with KEDA

KEDA triggers driving a Karpenter capacity buffer of warm nodes through Kedify ScaleAdapter

by Jan Wozniak

August 12, 2026


Warm Nodes on Schedule: Scaling Karpenter Capacity Buffers with KEDA

Event-driven autoscaling has a physics problem. KEDA can react to a queue spike in seconds and the HPA can request fifty new pods immediately, but if those pods need new nodes, everyone waits for instances to boot. Karpenter is fast at provisioning, yet “fast” still means a minute or more of pending pods at the worst possible moment: right when demand arrived.

Karpenter v1.14.0 ships an answer called capacity buffers. A CapacityBuffer describes spare capacity as a number of chunks of a pod shape, and Karpenter keeps enough nodes around to fit those chunks at all times. Real pods land on warm capacity instantly, and the buffer refills behind them.

There is just one catch: the buffer’s size is a static number. This post is about removing the word “static”.

A Number Nobody Can Scale

Warm capacity is insurance, and insurance you do not adjust is either too expensive or too thin. Ten warm nodes during the morning ramp-up are great; the same ten nodes at 3am are a pure waste. The obvious move is to autoscale the buffer itself: large before known peaks, small at night, zero on weekends.

The buffer API clearly anticipates this, since the size lives in an ordinary spec.replicas field. But the CRD exposes no /scale subresource, which means the HPA, KEDA, and even kubectl scale cannot target it. Karpenter’s own blueprint repositoryExternal Link demonstrates “forecast-driven buffers” with a shell script that patches spec.replicas in a loop. The scheduling machinery behind buffers is genuinely clever; the way to size them is a cron job running kubectl patch.

We wrote recently about ScaleAdapterExternal Link, a small bridge resource in the Kedify Agent that gives KEDA a complete /scale contract on behalf of targets that lack one. Its replica field-path mode was built for exactly this shape of problem: anything with a numeric “how many” field on a Kubernetes resource becomes a first-class KEDA scale target. A CapacityBuffer is precisely that.

The Whole Setup

Buffers are alpha and gated. On the Karpenter side, enable the CapacityBuffer feature gate (on EKS: --set settings.featureGates.capacityBuffer=true on the Karpenter chart). On the Kedify side, enable the ScaleAdapter controller and grant it RBAC on buffers:

agent:
features:
scaleAdaptersEnabled: true
extraRbacRules:
- apiGroups: ["autoscaling.x-k8s.io"]
resources: ["capacitybuffers"]
verbs: ["get", "list", "watch", "update"]

A buffer references a PodTemplate describing one chunk of capacity. The containers in it never run; Karpenter turns the buffer into in-memory virtual pods that only participate in scheduling simulation, bin-packed like real pods. N chunks does not mean N nodes, it means “room for N of these”:

apiVersion: autoscaling.x-k8s.io/v1beta1
kind: CapacityBuffer
metadata:
name: warm-pool
spec:
podTemplateRef:
name: standard-workload-shape
replicas: 1

The adapter points its field paths at the buffer’s replica fields, and a completely ordinary ScaledObject points at the adapter. Here, a cron trigger holds ten chunks of warm capacity through business hours and lets the buffer drop to zero at night:

apiVersion: autoscaling.kedify.io/v1alpha1
kind: ScaleAdapter
metadata:
name: warm-pool
spec:
targetRef:
apiVersion: autoscaling.x-k8s.io/v1beta1
kind: CapacityBuffer
name: warm-pool
selector:
matchLabels:
app: warm-pool
desiredReplicasPath: '.spec.replicas'
currentReplicasPath: '.status.replicas'
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: warm-pool
spec:
scaleTargetRef:
apiVersion: autoscaling.kedify.io/v1alpha1
kind: ScaleAdapter
name: warm-pool
minReplicaCount: 0
maxReplicaCount: 20
triggers:
- type: cron
metadata:
timezone: Europe/Prague
start: 30 7 * * 1-5
end: 0 20 * * 1-5
desiredReplicas: "10"

And that is the entire integration. The HPA and KEDA own every decision about the number: bounds, stabilization, activation, trigger math. Karpenter owns everything about turning chunks into nodes: bin-packing, instance selection, consolidation. The adapter translates between them and does nothing else.

Kedify home screenshot

Every KEDA scaler, for every workload.

Try Kedify on your cluster today.

Get Started

What Warm Capacity Buys, in Dollars and Seconds

The interesting question is economic: how much does warm capacity cost, and how much scale-out time does it actually buy? So we measured that on real nodes, on AKS with Karpenter-based node auto-provisioning.

The benchmark is the ideal bin-packed scale-from-zero: a burst of 4 pods requesting 3 vCPU each. In the cold case Karpenter sees the pending pods, picks one right-sized Standard_D16ls_v5, and boots it. In the warm case the same node is already standing, which is precisely the state a capacity buffer maintains for you. Two cold runs measured 76 seconds each until all four pods were Ready, of which 65 seconds was the VM booting and joining; the warm runs completed within our 2-second sampling interval.

Now the dollars. That D16ls_v5 costs $0.776 per hour at Azure on-demand retail pricing in the benchmark region. Holding it warm for a 12.5-hour business window costs $9.70 a day, or $1.79 on spot. And with a cron trigger you do not even need the window: pre-warming 15 minutes ahead of the morning ramp costs about twenty cents, and every pod in that ramp starts 73 seconds sooner. If a cold scale-out sits between your users and their first response of the day, that is the cheapest latency win on the bill.

Two details keep the bill honest. First, buffers provision only what the cluster is actually missing: when we created a one-chunk buffer on a cluster with spare room, the Provisioning condition reported FitsExistingCapacity and no node was created. Headroom you already have is free. Second, scale to zero applies to the buffer itself, so outside the window the warm pool costs exactly nothing, and the emptied nodes are consolidated away within about a minute.

From Schedules to Forecasts

The cron trigger is the simplest useful policy, and probably the right first step: most capacity crunches are predictable. But the trigger slot accepts the entire KEDA catalog:

  • Queue depth or request rate. Warm the node pool as the work approaches, ahead of the workers that will process it, using the same Prometheus or OTel metricsExternal Link that scale the workload itself.
  • Forecasts. The Karpenter blueprint’s forecast scenario, minus the shell script: Kedify Predictive ScalerExternal Link learns the demand pattern from history and raises the buffer before the spike it expects, turning reactive insurance into proactive capacity planning.
  • Combinations. Multiple triggers on one ScaledObject mean the buffer follows whichever signal currently demands the most, for example a cron baseline with a metric-driven boost on top.

Requirements, in short:

  • Karpenter v1.14.0 or newer, with the alpha CapacityBuffer feature gate enabled (off by default): --set settings.featureGates.capacityBuffer=true on the Karpenter chart
  • Kedify Agent v0.6.8 or newer, with agent.features.scaleAdaptersEnabled: true and RBAC on capacitybuffers

Try It Today


Built by the core maintainers of KEDA. Designed for teams that scale with confidence.

Get started free