Akash DahadProduct engineer · India

What is KEDA? Kubernetes autoscaling that can go to zero

Explainer Kubernetes and cloud cost 6 min read
KEDAKubernetesAutoscalingScale to zeroCron

KEDA scales Kubernetes workloads on queues, schedules and other events, down to zero pods. How it differs from the HPA, and which work belongs on it.

Some work doesn’t need to run all day. KEDA lets Kubernetes run zero pods for it until the work shows up, or until the clock says it’s time.

KEDA scales on events, not just CPU

KEDA stands for Kubernetes Event-driven Autoscaling. It’s an open-source project you install into your cluster. It became a graduated project of the CNCF, the foundation that also hosts Kubernetes, in 2023.

Plain Kubernetes scales on how busy your pods are. KEDA scales on what’s waiting for them: messages in a Kafka topic or a RabbitMQ or SQS queue, the result of a Prometheus query, or simply the time of day.

Three terms cover most of it:

  • Scaler. A connector that reads one event source and turns it into a number KEDA can scale on. KEDA ships with dozens, and you can write your own.
  • ScaledObject. The config you write. It points at a deployment and lists its triggers: which scalers to read and what thresholds decide the number of pods.
  • ScaledJob. The same idea for batch work. Instead of resizing a deployment, KEDA starts Kubernetes Jobs as events arrive.

If pods and deployments are new to you, Kubernetes in plain words covers them first.

How KEDA and the HPA split the work

The Horizontal Pod Autoscaler (HPA) is the autoscaler built into Kubernetes. Out of the box it scales on CPU and memory. For anything else it needs a metrics adapter, a separate component that feeds outside numbers into Kubernetes.

KEDA doesn’t replace the HPA. It works with it:

  • KEDA’s operator watches your ScaledObjects. It handles the step from zero pods to one, and from one back to zero, by itself.
  • From one pod upwards, it creates an HPA for you and hands that part over.
  • KEDA’s metrics server is the adapter. It serves the numbers from your scalers to that HPA.

So you keep the HPA’s scaling logic and plug KEDA’s event sources into it.

HPA on its ownKEDA
Comes with KubernetesYesNo, you install it
Scales onCPU and memory, other metrics through an adapter you set upDozens of event sources, through scalers
Scales to zeroFrom Kubernetes 1.37, on object or external metrics onlyYes, and it’s the default
Scales on a scheduleNoYes, with the cron scaler
Good forSteady services that get busier and quieterQueues, batch work and work tied to a time of day

That 1.37 change is recent. Scale-to-zero in the HPA is now beta and on by default, but CPU and memory can’t drive it, because they can only be measured on a pod that’s running. You still need an adapter to supply a queue length or another outside metric. KEDA is one way to get that adapter and the scalers in a single install.

Scaling to zero, and what it costs you

In a ScaledObject, minReplicaCount defaults to 0. When none of its triggers is active, KEDA scales the deployment down to zero pods. Three defaults decide how that behaves:

  • pollingInterval, 30 seconds: how often KEDA checks each trigger.
  • cooldownPeriod, 300 seconds: how long KEDA waits after the last active trigger before going back to zero. It only applies to that last step. Scaling from many pods down to one follows the HPA’s own rules.
  • maxReplicaCount, 100: the ceiling.

Zero has a price. The first piece of work after a quiet spell waits for a pod to start. The node may have to pull the image, and the app has to boot and load whatever it needs. For a queue that’s fine, because the work waits in the queue. For live web requests it usually isn’t. A Kubernetes Service doesn’t hold requests while a pod starts, so you’d need a separate layer that does.

Why our heavy indexing moved to a night shift

BHyve builds an AI knowledge platform. People ask questions and expect answers in seconds. Behind those answers is indexing: reading documents, cutting them into chunks and embedding them so they can be searched. Most of it is small. Bulk imports and large files are not.

Heavy work causes two problems when it runs next to live traffic. It competes for the same CPU, so one big import can slow down someone else’s question. And the capacity for it has to sit reserved all day, even though the big jobs only come now and then.

