Quick Setup: Deploy KEDA in 5 Minutes
The native Kubernetes Horizontal Pod Autoscaler (HPA) works well for HTTP services bound by CPU or memory. Asynchronous queue workers tell a completely different story. A worker thread can sit at 12% CPU while 50,000 video encoding jobs pile up. Conversely, a single compute-heavy task might peg CPU at 95%, triggering an unwanted scale-out even though the queue is empty.
Kubernetes Event-driven Autoscaling (KEDA) bridges this gap. It operates as both a custom metrics server and a lifecycle controller. KEDA monitors external queues directly, spins up pods when work arrives, and scales deployments down to zero when queues sit idle—saving infrastructure costs on idle worker nodes.
1. Install KEDA via Helm
Install KEDA on any Kubernetes cluster (v1.27+) using the official Helm chart:
# Add and update the KEDA Helm repository
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
# Deploy KEDA into its own namespace
helm install keda kedacore/keda \
--namespace keda \
--create-namespace \
--set metricsServer.dnsPolicy=ClusterFirstWithHostNet
2. Verify Installation
Check that both operator pods and metrics API server pods are up and running:
kubectl get pods -n keda
# Expected output:
# NAME READY STATUS RESTARTS AGE
# keda-operator-7b6494cb68-x28qf 1/1 Running 0 45s
# keda-operator-metrics-apiserver-585474775d-n4vwx 1/1 Running 0 45s
Core Concepts and Production Scalers
KEDA relies on two primary Custom Resource Definitions (CRDs):
- ScaledObject: Maps an event source (like RabbitMQ or SQS) to your Deployment or StatefulSet, defining min/max replica boundaries and target thresholds.
- TriggerAuthentication: Houses authentication credentials, connection strings, or cloud IAM roles separately from deployment manifests.
Zero-to-one scaling is handled directly by KEDA. Once the first pod is alive, KEDA hands metrics over to the standard Kubernetes HPA to manage scale-out from 1 to N replicas.
1. Scaling on RabbitMQ Queue Depth
Consider an image resizing service processing jobs from a RabbitMQ queue called image-tasks. The goal: allocate one Pod for every 30 backlog messages, capping growth at 20 replicas.
apiVersion: v1
kind: Secret
metadata:
name: rabbitmq-credentials
namespace: default
type: Opaque
data:
# Base64 string for: amqp://user:[email protected]:5672/vhost
host: YW1xcDovL3VzZXI6cGFzc3dvcmRAcmFiYml0bXEuZGVmYXVsdC5zdmMuY2x1c3Rlci5sb2NhbDo1NjcyL3Zob3N0
---
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: rabbitmq-trigger-auth
namespace: default
spec:
secretTargetRef:
- parameter: host
name: rabbitmq-credentials
key: host
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: image-processor-scaler
namespace: default
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: image-processor
minReplicaCount: 0
maxReplicaCount: 20
pollingInterval: 15
cooldownPeriod: 60
triggers:
- type: rabbitmq
metadata:
queueName: image-tasks
mode: QueueLength
value: "30"
authenticationRef:
name: rabbitmq-trigger-auth
2. Scaling on Apache Kafka Consumer Group Lag
Kafka scaling targets consumer group lag rather than raw message count. KEDA queries broker partition offsets and calculates total uncommitted lag across active consumers.
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: telemetry-consumer-scaler
namespace: default
spec:
scaleTargetRef:
name: telemetry-consumer
minReplicaCount: 1
maxReplicaCount: 16
pollingInterval: 10
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-cluster-kafka-bootstrap.kafka.svc:9092
consumerGroup: telemetry-workers
topic: device-telemetry
lagThreshold: "100"
offsetResetPolicy: latest
3. Scaling on AWS SQS with IAM Roles (IRSA)
Avoid static AWS access keys in Kubernetes secrets. When running on Amazon EKS, authenticate KEDA using IAM Roles for Service Accounts (IRSA) or EKS Pod Identity.
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: keda-aws-sqs-auth
namespace: default
spec:
podIdentity:
provider: aws-eks
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: order-processor-scaler
namespace: default
spec:
scaleTargetRef:
name: order-processor
minReplicaCount: 0
maxReplicaCount: 50
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/order-processing-queue
queueLength: "10"
awsRegion: us-east-1
authenticationRef:
name: keda-aws-sqs-auth
Fine-Tuning Autoscaling Behavior
1. Preventing Pod Flapping
Spiky message streams trigger aggressive autoscaling loops. Pods spin up, drain the queue in seconds, terminate immediately, and restart when the next batch lands. Stabilize scale-down operations by tuning HPA behavior directly inside the ScaledObject:
spec:
advanced:
horizontalPodAutoscalerConfig:
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
2. Combining Queue Triggers with CPU Fallbacks
KEDA supports composite triggers on a single workload. When defining multiple metrics, the controller calculates targets for each trigger independently and picks whichever produces the highest replica count:
spec:
triggers:
- type: rabbitmq
metadata:
queueName: high-priority-jobs
mode: QueueLength
value: "10"
authenticationRef:
name: rabbitmq-trigger-auth
- type: cpu
metadata:
type: Utilization
value: "75"
Production Gotchas to Watch For
Running event-driven workloads at scale reveals several operational traps. Keep these three rules in mind before going live:
1. Align Kafka Partitions with Max Replicas
A Kafka partition can only be read by one consumer in a consumer group. If your topic has 12 partitions and you configure maxReplicaCount: 30, 18 pods will consume CPU and memory while doing zero work. Cap maxReplicaCount to match partition capacity.
2. Protect In-Flight Jobs During Scale-Down
Kubernetes issues a SIGTERM signal when terminating replicas. If a worker is 40 seconds into a 60-second batch job, an immediate kill causes dropped connections or duplicate processing.
Extend terminationGracePeriodSeconds in your Pod spec and implement clean signal interception in your worker application:
spec:
template:
spec:
terminationGracePeriodSeconds: 120
containers:
- name: worker
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "touch /tmp/draining && sleep 5"]
3. Monitor Broker Polling Overhead
KEDA periodically polls queue endpoints (default: 30 seconds). Deploying 200 ScaledObject resources with a 2-second polling interval creates 100 HTTP or AMQP management queries per second against your broker. Stick with a 15–30 second interval unless strict sub-second queue response is required.

