Orchestration

KEDA ScaledJobs: AutoScale Batch Workloads

September 4, 2026 Kubezilla Team 16 min read
KEDA ScaledJobs: AutoScale Batch Workloads

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 Job manifest that your ScaledJob will use as a template.
  • Create a ScaledJob: Define a ScaledJob resource referencing your Job template and specifying a Trigger (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:

  1. 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.
  2. 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).

  3. Observability:
    Monitor your ScaledJobs, 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.

  4. Cost Optimization with Node Autoscaling:
    ScaledJobs will 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.

  5. Job History and Cleanup:
    The successfulJobsHistoryLimit and failedJobsHistoryLimit fields in ScaledJob control how many completed Jobs are kept. For longer retention or auditing, consider external logging solutions. The ttlSecondsAfterFinished field 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.

  6. Concurrency and Rate Limiting:
    Carefully tune maxReplicaCount to 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.

  7. 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.

  8. Testing and Performance Tuning:
    Thoroughly test your ScaledJobs under various load conditions. Monitor resource consumption (CPU, memory), Job completion times, and queue backlogs. Adjust pollingInterval, queueLength (or equivalent trigger metric), and maxReplicaCount to find the optimal balance between cost and performance.

Troubleshooting

Here are common issues you might encounter when working with KEDA ScaledJobs and their solutions:

  1. KEDA Operator Pods Not Running

    Issue: KEDA operator pods are stuck in Pending, CrashLoopBackOff, or Error state.

    Solution:

    • Check Events: Use kubectl describe pod <keda-operator-pod-name> -n keda to see if there are any scheduling issues (e.g., insufficient resources, taints/tolerations).
    • Check Logs: Use kubectl logs <keda-operator-pod-name> -n keda to 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.
  2. 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 yaml
      

      Look for the status field. It should show conditions and potentially errors if KEDA can’t connect to the trigger.

    • KEDA Operator Logs: Check the logs of the keda-operator pod (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 TriggerAuthentication resource is correct and the underlying credentials/IAM role are properly configured and have the necessary permissions to access the external service (e.g., SQS GetQueueAttributes, ReceiveMessage).
    • Queue/Topic Name/URL: Double-check the queueURL or topic name in your ScaledJob definition. A typo is a common culprit.
    • Polling Interval: Ensure pollingInterval isn’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 to 10 and you only have 5 messages, it won’t scale.
  3. Jobs Created But Stuck/Failing

    Issue: KEDA creates Jobs, but they are stuck in Pending, Error, or CrashLoopBackOff.

    Solution:

    • Check Job Status:
      kubectl get job
      kubectl describe job <job-name>
      

      Look for Conditions and Events in 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 serviceAccountName referenced by the Job has the necessary IAM permissions.
  4. 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. If queueLength: "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 but maxReplicaCount: 10, only 10 Jobs will run at a time.
    • minReplicaCount (for ScaledObjects, not ScaledJobs): While ScaledJobs generally scale from zero, for ScaledObjects (Deployments/StatefulSets), ensure minReplicaCount is set correctly.
    • Polling Interval: A very short polling interval combined with a low queueLength can lead to rapid scaling up.
  5. 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 the ScaledJob define 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’s spec (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

  1. 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 CronJob for time-based scheduling. A KEDA ScaledJob, 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.

  2. Can KEDA ScaledJobs scale based on multiple triggers?

    Yes, a ScaledJob (and ScaledObject) can define multiple triggers in its triggers array. KEDA will monitor all specified triggers and scale the Job

Leave a comment