Batch workloads are the unsung heroes of many modern applications, handling everything from data processing and analytics to report generation and machine learning inference. However, managing these ephemeral, resource-intensive tasks in Kubernetes presents a unique challenge: how do you ensure they scale efficiently without over-provisioning or starving your cluster?
Traditional Kubernetes scaling mechanisms like the Horizontal Pod Autoscaler (HPA) are fantastic for long-running services but fall short for Jobs. Jobs are designed to run to completion, not to serve continuous traffic. This is where KEDA (Kubernetes Event-driven Autoscaling) steps in, providing a powerful solution. Specifically, KEDA’s ScaledJob custom resource extends Kubernetes’ native Job API, allowing you to automatically create and scale Jobs based on external event sources, ensuring your batch processing is always right-sized for the demand.
This guide will dive deep into KEDA’s ScaledJob, demonstrating how to harness its capabilities to efficiently manage your batch processing pipelines. We’ll walk through a practical example, from setting up KEDA to defining a ScaledJob that processes messages from a queue, ensuring your cluster resources are optimized and your batch tasks are completed promptly.
TL;DR: KEDA ScaledJobs for Batch Autoscaling
KEDA’s ScaledJob resource enables event-driven autoscaling for Kubernetes Jobs, allowing you to dynamically create and scale batch workloads based on metrics from external systems (e.g., message queue length). This prevents over-provisioning and ensures timely processing.
Key Commands & Concepts:
- Install KEDA: Use Helm to deploy KEDA to your cluster.
- Define a Job: Create a standard Kubernetes
Jobmanifest that yourScaledJobwill use as a template. - Create a ScaledJob: Define a
ScaledJobresource referencing your Job template and specifying aTrigger(e.g., SQS queue, Kafka topic, Prometheus metric). - Event-driven Scaling: KEDA monitors the trigger source and, when events are present, creates instances of your Job. It scales down to zero when no events are detected.
- Example Trigger: Scale based on the number of messages in an AWS SQS queue.
# Install KEDA (if not already installed)
helm repo add kedacore https://kedacore.github.io/charts
helm repo update
helm install keda kedacore/keda --namespace keda --create-namespace
# Example Job template (my-batch-job.yaml)
# (Your actual job logic here)
# apiVersion: batch/v1
# kind: Job
# metadata:
# generateName: my-batch-processor-
# spec:
# template:
# spec:
# containers:
# - name: processor
# image: busybox
# command: ["sh", "-c", "echo 'Processing task...'; sleep 10; echo 'Task complete.'"]
# restartPolicy: Never
# backoffLimit: 4
# Example ScaledJob (my-scaled-job.yaml)
# apiVersion: keda.sh/v1alpha1
# kind: ScaledJob
# metadata:
# name: my-sqs-scaledjob
# spec:
# jobTargetRef:
# template:
# spec:
# # Use your job template here
# metadata:
# labels:
# app: my-batch-processor
# spec:
# containers:
# - name: processor
# image: busybox
# command: ["sh", "-c", "echo 'Processing SQS message...'; sleep 10; echo 'SQS task complete.'"]
# restartPolicy: Never
# pollingInterval: 30 # Check SQS every 30 seconds
# successfulJobsHistoryLimit: 5
# failedJobsHistoryLimit: 5
# maxReplicaCount: 10 # Max concurrent Jobs
# triggers:
# - type: aws-sqs
# metadata:
# queueURL: "https://sqs.us-east-1.amazonaws.com/123456789012/my-keda-queue"
# queueLength: "5" # Scale when queue has 5 or more messages
# awsRegion: "us-east-1"
# authenticationRef:
# name: keda-trigger-auth-aws # Reference to TriggerAuthentication
# Apply the ScaledJob
# kubectl apply -f my-scaled-job.yaml
# Monitor ScaledJob and created Jobs
# kubectl get scaledjob
# kubectl get job
Prerequisites
Before we embark on our KEDA ScaledJob journey, ensure you have the following:
- Kubernetes Cluster: A running Kubernetes cluster (v1.20+ recommended). This can be a local one like Minikube or Kind, or a cloud-managed service like EKS, AKS, or GKE.
kubectl: The Kubernetes command-line tool, configured to connect to your cluster. Refer to the official Kubernetes documentation for installation instructions.helm: The Kubernetes package manager, essential for installing KEDA. If you don’t have it, follow the Helm installation guide.- Basic Kubernetes Knowledge: Familiarity with Kubernetes concepts like Pods, Deployments, and Jobs is assumed.
- AWS Account (Optional but Recommended for Example): Our example will use AWS SQS as a trigger. You’ll need an AWS account with permissions to create SQS queues and an IAM role/user with SQS access.
Step-by-Step Guide: Autoscaling Batch Workloads with KEDA ScaledJobs
This guide will walk you through setting up KEDA and then deploying a ScaledJob that processes messages from an AWS SQS queue. This is a common pattern for event-driven batch processing.
Step 1: Install KEDA
First, we need to install KEDA into your Kubernetes cluster. KEDA consists of a controller and an admission webhook, which work together to manage ScaledObjects and ScaledJobs.
We’ll use Helm, the de-facto package manager for Kubernetes, to install KEDA. This is the recommended and simplest way to get KEDA up and running. We’ll create a dedicated namespace for KEDA to keep things organized.
# Add the KEDA Helm repository
helm repo add kedacore https://kedacore.github.io/charts
# Update your Helm repositories to fetch the latest chart information
helm repo update
# Install KEDA into the 'keda' namespace
# The --create-namespace flag will create the namespace if it doesn't exist
helm install keda kedacore/keda --namespace keda --create-namespace
Verify Installation
After running the Helm install command, KEDA’s components should be deployed and running. You can verify the installation by checking the Pods in the keda namespace.
kubectl get pods --namespace keda
Expected Output:
NAME READY STATUS RESTARTS AGE
keda-operator-5b7f7c8d9c-abcde 1/1 Running 0 2m
keda-operator-metrics-apiserver-7f8f9f8f-vwxyz 1/1 Running 0 2m
You should see two pods: keda-operator (the main controller) and keda-operator-metrics-apiserver (which exposes metrics to the HPA). Both should be in a Running state.
Step 2: Prepare AWS SQS Queue and IAM Permissions (for SQS Trigger)
For our example, we’ll use an AWS SQS queue as the event source. KEDA needs permissions to read metrics (queue length) from SQS. This typically involves creating an SQS queue and configuring an IAM role that KEDA can assume.
First, let’s create an SQS queue. You can do this via the AWS Console or AWS CLI. For simplicity, we’ll assume you create a standard queue named keda-scaledjob-queue in us-east-1.
# Replace 'YOUR_AWS_REGION' with your desired region, e.g., us-east-1
# Replace 'keda-scaledjob-queue' with your desired queue name
aws sqs create-queue --queue-name keda-scaledjob-queue --region us-east-1
Next, KEDA needs permissions to access this queue. The most secure way to grant this is using IAM Roles for Service Accounts (IRSA) if you’re on EKS, or by providing static credentials or environment variables if you’re on another cluster and can manage that securely. For this guide, we’ll demonstrate using IRSA, which is a best practice for AWS integrations.
You need to create an IAM policy and an IAM role, then associate it with a Kubernetes Service Account. KEDA will then use this Service Account for authentication.
IAM Policy (keda-sqs-policy.json):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:GetQueueAttributes",
"sqs:GetQueueUrl",
"sqs:ReceiveMessage"
],
"Resource": "arn:aws:sqs:us-east-1:YOUR_AWS_ACCOUNT_ID:keda-scaledjob-queue"
}
]
}
Replace YOUR_AWS_ACCOUNT_ID with your actual AWS account ID.
# Create the IAM policy
aws iam create-policy --policy-name KEDASQSReadAccess --policy-document file://keda-sqs-policy.json
# If using EKS, create an OIDC provider for your cluster if you don't have one
# eksctl utils associate-iam-oidc-provider --cluster YOUR_CLUSTER_NAME --approve
# Create a Kubernetes ServiceAccount and associate it with an IAM Role
# Replace YOUR_AWS_ACCOUNT_ID and YOUR_CLUSTER_NAME
eksctl create iamserviceaccount \
--name keda-sqs-sa \
--namespace default \
--cluster YOUR_CLUSTER_NAME \
--attach-policy-arn arn:aws:iam::YOUR_AWS_ACCOUNT_ID:policy/KEDASQSReadAccess \
--override-existing-serviceaccounts \
--approve
If you are not using EKS, you might need to provide AWS credentials as secrets or environment variables. Refer to the KEDA AWS SQS Scaler documentation for alternative authentication methods.
Step 3: Define a Kubernetes Job Template
A ScaledJob doesn’t define the Job’s Pod template directly. Instead, it acts as a wrapper around a standard Kubernetes Job. KEDA creates new instances of this Job definition whenever the trigger conditions are met. So, our first step is to define the Job that KEDA will scale.
This Job will simulate a batch processing task: it will sleep for a few seconds to represent work being done and then exit. In a real-world scenario, this Job would consume a message from the SQS queue, process it, and then delete it.
Save the following as batch-processor-job.yaml:
apiVersion: batch/v1
kind: Job
metadata:
# KEDA will generate a unique name based on the ScaledJob name,
# so 'generateName' is often preferred here.
generateName: sqs-processor-job-
labels:
app: sqs-processor
spec:
# A Job will retry up to 4 times by default if a Pod fails.
# For batch jobs, this is often desirable.
backoffLimit: 4
template:
spec:
# Use the ServiceAccount with SQS permissions
serviceAccountName: keda-sqs-sa
containers:
- name: processor
image: busybox:1.36 # A lightweight image for demonstration
command: ["sh", "-c"]
args:
- |
echo "Processing message from SQS queue..."
# In a real scenario, this is where your application logic would go.
# It would connect to SQS, retrieve a message, process it, and delete it.
# For this demo, we just simulate work.
sleep 10
echo "Message processed successfully."
restartPolicy: Never # Jobs should not restart a container if it completes successfully
# ttlSecondsAfterFinished: 300 # Optional: Clean up finished jobs after 5 minutes
Note the serviceAccountName: keda-sqs-sa. This links our Job to the Service Account we created in Step 2, ensuring it has the necessary AWS permissions.
Step 4: Define a KEDA TriggerAuthentication (for AWS SQS)
To securely connect KEDA to AWS SQS, we use a TriggerAuthentication resource. This resource defines how KEDA should authenticate with the external system. For AWS, it typically references the Service Account configured with an IAM role.
Save the following as keda-trigger-auth-aws.yaml:
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
name: keda-trigger-auth-aws
spec:
# This section tells KEDA to use the IAM role associated with the
# specified Service Account for authentication with AWS.
podIdentity:
provider: aws-eks # Use 'aws-eks' for EKS IRSA
kubectl apply -f keda-trigger-auth-aws.yaml
Verify Authentication
kubectl get triggerauthentication keda-trigger-auth-aws
Expected Output:
NAME AGE
keda-trigger-auth-aws 1m
The TriggerAuthentication resource should be created successfully.
Step 5: Create the KEDA ScaledJob
Now, let’s bring it all together by defining the ScaledJob. This resource will tell KEDA which Job template to use, what external system to monitor (SQS in our case), and the scaling parameters.
Save the following as sqs-scaledjob.yaml:
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: sqs-message-processor
spec:
# The jobTargetRef references the template for the Kubernetes Job
# that KEDA will create and manage.
jobTargetRef:
# This template is the same as our 'batch-processor-job.yaml' from Step 3.
# KEDA will use this as the blueprint for new Job instances.
template:
spec:
serviceAccountName: keda-sqs-sa
containers:
- name: processor
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
echo "Processing message from SQS queue by ScaledJob..."
sleep 10
echo "Message processed successfully by ScaledJob."
restartPolicy: Never
backoffLimit: 4
# KEDA-specific scaling parameters
pollingInterval: 10 # How often KEDA checks the SQS queue (in seconds)
successfulJobsHistoryLimit: 5 # Number of successful jobs to keep in history
failedJobsHistoryLimit: 5 # Number of failed jobs to keep in history
maxReplicaCount: 10 # Maximum number of concurrent Job instances KEDA can create
# Define the triggers that KEDA should monitor to scale the Job.
triggers:
- type: aws-sqs
metadata:
# Replace with your actual SQS queue URL
queueURL: "https://sqs.us-east-1.amazonaws.com/YOUR_AWS_ACCOUNT_ID/keda-scaledjob-queue"
# Scale when the queue length is 1 or more.
# KEDA will create one Job for every 'queueLength' messages.
queueLength: "1"
awsRegion: "us-east-1"
# Reference to the TriggerAuthentication resource defined in Step 4
authenticationRef:
name: keda-trigger-auth-aws
Important: Remember to replace YOUR_AWS_ACCOUNT_ID with your actual AWS account ID in the queueURL.
kubectl apply -f sqs-scaledjob.yaml
Verify ScaledJob Creation
Check if the ScaledJob resource has been created.
kubectl get scaledjob sqs-message-processor
Expected Output:
NAME AGE
sqs-message-processor 1m
Step 6: Test Autoscaling by Adding Messages to SQS
Now for the exciting part: seeing KEDA in action! Initially, there should be no Jobs running, as the SQS queue is empty.
kubectl get job
Expected Output (initially):
No resources found in default namespace.
Now, let’s send some messages to our SQS queue. We’ll send 5 messages using the AWS CLI.
# Replace YOUR_AWS_ACCOUNT_ID
QUEUE_URL="https://sqs.us-east-1.amazonaws.com/YOUR_AWS_ACCOUNT_ID/keda-scaledjob-queue"
# Send 5 messages
for i in $(seq 1 5); do
aws sqs send-message --queue-url $QUEUE_URL --message-body "Message $i for processing" --region us-east-1
echo "Sent message $i"
done
Within pollingInterval (10 seconds) of KEDA, you should start seeing new Jobs being created. Since our queueLength is set to 1, KEDA will try to create one Job for each message, up to maxReplicaCount.
kubectl get job -w
Expected Output (after sending messages):
NAME COMPLETIONS DURATION AGE
sqs-processor-job-abcde 0/1 1s 1s
sqs-processor-job-fghij 0/1 1s 1s
sqs-processor-job-klmno 0/1 1s 1s
sqs-processor-job-pqrst 0/1 1s 1s
sqs-processor-job-uvwxy 0/1 1s 1s
sqs-processor-job-abcde 1/1 12s 12s
sqs-processor-job-fghij 1/1 12s 12s
sqs-processor-job-klmno 1/1 12s 12s
sqs-processor-job-pqrst 1/1 12s 12s
sqs-processor-job-uvwxy 1/1 12s 12s
You’ll see multiple Jobs being created and then transitioning to 1/1 Completions, indicating success. If you keep watching, after all messages are processed and the queue is empty, KEDA will stop creating new Jobs. The existing Jobs will complete and remain in the cluster until cleaned up by successfulJobsHistoryLimit or ttlSecondsAfterFinished (if enabled).
This demonstrates the power of KEDA ScaledJobs: your batch workloads scale automatically based on demand, ensuring efficient resource utilization and timely processing.
Production Considerations
Deploying ScaledJobs in a production environment requires careful planning and consideration beyond the basic setup. Here are key aspects to keep in mind:
-
Robust Job Logic:
Your batch Job container should be designed for resilience. This includes:- Idempotency: Ensure your Job can be safely retried without adverse side effects. Message queues often support “at-least-once” delivery, meaning a message might be processed multiple times.
- Error Handling: Implement robust error handling within your Job, logging failures, and potentially moving problematic messages to a Dead Letter Queue (DLQ) for later inspection.
- Resource Limits: Define appropriate CPU and memory requests and limits for your Job Pods. This prevents resource exhaustion and ensures fair scheduling.
-
Authentication and Authorization:
As demonstrated with IRSA, securely authenticating your Jobs with external systems (databases, message queues, cloud APIs) is paramount. Avoid hardcoding credentials. Utilize Kubernetes Secrets, Service Accounts, and cloud provider identity solutions (like IRSA for AWS, Workload Identity for GCP, Managed Identities for Azure). -
Observability:
Monitor yourScaledJobs, the underlying Kubernetes Jobs, and the external event source (e.g., SQS queue length, Kafka lag). Integrate with your existing observability stack (Prometheus, Grafana, ELK, etc.). KEDA exposes its own metrics which can be scraped by Prometheus. For deeper insights into Kubernetes networking and eBPF capabilities, consider tools like eBPF Observability with Hubble. -
Cost Optimization with Node Autoscaling:
ScaledJobswill request new Pods, but if your cluster doesn’t have enough capacity, these Pods will remain pending. Integrate with a cluster autoscaler like Kubernetes Cluster Autoscaler or Karpenter Cost Optimization to dynamically provision nodes when new Jobs are created and scale them down when Jobs complete. This is crucial for optimizing costs, especially with bursty batch workloads. -
Job History and Cleanup:
ThesuccessfulJobsHistoryLimitandfailedJobsHistoryLimitfields inScaledJobcontrol how many completed Jobs are kept. For longer retention or auditing, consider external logging solutions. ThettlSecondsAfterFinishedfield on the Job template itself (not the ScaledJob) can automatically clean up completed Jobs after a specified duration, preventing your cluster from accumulating thousands of finished Job resources. -
Concurrency and Rate Limiting:
Carefully tunemaxReplicaCountto prevent your batch Jobs from overwhelming downstream systems or exhausting cluster resources. Consider the capacity of your message queue, database, or API endpoints your Jobs interact with. For advanced traffic management and rate limiting for services, explore solutions like Istio Ambient Mesh or the Kubernetes Gateway API, although these are more for long-running services than ephemeral Jobs. -
Network Policies:
Apply Kubernetes Network Policies to restrict network access for your Job Pods. This ensures they can only communicate with necessary services (e.g., SQS, database) and are isolated from other parts of your cluster, enhancing security. For advanced network encryption, you might even consider Cilium WireGuard Encryption. -
Testing and Performance Tuning:
Thoroughly test yourScaledJobsunder various load conditions. Monitor resource consumption (CPU, memory), Job completion times, and queue backlogs. AdjustpollingInterval,queueLength(or equivalent trigger metric), andmaxReplicaCountto find the optimal balance between cost and performance.
Troubleshooting
Here are common issues you might encounter when working with KEDA ScaledJobs and their solutions:
-
KEDA Operator Pods Not Running
Issue: KEDA operator pods are stuck in
Pending,CrashLoopBackOff, orErrorstate.Solution:
- Check Events: Use
kubectl describe pod <keda-operator-pod-name> -n kedato see if there are any scheduling issues (e.g., insufficient resources, taints/tolerations). - Check Logs: Use
kubectl logs <keda-operator-pod-name> -n kedato examine the operator’s logs for errors. - Resource Limits: Ensure your cluster has enough resources for KEDA. Sometimes, default installations might be too tight for very small clusters.
- Helm Installation: Re-run the Helm install command, ensuring no errors occurred.
- Check Events: Use
-
ScaledJob Not Creating Jobs
Issue: You’ve sent messages to your queue (or triggered your external source), but no Kubernetes Jobs are being created.
Solution:
- Check ScaledJob Status:
kubectl get scaledjob sqs-message-processor -o yamlLook for the
statusfield. It should showconditionsand potentially errors if KEDA can’t connect to the trigger. - KEDA Operator Logs: Check the logs of the
keda-operatorpod (kubectl logs -f -n keda <keda-operator-pod-name>). It will often show errors related to connecting to the external trigger (e.g., authentication failures, invalid queue URL). - Trigger Authentication: Verify your
TriggerAuthenticationresource is correct and the underlying credentials/IAM role are properly configured and have the necessary permissions to access the external service (e.g., SQSGetQueueAttributes,ReceiveMessage). - Queue/Topic Name/URL: Double-check the
queueURLor topic name in yourScaledJobdefinition. A typo is a common culprit. - Polling Interval: Ensure
pollingIntervalisn’t too high. KEDA only checks the trigger source at this interval. - Metric Threshold: Verify the
queueLength(or equivalent metric threshold) is set correctly. If it’s set to10and you only have5messages, it won’t scale.
- Check ScaledJob Status:
-
Jobs Created But Stuck/Failing
Issue: KEDA creates Jobs, but they are stuck in
Pending,Error, orCrashLoopBackOff.Solution:
- Check Job Status:
kubectl get job kubectl describe job <job-name>Look for
ConditionsandEventsin the Job description. - Check Pod Status: Get the Pod created by the Job:
kubectl get pods --selector=job-name=<job-name> kubectl describe pod <pod-name> kubectl logs <pod-name>The Pod’s events and logs are crucial for diagnosing issues like image pull errors, command failures, or application-level errors.
- Resource Limits: If Pods are
Pending, it might be due to insufficient CPU/memory resources in your cluster, or the requested resources exceed node capacity. - Application Errors: The most common cause is an error within your batch processing application itself (e.g., unable to connect to a database, parsing error, invalid logic). Debug your application logic as you would any other service.
- Service Account Permissions: If your Job needs to interact with external services (like SQS to delete messages), ensure the
serviceAccountNamereferenced by the Job has the necessary IAM permissions.
- Check Job Status:
-
Too Many/Too Few Jobs Created
Issue: KEDA is creating more or fewer Jobs than expected.
Solution:
queueLength/metricThreshold: This parameter defines how many messages/events correspond to one Job instance. IfqueueLength: "5", KEDA tries to create one Job for every 5 messages. Adjust this value to control concurrency.maxReplicaCount: This sets the upper limit on how many concurrent Jobs KEDA can create. If you have 100 messages butmaxReplicaCount: 10, only 10 Jobs will run at a time.minReplicaCount(for ScaledObjects, not ScaledJobs): WhileScaledJobsgenerally scale from zero, forScaledObjects(Deployments/StatefulSets), ensureminReplicaCountis set correctly.- Polling Interval: A very short polling interval combined with a low
queueLengthcan lead to rapid scaling up.
-
Completed Jobs Not Cleaning Up
Issue: Your cluster is accumulating many completed Job resources, even after they’ve finished.
Solution:
successfulJobsHistoryLimit/failedJobsHistoryLimit: These fields in theScaledJobdefine how many completed (successful or failed) Job objects KEDA should retain. Reduce these values to keep fewer historical Jobs.ttlSecondsAfterFinished: Add this field to the Job template’sspec(not the ScaledJob) to automatically delete Jobs after a specified time once they finish.# Inside your Job template spec: spec: ttlSecondsAfterFinished: 300 # Deletes Job 5 minutes after completion template: # ...
FAQ Section
-
What is the difference between KEDA ScaledJob and a regular Kubernetes Job?
A regular Kubernetes Job runs a Pod to completion, and you manually trigger it or use a
CronJobfor time-based scheduling. A KEDAScaledJob, on the other hand, *automatically* creates and manages instances of a Kubernetes Job based on metrics from an external event source (like a message queue length, database changes, or custom metrics). It provides event-driven autoscaling for batch workloads, scaling from zero to N Jobs and back to zero. -
Can KEDA ScaledJobs scale based on multiple triggers?
Yes, a
ScaledJob(andScaledObject) can define multiple triggers in itstriggersarray. KEDA will monitor all specified triggers and scale the Job
