Event-Driven Autoscaling with KEDA on Kubernetes: Scale Pods on RabbitMQ, Kafka, and AWS SQS

DevOps tutorial - IT technology blog
DevOps tutorial - IT technology blog

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.

Share: