Skip to content

DistributedScaledJob API reference

Job distribution requires member access and the KEDA raw-metrics path. Failover/duplication and retry behavior affect delivery semantics; design idempotent work and inspect member status before resubmitting jobs.

Group: keda.kedify.io. Kind: DistributedScaledJob. Scope: Namespaced.

Defaults below are schema defaults only. “Not specified” does not imply a controller default of zero or false. Required fields are required within their containing object. Runtime behavior can impose additional conditions described in the capability guide.

Start with distribution strategies, pod override merge rules and failover. The tables retain Kedify fields and link inherited Kubernetes structures upstream.

Served: true; storage: true.

FieldTypeRequiredSchema defaultMeaning and validation
specobjectNoNot specifiedDistributedScaledJobSpec defines the desired state of DistributedScaledJob x-kubernetes-validations: [{“message”:“memberClusters must be non-empty when clusterScheduling.strategy=priorityFailover”,“rule”:“self.clusterScheduling.strategy != ‘priorityFailover’ || (has(self.memberClusters) && self.memberClusters.size() > 0)”},{“message”:“memberClusters[].scheduling.priority is required when clusterScheduling.strategy=priorityFailover”,“rule”:“self.clusterScheduling.strategy != ‘priorityFailover’ || (has(self.memberClusters) && self.memberClusters.all(c, has(c.scheduling) && has(c.scheduling.priority)))”},{“message”:“scaledJobSpec.scalingStrategy.strategy must be pendingAware (or omitted) when clusterScheduling.strategy=priorityFailover”,“rule”:“self.clusterScheduling.strategy != ‘priorityFailover’ || self.scaledJobSpec.scalingStrategy.strategy == ” || self.scaledJobSpec.scalingStrategy.strategy == ‘pendingAware’”}]
spec.clusterSchedulingobjectNo{"strategy":"weightedRoundRobin"}ClusterScheduling controls member-cluster selection strategy for DSJ. x-kubernetes-validations: [{“message”:“clusterScheduling.workloadAffinity is required when clusterScheduling.strategy=priorityFailover”,“rule”:“self.strategy != ‘priorityFailover’ || has(self.workloadAffinity)”},{“message”:“clusterScheduling.workloadAffinity must be omitted when clusterScheduling.strategy=weightedRoundRobin”,“rule”:“self.strategy == ‘priorityFailover’ || !has(self.workloadAffinity)”}]
spec.clusterScheduling.failoverPolicyobjectNo{"gracePeriod":"1m","hardTaintDuration":"5m","softTaintDuration":"3m"}FailoverPolicy controls how the controller should behave when clusters fail
spec.clusterScheduling.failoverPolicy.duplicationPolicystringNo"keepAll"DuplicationPolicy controls how duplicate jobs are resolved after pending-aware failover. - keepAll: keep all source and failover cluster jobs, results in temporarily exceeded maxReplicaCount during failover but provides better availability (default) - preferFailover: prefer failover cluster job over source cluster job, delete source cluster job only when failover cluster job is running, best effort avoids maxReplicaCount exceed - preferSource: prefer source cluster job over failover cluster job, delete failover cluster job immediately, can cause running job preemption when source cluster recovers during failover, best effort avoids maxReplicaCount exceed - immediateSourceCleanup: clean up source cluster job right after failover enum: [“immediateSourceCleanup”,“keepAll”,“preferFailover”,“preferSource”]
spec.clusterScheduling.failoverPolicy.gracePeriodstringNo"1m"GracePeriod is the duration to wait before re-creating Jobs that fail to progress from Pending to Running state
spec.clusterScheduling.failoverPolicy.hardTaintDurationstringNo"5m"HardTaintDuration is the duration to taint a failing cluster before attempting to scale Jobs on it again
spec.clusterScheduling.failoverPolicy.softTaintDurationstringNo"3m"SoftTaintDuration is the time window to wait for a second failure before applying a hard taint. Set to 0s to disable soft taint and hard-taint on the first failure.
spec.clusterScheduling.strategystringNo"weightedRoundRobin"Strategy defines how member clusters are selected when creating Jobs. enum: [“weightedRoundRobin”,“priorityFailover”]
spec.clusterScheduling.workloadAffinityobjectNoNot specifiedWorkloadAffinity classifies workloads for failover taint matching. Required when strategy=priorityFailover.
spec.clusterScheduling.workloadAffinity.classstringNoNot specifiedClass groups workloads for failover taint matching (e.g., for org/team).
spec.clusterScheduling.workloadAffinity.sizeintegerYesNot specifiedSize is the monotonic affinity class value. minimum: 0
spec.memberClustersarrayNoNot specifiedMemberClusters is the list of member clusters where the ScaledJob should trigger scaling of the target workloads if empty, use all available member clusters maxItems: 64; x-kubernetes-validations: [{“message”:“memberClusters.name must be non-empty”,“rule”:“self.all(c, c.name != ”)”},{“message”:“memberClusters.name values must be unique”,“rule”:“self.map(c, c.name).all(n, self.map(c, c.name).filter(x, x == n).size() == 1)”}]
spec.memberClusters[]objectNoNot specifiedDistributedScaledJobMemberCluster represents a member cluster where the ScaledJob should be applied.
spec.memberClusters[].namestringYesNot specifiedName is the name of the member cluster.
spec.memberClusters[].podOverridesobjectNoNot specifiedPodOverrides are applied to Job pod template for this cluster.
spec.memberClusters[].podOverrides.affinityobjectNoNot specifiedSee Kubernetes field definitions.
spec.memberClusters[].podOverrides.containerOverridesobjectNoNot specifiedContainerOverrides are keyed by container name.
spec.memberClusters[].podOverrides.containerOverrides.*objectNoNot specifiedDistributedScaledJobContainerOverrides defines container-level overrides for a single member cluster.
spec.memberClusters[].podOverrides.containerOverrides.*.envarrayNoNot specifiedEnv overrides container environment variables.
spec.memberClusters[].podOverrides.containerOverrides.*.env[]objectNoNot specifiedEnvVar represents an environment variable present in a Container.
spec.memberClusters[].podOverrides.containerOverrides.*.env[].namestringYesNot specifiedName of the environment variable. May consist of any printable ASCII characters except ’=’.
spec.memberClusters[].podOverrides.containerOverrides.*.env[].valuestringNoNot specifiedVariable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. ”$$(VAR_NAME)” will produce the string literal ”$(VAR_NAME)”. Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "".
spec.memberClusters[].podOverrides.containerOverrides.*.env[].valueFromobjectNoNot specifiedSee Kubernetes field definitions.
spec.memberClusters[].podOverrides.containerOverrides.*.imagestringNoNot specifiedImage overrides container image for this cluster.
spec.memberClusters[].podOverrides.containerOverrides.*.resourcesobjectNoNot specifiedResources partially overrides container resource requirements.
spec.memberClusters[].podOverrides.containerOverrides.*.resources.claimsarrayNoNot specifiedClaims lists the names of resources, defined in spec.resourceClaims, that are used by this container. This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers.
spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[]objectNoNot specifiedResourceClaim references one entry in PodSpec.ResourceClaims.
spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[].namestringYesNot specifiedName must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.
spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[].requeststringNoNot specifiedRequest is the name chosen for a request in the referenced claim. If empty, everything from the claim is made available, otherwise only the result of this request.
spec.memberClusters[].podOverrides.containerOverrides.*.resources.limitsobjectNoNot specifiedLimits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
spec.memberClusters[].podOverrides.containerOverrides.*.resources.limits.*integer or stringNoNot specifiedpattern: ”^(\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))))?$”
spec.memberClusters[].podOverrides.containerOverrides.*.resources.requestsobjectNoNot specifiedRequests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
spec.memberClusters[].podOverrides.containerOverrides.*.resources.requests.*integer or stringNoNot specifiedpattern: ”^(\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))))?$”
spec.memberClusters[].podOverrides.nodeSelectorobjectNoNot specifiedNodeSelector overrides pod nodeSelector.
spec.memberClusters[].podOverrides.nodeSelector.*stringNoNot specified
spec.memberClusters[].podOverrides.tolerationsarrayNoNot specifiedSee Kubernetes field definitions.
spec.memberClusters[].schedulingobjectNoNot specifiedScheduling contains cluster-selection attributes for this member cluster.
spec.memberClusters[].scheduling.priorityintegerNoNot specifiedPriority is used by clusterScheduling.strategy=priorityFailover. Higher value means stronger preference. minimum: 0; maximum: 1000
spec.memberClusters[].targetNamespacestringNoNot specifiedTargetNamespace is the namespace in the member cluster where the scaling should be applied. Defaults to the namespace of the owning distributed resource if not specified.
spec.memberClusters[].weightintegerYes1Weight is the weight of the member cluster for scaling distribution.
spec.scaledJobSpecobjectYesNot specifiedScaledJobSpec is the specification of the KEDA ScaledJob
spec.scaledJobSpec.failedJobsHistoryLimitintegerNoNot specified
spec.scaledJobSpec.jobTargetRefobjectYesNot specifiedSee Kubernetes field definitions.
spec.scaledJobSpec.maxReplicaCountintegerNoNot specified
spec.scaledJobSpec.minReplicaCountintegerNoNot specified
spec.scaledJobSpec.pollingIntervalintegerNoNot specified
spec.scaledJobSpec.rolloutobjectNoNot specifiedRollout defines the strategy for job rollouts
spec.scaledJobSpec.rollout.propagationPolicystringNoNot specified
spec.scaledJobSpec.rollout.strategystringNoNot specified
spec.scaledJobSpec.scalingStrategyobjectNoNot specifiedScalingStrategy defines the strategy of Scaling
spec.scaledJobSpec.scalingStrategy.customScalingQueueLengthDeductionintegerNoNot specified
spec.scaledJobSpec.scalingStrategy.customScalingRunningJobPercentagestringNoNot specified
spec.scaledJobSpec.scalingStrategy.multipleScalersCalculationstringNoNot specified
spec.scaledJobSpec.scalingStrategy.pendingPodConditionsarrayNoNot specified
spec.scaledJobSpec.scalingStrategy.pendingPodConditions[]stringNoNot specified
spec.scaledJobSpec.scalingStrategy.strategystringNoNot specifiedenum: [“basic”,“custom”,“accurate”,“eager”,“pendingAware”]
spec.scaledJobSpec.successfulJobsHistoryLimitintegerNoNot specified
spec.scaledJobSpec.triggersarrayYesNot specifiedmaxItems: 64; x-kubernetes-validations: [{“message”:“triggers.name must be non-empty”,“rule”:“self.all(t, t.name != ”)”},{“message”:“triggers.name values must be unique”,“rule”:“self.map(t, t.name).all(n, self.map(t, t.name).filter(x, x == n).size() == 1)”}]
spec.scaledJobSpec.triggers[]objectNoNot specifiedScaleTriggers reference the scaler that will be used
spec.scaledJobSpec.triggers[].authenticationRefobjectNoNot specifiedAuthenticationRef points to the TriggerAuthentication or ClusterTriggerAuthentication object that is used to authenticate the scaler with the environment
spec.scaledJobSpec.triggers[].authenticationRef.kindstringNoNot specifiedKind of the resource being referred to. Defaults to TriggerAuthentication. enum: [“TriggerAuthentication”,“ClusterTriggerAuthentication”]
spec.scaledJobSpec.triggers[].authenticationRef.namestringYesNot specified
spec.scaledJobSpec.triggers[].metadataobjectYesNot specified
spec.scaledJobSpec.triggers[].metadata.*stringNoNot specified
spec.scaledJobSpec.triggers[].metricTypestringNoNot specifiedMetricTargetType specifies the type of metric being targeted, and should be either “Value”, “AverageValue”, or “Utilization”
spec.scaledJobSpec.triggers[].namestringNoNot specified
spec.scaledJobSpec.triggers[].typestringYesNot specifiedminLength: 1
spec.scaledJobSpec.triggers[].useCachedMetricsbooleanNoNot specified
spec.totalTargetReplicasintegerNo0TotalTargetReplicas is the desired number of replicas
FieldTypeRequiredSchema defaultMeaning and validation
statusobjectNoNot specifiedDistributedScaledJobStatus defines the observed state of DistributedScaledJob
status.conditionsarrayNoNot specified
status.conditions[]objectNoNot specifiedCondition contains details for one aspect of the current state of this API Resource.
status.conditions[].lastTransitionTimestringYesNot specifiedlastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
status.conditions[].messagestringYesNot specifiedmessage is a human readable message indicating details about the transition. This may be an empty string. maxLength: 32768
status.conditions[].observedGenerationintegerNoNot specifiedobservedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. minimum: 0
status.conditions[].reasonstringYesNot specifiedreason contains a programmatic identifier indicating the reason for the condition’s last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. minLength: 1; maxLength: 1024; pattern: ”^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$”
status.conditions[].statusstringYesNot specifiedstatus of the condition, one of True, False, Unknown. enum: [“True”,“False”,“Unknown”]
status.conditions[].typestringYesNot specifiedtype of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316; pattern: ”^([a-z0-9]([-a-z0-9][a-z0-9])?(\.[a-z0-9]([-a-z0-9][a-z0-9])?)/)?(([A-Za-z0-9][-A-Za-z0-9_.])?[A-Za-z0-9])$”
status.desiredJobsintegerYesNot specifiedDesiredJobs is the desired number of jobs calculated from metrics
status.memberClusterStatusesobjectNoNot specifiedMemberClusterStatuses represents the status of each member cluster
status.memberClusterStatuses.*objectNoNot specifiedDistributedScaledJobMemberClusterStatus represents the status of a member cluster
status.memberClusterStatuses.*.descriptionstringYesNot specifiedDescription provides additional information about the member cluster status
status.memberClusterStatuses.*.excludedbooleanNoNot specifiedExcluded indicates whether the member cluster is excluded from scaling for example, due to being unhealthy for longer than the grace period
status.memberClusterStatuses.*.hardTaintScopesarrayNoNot specifiedHardTaintScopes lists hard-taint scopes for priorityFailover scheduling. Multiple entries may exist for a single cluster (for different classes/priorities).
status.memberClusterStatuses.*.hardTaintScopes[]objectNoNot specifiedDistributedScaledJobHardTaintScope identifies one priorityFailover hard-taint scope.
status.memberClusterStatuses.*.hardTaintScopes[].classstringYesNot specifiedClass is the workload affinity class for which the cluster is tainted. This can be empty to represent the default affinity class.
status.memberClusterStatuses.*.hardTaintScopes[].priorityintegerNoNot specifiedPriority is the member-cluster scheduling priority for which the cluster is tainted.
status.memberClusterStatuses.*.hardTaintedbooleanNoNot specifiedHardTainted indicates the cluster is hard tainted for at least one DSJ scheduling path.
status.memberClusterStatuses.*.idstringYesNot specifiedID is the identification of the member cluster for multicluster-controller
status.memberClusterStatuses.*.lastScalingProgressTimestringNoNot specifiedLastScalingProgressTime is the last time the scaling made progress This is used to determine how long the member cluster has been making slow progress for rebalancing purposes
status.memberClusterStatuses.*.lastStatusChangeTimestringNoNot specifiedLastStatusChangeTime is the last time the status changed This is used to determine how long the member cluster has been unhealthy for rebalancing purposes
status.memberClusterStatuses.*.pendingJobsintegerYesNot specifiedPendingJobs is the number of jobs that are pending in the member cluster
status.memberClusterStatuses.*.providerstringNoNot specifiedProvider is the cluster provider type (for example file, kubeconfig)
status.memberClusterStatuses.*.runningJobsintegerYesNot specifiedRunningJobs is the current number of running jobs in the member cluster
status.memberClusterStatuses.*.softTaintedbooleanNoNot specifiedSoftTainted indicates the cluster has a recent failure but is not hard tainted
status.memberClusterStatuses.*.statestringYesNot specifiedState is the state of the member cluster (e.g., Ready, NotReady)
status.memberClusterStatuses.*.stuckJobsintegerYesNot specifiedStuckJobs is the number of jobs that are stuck (e.g., pending for too long) in the member cluster
status.pendingJobsintegerYesNot specifiedPendingJobs is the current number of pending jobs across all member clusters
status.printobjectNoNot specifiedPrint is used for custom printing of DistributedScaledJob status
status.print.membersCountstringNoNot specifiedMembersCount is the status of member clusters
status.print.runningJobsstringNoNot specifiedRunningJobs represents the status of running jobs across member clusters
status.runningJobsintegerYesNot specifiedRunningJobs is the current number of running jobs across all member clusters
status.scalersTargetAverageValueinteger or stringNoNot specifiedScalersTargetAverageValue is the target average value across all scalers pattern: ”^(\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))))?$”
status.selectorstringYesNot specifiedSelector is the selector for the distributed jobs
status.triggerMetricsStatusobjectNoNot specifiedTriggerMetricStatus represents the status of each metric used for scaling by trigger name
status.triggerMetricsStatus.*objectNoNot specifiedTriggerMetricStatus represents the status of a single metric used for scaling by trigger name
status.triggerMetricsStatus.*.infostringNoNot specifiedInfo provides additional information about the metric status, such as error messages or reasons for inactivity
status.triggerMetricsStatus.*.lastChangeTimestringNoNot specifiedLastChangeTime is the last time the metric status changed, used for tracking how long a metric has been in a particular state
status.triggerMetricsStatus.*.statestringYesNot specifiedState represents the state of the metric (“Active”, “Inactive”, “Error”) used to determine if scaling should occur
Terminal window
kubectl get crd distributedscaledjobs.keda.kedify.io -o yaml
kubectl explain distributedscaledjob.spec --recursive

