Introduction to KEDA and Event-Driven Autoscaling
What is KEDA?
KEDA (Kubernetes Event-Driven Autoscaling) is a Cloud Native Computing Foundation (CNCF) graduated project that extends Kubernetes with event-driven autoscaling capabilities. While the native Horizontal Pod Autoscaler (HPA) scales workloads based on resource metrics like CPU and memory, KEDA allows you to scale based on the number of events waiting in a queue, metrics from external systems, or custom application metrics. It acts as a thin layer on top of HPA, feeding external metrics into Kubernetes so that scaling decisions become event-aware.
KEDA supports a rich ecosystem of scalers — ready-to-use connectors for Apache Kafka, RabbitMQ, Azure Event Hubs, AWS SQS, GCP Pub/Sub, Prometheus, Redis, and many more. With KEDA, you can scale deployments down to zero when no events are present, drastically reducing infrastructure costs for event-driven workloads.
Why Event-Driven Autoscaling Matters
Traditional resource-based autoscaling often fails to capture the true demand of event-driven applications. A consumer processing messages from a queue might have low CPU usage but a growing backlog of unprocessed events. Relying only on CPU or memory metrics can lead to delayed scaling, dropped messages, and poor user experience.
KEDA solves this by:
- Scaling to zero — idle workloads consume zero resources when no events are present, which is impossible with standard HPA.
- Direct event-driven triggers — scaling decisions are based on actual workload demand (queue length, message rate, lag, etc.).
- Broad protocol support — over 50 built-in scalers cover databases, message brokers, monitoring systems, and cloud services.
- Predictable fine-tuning — you can define min/max replicas, polling intervals, cooldown periods, and idle replica counts.
- Job scaling — beyond deployments, KEDA can scale Kubernetes Jobs based on event sources, making batch processing event-driven.
How KEDA Works
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →KEDA consists of two main components:
- KEDA Operator — manages ScaledObjects and ScaledJobs, queries external scalers, and pushes metrics to the Kubernetes metrics API.
- Metrics Server — exposes the collected metrics so the HPA can read them and perform the actual scaling of deployments.
When you create a ScaledObject, KEDA starts polling the event source defined in the trigger. It translates the event count (e.g., queue length) into a target metric value and feeds it to the HPA. The HPA then adjusts the replica count of the target deployment or statefulset accordingly. The whole process is transparent — your pods don't need any KEDA-specific libraries.
Key Concepts: ScaledObject and Triggers
ScaledObject is the main custom resource that defines which workload to scale and how. It references a target deployment (or statefulset) and contains a list of triggers. Each trigger specifies a scaler type (e.g., rabbitmq, kafka) and its metadata (connection details, thresholds, etc.).
Triggers can be combined; when multiple triggers are defined, KEDA scales based on the maximum calculated replica count among them, ensuring capacity for the most demanding source.
For job-based workloads, KEDA provides ScaledJob, which creates one job per event or per batch of events, ideal for fire-and-forget processing.
Step-by-Step Implementation Guide
Prerequisites
- A running Kubernetes cluster (version 1.21+ recommended).
kubectlconfigured to communicate with the cluster.- Helm v3 (optional but recommended for installation).
- Access to the event source you plan to use (e.g., a RabbitMQ broker, Kafka cluster).
Installing KEDA
You can install KEDA using Helm or via static manifests. The Helm approach is simpler and keeps you updated.
Add the KEDA Helm repository and install:
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace
Verify the installation:
kubectl get pods -n keda
# You should see the operator and metrics-server pods running
For a non-Helm installation, apply the latest release YAML from the KEDA GitHub releases page:
kubectl apply -f https://github.com/kedacore/keda/releases/latest/download/keda.yaml
Deploying a Sample Application
We'll deploy a simple message consumer that reads from a RabbitMQ queue. The deployment starts with zero replicas; KEDA will scale it based on the queue length.
First, create a deployment manifest (consumer-deploy.yaml):
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-consumer
labels:
app: orders-consumer
spec:
replicas: 0 # start scaled down to zero
selector:
matchLabels:
app: orders-consumer
template:
metadata:
labels:
app: orders-consumer
spec:
containers:
- name: consumer
image: ghcr.io/kedacore/sample-rabbitmq-consumer:latest
env:
- name: RABBITMQ_HOST
value: "rabbitmq-service.default.svc.cluster.local"
- name: QUEUE_NAME
value: "orders"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
Apply it:
kubectl apply -f consumer-deploy.yaml
Ensure the deployment exists with zero replicas:
kubectl get deployment orders-consumer
# READY 0/0
Defining a ScaledObject for Event-Driven Scaling
Now we create a ScaledObject that tells KEDA to monitor the "orders" queue and scale the deployment accordingly.
Create scaled-object.yaml:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: orders-scaler
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: orders-consumer
minReplicaCount: 0 # allow scaling down to zero
maxReplicaCount: 20 # upper bound
cooldownPeriod: 300 # seconds to wait before scaling down after spike
pollingInterval: 30 # check queue every 30 seconds
triggers:
- type: rabbitmq
metadata:
host: "amqp://guest:guest@rabbitmq-service.default.svc.cluster.local:5672/"
queueName: "orders"
mode: "QueueLength" # number of messages
value: "10" # target queue length per pod
protocol: "amqp"
vhost: "/"
Apply it:
kubectl apply -f scaled-object.yaml
KEDA will now poll the RabbitMQ queue every 30 seconds. When the queue length exceeds the threshold (10 messages per pod), it will feed a desired replica count to the HPA, scaling up the deployment. As the queue drains, the replica count reduces, eventually reaching zero if no messages remain.
Testing the Autoscaling Behavior
To see scaling in action, publish messages to the "orders" queue. You can use the RabbitMQ management interface or a simple producer pod.
For example, run a temporary producer pod that sends 100 messages:
kubectl run orders-producer --image=ghcr.io/kedacore/sample-rabbitmq-producer:latest \
--env="RABBITMQ_HOST=amqp://guest:guest@rabbitmq-service.default.svc.cluster.local:5672/" \
--env="QUEUE_NAME=orders" --env="MESSAGE_COUNT=100" --rm -it --restart=Never
Watch the deployment scale up:
kubectl get deployment orders-consumer -w
# Observe replicas increasing from 0
Inspect the ScaledObject status:
kubectl describe scaledobject orders-scaler
# Shows trigger status, last poll time, and HPA reference
Once the queue empties, after the cooldown period (300 seconds), the deployment will scale back down to zero.
Using Other Popular Scalers
KEDA's power lies in its scaler ecosystem. Below are examples for Apache Kafka and Prometheus. The structure remains the same — only the trigger type and metadata change.
Apache Kafka Scaler
Scale based on consumer group lag for a specific topic:
triggers:
- type: kafka
metadata:
bootstrapServers: "kafka-broker1:9092,kafka-broker2:9092"
consumerGroup: "orders-group"
topic: "orders"
lagThreshold: "5" # desired max lag per pod
activationLagThreshold: "0" # optional threshold to activate from zero
Prometheus Scaler
Scale based on any Prometheus metric, e.g., HTTP request rate:
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-server.monitoring.svc.cluster.local:9090
metricName: http_requests_total
threshold: "100"
query: "sum(rate(http_requests_total{job=\"my-app\"}[1m]))"
You can mix multiple triggers in the same ScaledObject; KEDA will use the highest calculated replica count across all triggers.
Scaling Jobs with ScaledJob
For workloads that run to completion (batch processing), KEDA offers ScaledJob. Instead of maintaining a pool of long-running pods, it creates a new Kubernetes Job for each event or group of events. This is perfect for processing files from blob storage, messages in a queue as individual tasks, or one-off data transformations.
Example ScaledJob that processes one job per message in a RabbitMQ queue:
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: orders-job-scaler
spec:
jobTargetRef:
template:
spec:
containers:
- name: processor
image: my-job-processor:latest
env:
- name: QUEUE_MESSAGE
value: "{{.Message}}" # injected event data
restartPolicy: Never
backoffLimit: 3
triggers:
- type: rabbitmq
metadata:
host: "amqp://guest:guest@rabbitmq-service.default.svc.cluster.local:5672/"
queueName: "orders"
mode: "QueueLength"
value: "1" # one job per message
minReplicaCount: 0
maxReplicaCount: 100
scalingStrategy:
strategy: "accurate" # or "immediate"
Best Practices for Production
- Set appropriate min/max replica counts — Use
minReplicaCount: 0to truly scale to zero, but if you need warm pods for latency, setminReplicaCount > 0or useidleReplicaCountto keep a few replicas idle even when no events exist. - Fine-tune polling intervals and cooldowns — The
pollingIntervalcontrols how often KEDA checks the event source. For high-frequency data, a lower value (e.g., 10s) helps react faster, but be mindful of rate limits on the scaler's API. ThecooldownPeriodprevents rapid scale-down after a burst, reducing flapping. - Define resource requests and limits — Always set container resource specifications. KEDA relies on the HPA, which can be influenced by resource metrics; clean resource definitions improve scheduling and avoid unexpected throttling.
- Secure scaler connections — Never hard-code credentials in ScaledObject metadata. Use
TriggerAuthenticationto reference Kubernetes secrets for hosts, passwords, API keys, or certificates. - Combine triggers thoughtfully — When using multiple triggers, ensure their thresholds align logically. For example, combine a queue length trigger with a CPU trigger to cap scaling based on node capacity.
- Monitor KEDA internals — KEDA exposes Prometheus metrics at its operator's
/metricsendpoint. Monitor scaler errors, polling duration, and HPA synchronization to detect misconfigurations early. - Test scaling behavior under load — Use load generators to verify that your thresholds, cooldown, and max replica counts produce the desired autoscaling shape without overwhelming downstream services.
- Leverage activation scalers for zero-to-one — Some scalers support an
activation*threshold (e.g.,activationLagThresholdfor Kafka) that allows scaling from zero only when a certain minimum event count is reached, preventing constant bounce.
TriggerAuthentication and Secrets
To avoid exposing credentials in plain text, create a TriggerAuthentication resource that references Kubernetes secrets:
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: rabbitmq-auth
spec:
secretTargetRef:
- parameter: host
name: rabbitmq-credentials
key: connection-string
Then reference it in the ScaledObject trigger:
triggers:
- type: rabbitmq
authenticationRef:
name: rabbitmq-auth
metadata:
queueName: "orders"
mode: "QueueLength"
value: "10"
This keeps your ScaledObject portable and secure. The same pattern works for Kafka, Azure services, AWS, and any scaler requiring sensitive parameters.
Monitoring and Observability
KEDA's operator exposes metrics at port 8080, path /metrics. You can scrape them with Prometheus by adding a ServiceMonitor or a PodMonitor. Key metrics include:
keda_scaler_active— indicates if the scaler is active (1 or 0).keda_scaled_object_errors— total errors during metric collection.keda_operator_scaled_object_metrics_value— current metric values used for scaling.
Example Prometheus scrape configuration snippet:
- job_name: 'keda'
static_configs:
- targets: ['keda-operator.keda.svc.cluster.local:8080']
These metrics help you alert on scaler failures and visualize event-driven scaling patterns.
Cooldown and Scaling Windows
KEDA respects the cooldownPeriod (in seconds) after a scale-up before allowing scale-down. Additionally, the underlying HPA has its own stabilization windows (--horizontal-pod-autoscaler-downscale-stabilization). For precise control, you can set HPA behaviors in the ScaledObject's advanced section:
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
This allows granular tuning, but the defaults are usually sufficient for most workloads.
Conclusion
KEDA brings event-driven intelligence to Kubernetes autoscaling, bridging the gap between resource-based HPA and real-world event sources. By scaling based on queue lengths, message rates, or custom metrics, you can build truly elastic systems that right-size themselves to actual demand — even scaling down to zero when idle. The implementation pattern is consistent across scalers, making it easy to adopt for diverse event sources like Kafka, RabbitMQ, Prometheus, and cloud services.
Start by installing KEDA, wrapping your deployment with a ScaledObject, and watching it react to events. Follow best practices around secure authentication, resource limits, and cooldown tuning to ensure stable, cost-efficient production behavior. With KEDA, you unlock the full promise of event-driven architectures on Kubernetes — responsive, resource-efficient, and infinitely scalable.