Amazon EKS — KEDA
KEDA (Kubernetes Event-Driven Autoscaling) extends HPA with event-driven triggers. It can scale pods based on external metrics like SQS queue depth, Kinesis stream lag, CloudWatch metrics, and dozens of other sources — including scaling to zero.
Why KEDA?
| Feature | HPA | KEDA |
|---|---|---|
| Metric sources | CPU, memory, custom metrics API | 60+ external scalers |
| Scale to zero | No (minimum 1 replica) | Yes |
| AWS service integration | Manual (Prometheus Adapter) | Native SQS, Kinesis, CloudWatch scalers |
| Job scaling | No | Yes (ScaledJob) |
Installation
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda \
--namespace keda --create-namespace \
--set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::123456789:role/KedaRole
IRSA for KEDA
cat > keda-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl",
"cloudwatch:GetMetricData",
"kinesis:DescribeStreamSummary"
],
"Resource": "*"
}
]
}
EOF
aws iam create-policy --policy-name KedaPolicy --policy-document file://keda-policy.json
eksctl create iamserviceaccount \
--cluster my-cluster \
--namespace keda \
--name keda-operator \
--attach-policy-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/KedaPolicy \
--approve
ScaledObject — SQS Queue
Scale a deployment based on SQS queue depth:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: sqs-consumer
namespace: production
spec:
scaleTargetRef:
name: queue-processor
pollingInterval: 15
cooldownPeriod: 60
minReplicaCount: 0 # Scale to zero!
maxReplicaCount: 50
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789/my-queue
queueLength: "5" # Messages per replica
awsRegion: us-east-1
authenticationRef:
name: keda-aws-credentials
ScaledObject — CloudWatch Metric
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: cloudwatch-scaler
namespace: production
spec:
scaleTargetRef:
name: api-server
minReplicaCount: 2
maxReplicaCount: 30
triggers:
- type: aws-cloudwatch
metadata:
namespace: AWS/ApplicationELB
dimensionName: LoadBalancer
dimensionValue: app/my-alb/1234567890
metricName: RequestCountPerTarget
targetMetricValue: "100"
minMetricValue: "0"
awsRegion: us-east-1
metricStatPeriod: "60"
metricStat: Average
ScaledJob — Batch Processing
For one-off jobs triggered by events:
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: image-processor
namespace: production
spec:
jobTargetRef:
template:
spec:
containers:
- name: processor
image: myapp/image-processor:latest
env:
- name: QUEUE_URL
value: https://sqs.us-east-1.amazonaws.com/123456789/image-queue
restartPolicy: Never
backoffLimit: 3
pollingInterval: 10
maxReplicaCount: 20
triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789/image-queue
queueLength: "1"
awsRegion: us-east-1
Authentication
TriggerAuthentication with IRSA
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: keda-aws-credentials
namespace: production
spec:
podIdentity:
provider: aws-eks
KEDA vs HPA
| Feature | HPA | KEDA |
|---|---|---|
| Built-in | Yes | Add-on |
| Scale to zero | No | Yes |
| SQS trigger | No (needs adapter) | Native |
| CloudWatch trigger | No (needs adapter) | Native |
| Cron-based scaling | No | Yes |
| Job scaling | No | ScaledJob |
| Custom metrics | Prometheus Adapter | 60+ built-in scalers |
KEDA creates HPA resources under the hood — it extends HPA, it doesn’t replace it.
Popular AWS Scalers
| Scaler | Trigger | Use Case |
|---|---|---|
aws-sqs-queue |
Queue depth | Message processing |
aws-cloudwatch |
Any CloudWatch metric | Custom business metrics |
aws-kinesis-stream |
Shard iterator lag | Stream processing |
aws-dynamodb-streams |
Stream records | Event processing |
cron |
Time-based | Scheduled scaling |
prometheus |
PromQL query | Custom app metrics |
Best Practices
- Use KEDA for event-driven workloads — don’t force HPA to do SQS-based scaling
- Scale to zero for batch processors — no cost when queue is empty
- Set appropriate
queueLength— messages per replica determines scaling speed - Use
cooldownPeriodto prevent rapid scale-down after burst - Use ScaledJob for one-shot tasks — ScaledObject for long-running services
- Use IRSA/Pod Identity for KEDA operator — scope to only the metrics it needs