Compare served/storage versions and schema before applying configuration. Inspect the resource’s status and events after changing its owning source; schema acceptance alone does not prove successful reconciliation.

Scaling strategies are used to compute how many new Jobs to create across clusters. Choose one of: basic, pendingAware, custom, accurate, eager.

Inputs:

  • desiredJobsCount: target number derived from metrics and DSJ min/max bounds
  • runningJobsCount: number of non-terminal Jobs currently present (includes “pending”)
  • pendingJobsCount: subset of running Jobs considered “pending” (not yet progressed)
  • maxReplicaCount: DSJ upper bound on total concurrent non-terminal Jobs

Scale to the gap between desired and running.

  • Formula: desired - running
  • Behavior: simple “catch up” to desired.

Immediately re-create pending Jobs on other clusters while honoring capacity.

  • Idea: replace stuck Jobs.
  • Formula:
    • needed = max(0, desired - running + pending)
    • capacity = max(0, maxReplica - running)
    • scaleTo = min(needed, capacity)
  • Use when pending/stuck Jobs should be replaced/failovered elsewhere quickly.

User-defined scaling using a percentage of running Jobs and optional queue deduction.

  • Inputs: runningJobPercentage (float), queueLengthDeduction (int)
  • Formula: scaleTo = min(desired - deduction - running * percentage, maxReplica)
  • Notes:
    • If percentage parse fails, falls back to Basic.