The question that settles it is when the work needs to be done. A bulk import has to be searchable by the next morning, not in the next few seconds. Work that can wait belongs on a queue, and a queue is exactly what KEDA scales on.

So bulk imports and large files now wait on a queue that drains at night. KEDA brings the night pods up when it’s time and takes them back to zero afterwards. By day, those pods don’t exist.

Before sizing them, we measured both shapes. Scaling out to 2-core pods beat a single 16-core pod, which cost 5.9× more. Small pods suit KEDA well, since it adds and removes capacity one pod at a time.

What it changed:

  • For 56% of every day, the night workers cost nothing, because there are no pods to pay for.
  • Live answers stayed fast, because heavy work no longer shares a pod with them.

The night shift also uses a GPU for embedding, only at night. That gets its own post.

A minimal ScaledObject

Here’s a minimal example for a night worker. It’s an illustration, not our config:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: night-worker
spec:
  scaleTargetRef:
    name: night-worker        # the Deployment to scale
  minReplicaCount: 0          # zero pods outside the window (also the default)
  cooldownPeriod: 300         # wait 5 minutes before going back to zero
  triggers:
    - type: cron
      metadata:
        timezone: "Etc/UTC"   # any IANA timezone name
        start: "0 22 * * *"   # 22:00
        end: "0 7 * * *"      # 07:00 the next morning
        desiredReplicas: "2"

Between 22:00 and 07:00 the cron trigger asks for two pods. Outside that window nothing is active, so after the cooldown KEDA takes the deployment to zero. A window that crosses midnight, like this one, works fine.

Two things to know before you copy it:

  • Don’t use desiredReplicas: "0" to mean “off”. Set minReplicaCount to 0 and let the window turn pods on.
  • Several triggers means the highest number wins. You could swap the cron trigger for a queue scaler, such as rabbitmq with a target queue length, or list both. But the HPA takes the highest pod count any trigger asks for, so a queue trigger would wake pods in the daytime whenever the queue has work in it. If you only want night pods, fill that queue only at night, or combine the triggers with a formula under KEDA’s advanced.scalingModifiers.

When KEDA is worth it, and when it isn’t

It’s worth it when:

  • Work arrives in a queue and can wait a little.
  • Work belongs to a time of day, like overnight imports or a nightly rebuild.
  • Pods are expensive and sit idle for long stretches.
  • You want to scale on something other than CPU, such as queue length or a Prometheus query.

It isn’t when:

  • The service must answer instantly. Zero pods means the first user waits for a cold start.
  • The service is steady and scales fine on CPU. The HPA alone does that.
  • The cluster is tiny, and one more operator to install, upgrade and watch costs more attention than it saves.
  • You’re on Kubernetes 1.37 or later, already run a metrics adapter, and need one external metric to reach zero. The HPA alone may now be enough.

The question to ask of every workload is the one that drove most of our savings: when does this actually need to exist? The server costs case study shows where asking it took us.

Quick answers

What is KEDA?
KEDA (Kubernetes Event-driven Autoscaling) is an open-source CNCF project that scales Kubernetes workloads on events such as queue length, a schedule or a metric query, and can scale them down to zero pods.
What is the difference between KEDA and the HPA?
The HPA is built into Kubernetes and scales on CPU and memory out of the box. KEDA adds dozens of event sources, handles scaling between zero and one pod itself, and creates an HPA to scale from one pod upwards.
Can KEDA scale to zero?
Yes. A ScaledObject's minReplicaCount defaults to 0, so when no trigger is active KEDA scales the workload to zero pods after the cooldownPeriod, which is 300 seconds by default.
Can the Kubernetes HPA scale to zero without KEDA?
From Kubernetes 1.37 the HPA can scale to zero by default, but only on object or external metrics, never on CPU or memory alone. You still need a metrics adapter to supply those metrics.
How do I scale pods on a schedule in Kubernetes?
KEDA's cron scaler sets a number of replicas between a start and an end cron expression, in a timezone you choose. With minReplicaCount set to 0, the workload drops to zero pods outside that window.
The case study behind this post Bringing server costs down: pay for capacity only when it's needed −75% idle AI pods