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.
API details
Section titled “API details”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.
v1alpha1 fields
Section titled “v1alpha1 fields”Served: true; storage: true.
| Field | Type | Required | Schema default | Meaning and validation |
|---|---|---|---|---|
spec | object | No | Not specified | DistributedScaledJobSpec 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.clusterScheduling | object | No | {"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.failoverPolicy | object | No | {"gracePeriod":"1m","hardTaintDuration":"5m","softTaintDuration":"3m"} | FailoverPolicy controls how the controller should behave when clusters fail |
spec.clusterScheduling.failoverPolicy.duplicationPolicy | string | No | "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.gracePeriod | string | No | "1m" | GracePeriod is the duration to wait before re-creating Jobs that fail to progress from Pending to Running state |
spec.clusterScheduling.failoverPolicy.hardTaintDuration | string | No | "5m" | HardTaintDuration is the duration to taint a failing cluster before attempting to scale Jobs on it again |
spec.clusterScheduling.failoverPolicy.softTaintDuration | string | No | "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.strategy | string | No | "weightedRoundRobin" | Strategy defines how member clusters are selected when creating Jobs. enum: [“weightedRoundRobin”,“priorityFailover”] |
spec.clusterScheduling.workloadAffinity | object | No | Not specified | WorkloadAffinity classifies workloads for failover taint matching. Required when strategy=priorityFailover. |
spec.clusterScheduling.workloadAffinity.class | string | No | Not specified | Class groups workloads for failover taint matching (e.g., for org/team). |
spec.clusterScheduling.workloadAffinity.size | integer | Yes | Not specified | Size is the monotonic affinity class value. minimum: 0 |
spec.memberClusters | array | No | Not specified | MemberClusters 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[] | object | No | Not specified | DistributedScaledJobMemberCluster represents a member cluster where the ScaledJob should be applied. |
spec.memberClusters[].name | string | Yes | Not specified | Name is the name of the member cluster. |
spec.memberClusters[].podOverrides | object | No | Not specified | PodOverrides are applied to Job pod template for this cluster. |
spec.memberClusters[].podOverrides.affinity | object | No | Not specified | See Kubernetes field definitions. |
spec.memberClusters[].podOverrides.containerOverrides | object | No | Not specified | ContainerOverrides are keyed by container name. |
spec.memberClusters[].podOverrides.containerOverrides.* | object | No | Not specified | DistributedScaledJobContainerOverrides defines container-level overrides for a single member cluster. |
spec.memberClusters[].podOverrides.containerOverrides.*.env | array | No | Not specified | Env overrides container environment variables. |
spec.memberClusters[].podOverrides.containerOverrides.*.env[] | object | No | Not specified | EnvVar represents an environment variable present in a Container. |
spec.memberClusters[].podOverrides.containerOverrides.*.env[].name | string | Yes | Not specified | Name of the environment variable. May consist of any printable ASCII characters except ’=’. |
spec.memberClusters[].podOverrides.containerOverrides.*.env[].value | string | No | Not specified | Variable 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[].valueFrom | object | No | Not specified | See Kubernetes field definitions. |
spec.memberClusters[].podOverrides.containerOverrides.*.image | string | No | Not specified | Image overrides container image for this cluster. |
spec.memberClusters[].podOverrides.containerOverrides.*.resources | object | No | Not specified | Resources partially overrides container resource requirements. |
spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims | array | No | Not specified | Claims 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[] | object | No | Not specified | ResourceClaim references one entry in PodSpec.ResourceClaims. |
spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[].name | string | Yes | Not specified | Name 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[].request | string | No | Not specified | Request 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.limits | object | No | Not specified | Limits 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 string | No | Not specified | pattern: ”^(\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))))?$” |
spec.memberClusters[].podOverrides.containerOverrides.*.resources.requests | object | No | Not specified | Requests 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 string | No | Not specified | pattern: ”^(\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9])?)|(\.[0-9]+))))?$” |
spec.memberClusters[].podOverrides.nodeSelector | object | No | Not specified | NodeSelector overrides pod nodeSelector. |
spec.memberClusters[].podOverrides.nodeSelector.* | string | No | Not specified | |
spec.memberClusters[].podOverrides.tolerations | array | No | Not specified | See Kubernetes field definitions. |
spec.memberClusters[].scheduling | object | No | Not specified | Scheduling contains cluster-selection attributes for this member cluster. |
spec.memberClusters[].scheduling.priority | integer | No | Not specified | Priority is used by clusterScheduling.strategy=priorityFailover. Higher value means stronger preference. minimum: 0; maximum: 1000 |
spec.memberClusters[].targetNamespace | string | No | Not specified | TargetNamespace 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[].weight | integer | Yes | 1 | Weight is the weight of the member cluster for scaling distribution. |
spec.scaledJobSpec | object | Yes | Not specified | ScaledJobSpec is the specification of the KEDA ScaledJob |
spec.scaledJobSpec.failedJobsHistoryLimit | integer | No | Not specified | |
spec.scaledJobSpec.jobTargetRef | object | Yes | Not specified | See Kubernetes field definitions. |
spec.scaledJobSpec.maxReplicaCount | integer | No | Not specified | |
spec.scaledJobSpec.minReplicaCount | integer | No | Not specified | |
spec.scaledJobSpec.pollingInterval | integer | No | Not specified | |
spec.scaledJobSpec.rollout | object | No | Not specified | Rollout defines the strategy for job rollouts |
spec.scaledJobSpec.rollout.propagationPolicy | string | No | Not specified | |
spec.scaledJobSpec.rollout.strategy | string | No | Not specified | |
spec.scaledJobSpec.scalingStrategy | object | No | Not specified | ScalingStrategy defines the strategy of Scaling |
spec.scaledJobSpec.scalingStrategy.customScalingQueueLengthDeduction | integer | No | Not specified | |
spec.scaledJobSpec.scalingStrategy.customScalingRunningJobPercentage | string | No | Not specified | |
spec.scaledJobSpec.scalingStrategy.multipleScalersCalculation | string | No | Not specified | |
spec.scaledJobSpec.scalingStrategy.pendingPodConditions | array | No | Not specified | |
spec.scaledJobSpec.scalingStrategy.pendingPodConditions[] | string | No | Not specified | |
spec.scaledJobSpec.scalingStrategy.strategy | string | No | Not specified | enum: [“basic”,“custom”,“accurate”,“eager”,“pendingAware”] |
spec.scaledJobSpec.successfulJobsHistoryLimit | integer | No | Not specified | |
spec.scaledJobSpec.triggers | array | Yes | Not specified | maxItems: 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[] | object | No | Not specified | ScaleTriggers reference the scaler that will be used |
spec.scaledJobSpec.triggers[].authenticationRef | object | No | Not specified | AuthenticationRef points to the TriggerAuthentication or ClusterTriggerAuthentication object that is used to authenticate the scaler with the environment |
spec.scaledJobSpec.triggers[].authenticationRef.kind | string | No | Not specified | Kind of the resource being referred to. Defaults to TriggerAuthentication. enum: [“TriggerAuthentication”,“ClusterTriggerAuthentication”] |
spec.scaledJobSpec.triggers[].authenticationRef.name | string | Yes | Not specified | |
spec.scaledJobSpec.triggers[].metadata | object | Yes | Not specified | |
spec.scaledJobSpec.triggers[].metadata.* | string | No | Not specified | |
spec.scaledJobSpec.triggers[].metricType | string | No | Not specified | MetricTargetType specifies the type of metric being targeted, and should be either “Value”, “AverageValue”, or “Utilization” |
spec.scaledJobSpec.triggers[].name | string | No | Not specified | |
spec.scaledJobSpec.triggers[].type | string | Yes | Not specified | minLength: 1 |
spec.scaledJobSpec.triggers[].useCachedMetrics | boolean | No | Not specified | |
spec.totalTargetReplicas | integer | No | 0 | TotalTargetReplicas is the desired number of replicas |
status
Section titled “status”| Field | Type | Required | Schema default | Meaning and validation |
|---|---|---|---|---|
status | object | No | Not specified | DistributedScaledJobStatus defines the observed state of DistributedScaledJob |
status.conditions | array | No | Not specified | |
status.conditions[] | object | No | Not specified | Condition contains details for one aspect of the current state of this API Resource. |
status.conditions[].lastTransitionTime | string | Yes | Not specified | lastTransitionTime 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[].message | string | Yes | Not specified | message is a human readable message indicating details about the transition. This may be an empty string. maxLength: 32768 |
status.conditions[].observedGeneration | integer | No | Not specified | observedGeneration 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[].reason | string | Yes | Not specified | reason 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[].status | string | Yes | Not specified | status of the condition, one of True, False, Unknown. enum: [“True”,“False”,“Unknown”] |
status.conditions[].type | string | Yes | Not specified | type 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.desiredJobs | integer | Yes | Not specified | DesiredJobs is the desired number of jobs calculated from metrics |
status.memberClusterStatuses | object | No | Not specified | MemberClusterStatuses represents the status of each member cluster |
status.memberClusterStatuses.* | object | No | Not specified | DistributedScaledJobMemberClusterStatus represents the status of a member cluster |
status.memberClusterStatuses.*.description | string | Yes | Not specified | Description provides additional information about the member cluster status |
status.memberClusterStatuses.*.excluded | boolean | No | Not specified | Excluded indicates whether the member cluster is excluded from scaling for example, due to being unhealthy for longer than the grace period |
status.memberClusterStatuses.*.hardTaintScopes | array | No | Not specified | HardTaintScopes lists hard-taint scopes for priorityFailover scheduling. Multiple entries may exist for a single cluster (for different classes/priorities). |
status.memberClusterStatuses.*.hardTaintScopes[] | object | No | Not specified | DistributedScaledJobHardTaintScope identifies one priorityFailover hard-taint scope. |
status.memberClusterStatuses.*.hardTaintScopes[].class | string | Yes | Not specified | Class is the workload affinity class for which the cluster is tainted. This can be empty to represent the default affinity class. |
status.memberClusterStatuses.*.hardTaintScopes[].priority | integer | No | Not specified | Priority is the member-cluster scheduling priority for which the cluster is tainted. |
status.memberClusterStatuses.*.hardTainted | boolean | No | Not specified | HardTainted indicates the cluster is hard tainted for at least one DSJ scheduling path. |
status.memberClusterStatuses.*.id | string | Yes | Not specified | ID is the identification of the member cluster for multicluster-controller |
status.memberClusterStatuses.*.lastScalingProgressTime | string | No | Not specified | LastScalingProgressTime 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.*.lastStatusChangeTime | string | No | Not specified | LastStatusChangeTime 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.*.pendingJobs | integer | Yes | Not specified | PendingJobs is the number of jobs that are pending in the member cluster |
status.memberClusterStatuses.*.provider | string | No | Not specified | Provider is the cluster provider type (for example file, kubeconfig) |
status.memberClusterStatuses.*.runningJobs | integer | Yes | Not specified | RunningJobs is the current number of running jobs in the member cluster |
status.memberClusterStatuses.*.softTainted | boolean | No | Not specified | SoftTainted indicates the cluster has a recent failure but is not hard tainted |
status.memberClusterStatuses.*.state | string | Yes | Not specified | State is the state of the member cluster (e.g., Ready, NotReady) |
status.memberClusterStatuses.*.stuckJobs | integer | Yes | Not specified | StuckJobs is the number of jobs that are stuck (e.g., pending for too long) in the member cluster |
status.pendingJobs | integer | Yes | Not specified | PendingJobs is the current number of pending jobs across all member clusters |
status.print | object | No | Not specified | Print is used for custom printing of DistributedScaledJob status |
status.print.membersCount | string | No | Not specified | MembersCount is the status of member clusters |
status.print.runningJobs | string | No | Not specified | RunningJobs represents the status of running jobs across member clusters |
status.runningJobs | integer | Yes | Not specified | RunningJobs is the current number of running jobs across all member clusters |
status.scalersTargetAverageValue | integer or string | No | Not specified | ScalersTargetAverageValue 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.selector | string | Yes | Not specified | Selector is the selector for the distributed jobs |
status.triggerMetricsStatus | object | No | Not specified | TriggerMetricStatus represents the status of each metric used for scaling by trigger name |
status.triggerMetricsStatus.* | object | No | Not specified | TriggerMetricStatus represents the status of a single metric used for scaling by trigger name |
status.triggerMetricsStatus.*.info | string | No | Not specified | Info provides additional information about the metric status, such as error messages or reasons for inactivity |
status.triggerMetricsStatus.*.lastChangeTime | string | No | Not specified | LastChangeTime is the last time the metric status changed, used for tracking how long a metric has been in a particular state |
status.triggerMetricsStatus.*.state | string | Yes | Not specified | State represents the state of the metric (“Active”, “Inactive”, “Error”) used to determine if scaling should occur |
Inspect the installed contract
Section titled “Inspect the installed contract”kubectl get crd distributedscaledjobs.keda.kedify.io -o yamlkubectl explain distributedscaledjob.spec --recursiveCompare 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.
Use, verify and diagnose
Section titled “Use, verify and diagnose”- Distributed multi-cluster scaling
- Distribute jobs across clusters
- Register member clusters with GitOps
- Diagnose multi-cluster and tenant controllers
Scaling strategies
Section titled “Scaling strategies”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.
Pending-Aware
Section titled “Pending-Aware”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.
Custom
Section titled “Custom”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.
Accurate
Section titled “Accurate”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
- If
- 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.
Choosing a strategy
Section titled “Choosing a strategy”- 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 Pod Overrides
Section titled “DistributedScaledJob Pod Overrides”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 podnodeSelectormap (override values replace existing keys)podOverrides.tolerations: merged with deterministic deduplication by identity key(key, operator, effect, value);jobTargetReftolerations are applied first andpodOverrides.tolerationsare applied on top, so when the identity key matches an entry frompodOverrides.tolerationsoverrides the one fromjobTargetRef(last-writer-wins)podOverrides.affinity: replaces base pod affinitypodOverrides.containerOverrides.<containerName>.image: replaces container imagepodOverrides.containerOverrides.<containerName>.env: merged by env var name with override taking precedencepodOverrides.containerOverrides.<containerName>.resources:requestsandlimitsare merged by resource nameclaimsare 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/v1alpha1kind: DistributedScaledJobmetadata: name: processor-jobspec: 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-jobFor a walkthrough example on how to set up and use multi-cluster scaling with Kedify, refer to the examples repository.
FailoverPolicy
Section titled “FailoverPolicy”spec.clusterScheduling.failoverPolicy controls failover timing, taint duration and duplicate-job handling.
spec: clusterScheduling: failoverPolicy: gracePeriod: 1m hardTaintDuration: 5m softTaintDuration: 3m duplicationPolicy: keepAllFields:
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
0sfor immediate hard taint on first failure.
duplicationPolicy(default:keepAll): how to resolve duplicate source/failover jobs in pending-aware failover.
Duplication Policy
Section titled “Duplication Policy”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
keepAllorpreferFailover. - If you optimize for strict capacity/cost control, use
immediateSourceCleanuporpreferSource, accepting higher underprovisioning risk.
Cluster Scheduling Strategies
Section titled “Cluster Scheduling Strategies”WeightedRoundRobin
Section titled “WeightedRoundRobin”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.priorityis ignored in this mode
PriorityFailover
Section titled “PriorityFailover”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/v1alpha1kind: DistributedScaledJobmetadata: name: processor-job-failoverspec: 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: pendingAwareImportant differences vs weightedRoundRobin:
clusterScheduling.workloadAffinityis required inpriorityFailovermemberClusters[].scheduling.priorityis required for each member cluster inpriorityFailover- failover taints are evaluated by
(order, size, priority):- a taint recorded at size
Nexcludes workloads with size>= N - workloads with smaller size can still use the same cluster
- a taint recorded at size
- transient
create JobAPI failures are treated as transient and do not taint the cluster inpriorityFailover - dsj
priorityFailoversupportsscaledJobSpec.scalingStrategy.strategy: pendingAware(or omitted strategy, which defaults topendingAware) podOverrides.affinityis only pod-placement configuration and does not drive cluster failover selection
DistributedScaledJob Specification
Section titled “DistributedScaledJob Specification”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/v1alpha1kind: DistributedScaledJobmetadata: name: processor-jobspec: 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
Section titled “Soft And Hard Taints”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.