Balance towards desired while staying within capacity; subtract pending from desired unless over max.

  • Formula:
    • If desired + running > maxReplica: scaleTo = maxReplica - running
    • Else: scaleTo = desired - pending
  • Use when pending work should defer new creations and capacity must be respected.

Fill available capacity (excluding pending) up to desired.

  • Formula: scaleTo = min(maxReplica - running - pending, desired)
  • Use when it’s safe to aggressively utilize capacity.
  • Pending-Aware (default): prioritize re-creating stuck Jobs elsewhere.
  • Basic: simplest gap-based scaling.
  • Accurate: conservative, subtracts pending.
  • Eager: aggressive, fills capacity quickly.
  • Custom: tailor behavior with percentage and deductions.

DistributedScaledJob supports per-cluster overrides of selected pod/container fields. Overrides are defined on each memberClusters[] entry and are applied only when creating new Jobs. Existing Jobs are not modified in place.

Supported overrides:

  • podOverrides.nodeSelector: merged into the pod nodeSelector map (override values replace existing keys)
  • podOverrides.tolerations: merged with deterministic deduplication by identity key (key, operator, effect, value); jobTargetRef tolerations are applied first and podOverrides.tolerations are applied on top, so when the identity key matches an entry from podOverrides.tolerations overrides the one from jobTargetRef (last-writer-wins)
  • podOverrides.affinity: replaces base pod affinity
  • podOverrides.containerOverrides.<containerName>.image: replaces container image
  • podOverrides.containerOverrides.<containerName>.env: merged by env var name with override taking precedence
  • podOverrides.containerOverrides.<containerName>.resources:
    • requests and limits are merged by resource name
    • claims are replaced when explicitly provided (including empty list to clear existing claims)

If containerOverrides references a container name that does not exist in scaledJobSpec.jobTargetRef.template.spec.containers, the override is ignored and a warning event is emitted.

Example:

apiVersion: keda.kedify.io/v1alpha1
kind: DistributedScaledJob
metadata:
name: processor-job
spec:
memberClusters:
- name: member-cluster-1
weight: 2
podOverrides:
nodeSelector:
nodepool: gpu
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: region
operator: In
values: ["us-east-1"]
containerOverrides:
processor:
image: my-registry.local/processor:v2
env:
- name: LOG_LEVEL
value: debug
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
- name: member-cluster-2
weight: 3
clusterScheduling:
strategy: weightedRoundRobin
failoverPolicy:
gracePeriod: 1m
hardTaintDuration: 5m
softTaintDuration: 3m
scaledJobSpec:
jobTargetRef:
template:
spec:
containers:
- name: processor
image: myapp:latest
restartPolicy: Never
triggers:
- type: rabbitmq
name: rabbit
metadata:
queueName: tasks
host: http://guest:password@localhost:15672/path/vhost
value: "5"

Status of the DistributedScaledJob provides insights into the job state across member clusters:

status:
desiredJobs: 10
runningJobs: 8
pendingJobs: 2
memberClusterStatuses:
member-cluster-1:
description: Cluster is healthy
id: /etc/mc/kubeconfigs/member-cluster-1.kubeconfig+kedify-agent@member-cluster-1
runningJobs: 3
pendingJobs: 1
stuckJobs: 0
softTainted: false
lastStatusChangeTime: "2025-11-05T16:46:39Z"
state: Ready
excluded: false
member-cluster-2:
description: Cluster is healthy
id: /etc/mc/kubeconfigs/member-cluster-2.kubeconfig+kedify-agent@member-cluster-2
runningJobs: 5
pendingJobs: 1
stuckJobs: 0
softTainted: false
lastStatusChangeTime: "2025-11-05T15:45:44Z"
state: Ready
excluded: false
selector: kedify-agent-distributedscaledjob=processor-job

For a walkthrough example on how to set up and use multi-cluster scaling with Kedify, refer to the examples repository.

spec.clusterScheduling.failoverPolicy controls failover timing, taint duration and duplicate-job handling.

spec:
clusterScheduling:
failoverPolicy:
gracePeriod: 1m
hardTaintDuration: 5m
softTaintDuration: 3m
duplicationPolicy: keepAll

Fields:

  • gracePeriod (default: 1m): how long a pending job can stay pending before it is treated as stuck.
  • hardTaintDuration (default: 5m): how long a cluster remains hard-tainted after failure.
  • softTaintDuration (default: 3m): soft-taint window for escalation.
    • If another failure happens within this window, taint escalates to hard taint.
    • Set to 0s for immediate hard taint on first failure.
  • duplicationPolicy (default: keepAll): how to resolve duplicate source/failover jobs in pending-aware failover.
A Pending source job causes a replacement to be scheduled; four duplication policies determine which job survives and when cleanup occurs.
Scroll to explore
Diagram description

In pending-aware DistributedScaledJob failover, a source job remains Pending beyond its grace period and a replacement is scheduled in an eligible cluster. keepAll retains both jobs. preferFailover removes the source only after a healthy replacement is confirmed. preferSource removes the replacement when the source resumes ownership. immediateSourceCleanup deletes the source immediately after failover scheduling, before replacement health is guaranteed.

  • keepAll

    • Behavior: keep source and failover jobs running when both exist.
    • Provisioning tendency: can temporarily overprovision during failover/recovery windows (availability-first).
    • Underprovisioning risk: lowest among the policies.
  • preferFailover

    • Behavior: prefer the failover job; source is deleted once a healthy failover replacement is confirmed.
    • Provisioning tendency: short-lived overprovisioning can happen while waiting for replacement health confirmation.
    • Underprovisioning risk: low to medium (mainly during failover transitions if replacement cannot be confirmed quickly).
  • preferSource

    • Behavior: prefer the original source job; replacement is deleted when source should resume ownership.
    • Provisioning tendency: generally avoids prolonged overprovisioning.
    • Underprovisioning risk: medium to high during unstable source-cluster recovery (source can be preferred before fully stable).
  • immediateSourceCleanup

    • Behavior: source job is deleted immediately after failover scheduling.
    • Provisioning tendency: minimizes overprovisioning.
    • Underprovisioning risk: highest if the replacement is delayed or fails to become healthy.

Rule of thumb:

  • If you optimize for continuity/availability, use keepAll or preferFailover.
  • If you optimize for strict capacity/cost control, use immediateSourceCleanup or preferSource, accepting higher underprovisioning risk.

weightedRoundRobin is the default strategy. If spec.clusterScheduling.strategy is omitted, DistributedScaledJob uses weightedRoundRobin. In this mode, jobs are distributed by memberClusters[].weight.

  • hard-tainted clusters are excluded from scheduling for all workloads
  • soft-tainted clusters remain eligible
  • memberClusters[].scheduling.priority is ignored in this mode

priorityFailover provides primary/failover behavior. The scheduler prefers the highest memberClusters[].scheduling.priority cluster, and only falls back when that cluster is excluded (for example tainted or overloaded).

Short example focused on failover configuration: Member cluster member-primary is preferred by the scheduler for new jobs until a job fails to progress from Pending to Running for longer than spec.clusterScheduling.failoverPolicy.gracePeriod. After the grace period is reached, the cluster is tainted for this affinity tuple (class: team-a, size: 4): jobs with size 4 and higher are excluded from that tainted cluster, while smaller jobs can still be scheduled there.

apiVersion: keda.kedify.io/v1alpha1
kind: DistributedScaledJob
metadata:
name: processor-job-failover
spec:
clusterScheduling:
strategy: priorityFailover
workloadAffinity:
class: team-a
size: 4
failoverPolicy:
gracePeriod: 1m
hardTaintDuration: 5m
softTaintDuration: 3m
memberClusters:
- name: member-primary
scheduling:
priority: 100
- name: member-failover
scheduling:
priority: 0
scaledJobSpec:
scalingStrategy:
strategy: pendingAware

Important differences vs weightedRoundRobin:

  • clusterScheduling.workloadAffinity is required in priorityFailover
  • memberClusters[].scheduling.priority is required for each member cluster in priorityFailover
  • failover taints are evaluated by (order, size, priority):
    • a taint recorded at size N excludes workloads with size >= N
    • workloads with smaller size can still use the same cluster
  • transient create Job API failures are treated as transient and do not taint the cluster in priorityFailover
  • dsj priorityFailover supports scaledJobSpec.scalingStrategy.strategy: pendingAware (or omitted strategy, which defaults to pendingAware)
  • podOverrides.affinity is only pod-placement configuration and does not drive cluster failover selection

The following example of a DistributedScaledJob splits the execution of job processing from the RabbitMQ task queue between two member clusters in a 2:3 ratio.

apiVersion: keda.kedify.io/v1alpha1
kind: DistributedScaledJob
metadata:
name: processor-job
spec:
memberClusters: # optional list of member clusters to use, if omitted all registered member clusters will be used
- name: member-cluster-1
weight: 2 # weight determines the proportion of jobs to be allocated to this cluster
- name: member-cluster-2
weight: 3
clusterScheduling:
strategy: weightedRoundRobin
failoverPolicy:
gracePeriod: 1m # wait before re-creating jobs that do not progress from Pending to Running
hardTaintDuration: 5m # taint a failing cluster before scheduling jobs on it again
softTaintDuration: 3m # if another failure happens in this window, apply a hard taint
scaledJobSpec: # standard ScaledJob spec
failedJobsHistoryLimit: 2 # keep up to 2 failed jobs, delete all older
successfulJobsHistoryLimit: 2 # keep up to 2 jobs that completed successfully, delete all older
jobTargetRef:
template:
spec:
containers:
- name: processor
image: myapp:latest
command: ["process"]
restartPolicy: Never
pollingInterval: 30
maxReplicaCount: 20
scalingStrategy:
strategy: pendingAware # pending/stuck jobs can be re-created on another cluster
triggers:
- type: rabbitmq
name: rabbit
metadata:
queueName: tasks
host: http://guest:password@localhost:15672/path/vhost
value: "5"

In this example, the DistributedScaledJob named processor-job is configured to scale Jobs across two member clusters.

clusterScheduling.failoverPolicy controls how taints are applied to failing clusters. If omitted, defaults are applied (gracePeriod: 1m, hardTaintDuration: 5m, softTaintDuration: 3m). See FailoverPolicy for details.

The jobTargetRef field contains the standard Kubernetes Job template specification. Jobs are created in the member clusters based on the scaling metrics and cluster weights.

Soft and hard taints for failing clusters: when a call from KEDA cluster to member cluster fails, that cluster is not removed from scheduling immediately. Instead, it is soft-tainted first. After a second failure, the soft taint is escalated to a hard taint. This helps prevent unnecessary scheduling pauses due to temporary network glitches.