This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
Workload API
FEATURE STATE:
Kubernetes v1.37 [beta](disabled by default)
The Workload API resource defines the scheduling requirements and structure of a multi-Pod
application. While workload controllers such as Job
manage the application's runtime state, the Workload specifies how groups of Pods
should be scheduled. The Job controller is the only built-in controller that creates
PodGroup objects from the Workload's
PodGroupTemplates at runtime.
What is a Workload?
The Workload API resource is part of the scheduling.k8s.io/v1beta1
API group
and your cluster must have that API group enabled, as well as the GenericWorkload
feature gate,
before you can use this API.
A Workload is a static, long-lived policy template. It defines what scheduling
policies should be applied to groups of Pods, but does not track runtime state itself.
Runtime scheduling state is maintained by PodGroup
objects, which controllers create from the Workload's PodGroupTemplates.
API structure
A Workload consists of two fields: a list of PodGroupTemplates and an optional controller
reference. The entire Workload spec is immutable after creation: you cannot modify
existing templates, add new templates, or remove templates from podGroupTemplates.
PodGroupTemplates
The spec.podGroupTemplates list defines the distinct components of your workload.
For example, a machine learning job might have a driver template and a worker template.
Each entry in podGroupTemplates must have:
- A unique
name that will be used to reference the template in the PodGroup's spec.podGroupTemplateRef.
- A scheduling policy (
basic or gang).
Each entry can also have
priority and disruption mode
fields.
The maximum number of PodGroupTemplates in a single Workload is 8.
apiVersion: scheduling.k8s.io/v1beta1
kind: Workload
metadata:
name: training-job-workload
namespace: some-ns
spec:
controllerRef:
apiGroup: batch
kind: Job
name: training-job
podGroupTemplates:
- name: workers
schedulingPolicy:
gang:
# The gang is schedulable only if 4 pods can run at once
minCount: 4
priorityClassName: high-priority
disruptionMode:
all: {}
When a workload controller creates a PodGroup from one of these templates, it copies the
schedulingPolicy into the PodGroup's own spec. Changes to the Workload only affect
newly created PodGroups, not existing ones.
Referencing a workload controlling object
The controllerRef field links the Workload back to the specific high-level object defining the application,
such as a Job or a custom CRD. This is useful for observability and tooling.
This data is not used to schedule or manage the Workload.
CompositePodGroupTemplates
FEATURE STATE:
Kubernetes v1.37 [alpha](disabled by default)
When the CompositePodGroup
feature gate and the scheduling.k8s.io/v1alpha3 API group
are enabled, you can use CompositePodGroupTemplates to define multi-level, hierarchical scheduling
requirements in a Workload. These requirements can include enforcing nested topology constraints
across different layers of cluster infrastructure (multi-level topology-aware scheduling),
all-or-nothing scheduling across child groups (multi-level gang scheduling), or group-level
disruption policies.
CompositePodGroupTemplates can be defined using the spec.compositePodGroupTemplates field in the
Workload API. At runtime, workload controllers create CompositePodGroup
and PodGroup objects from these templates to maintain the runtime
scheduling state of the hierarchy. While PodGroup objects manage groups of Pods at the leaves,
CompositePodGroup objects represent non-leaf groups that enforce scheduling policies across child groups.
Note:
In a Workload specification, spec.compositePodGroupTemplates and spec.podGroupTemplates
fields form a union: a Workload must define either spec.podGroupTemplates (for flat
workloads) or spec.compositePodGroupTemplates (for hierarchical workloads), but cannot
specify both.
Structure and constraints
The spec.compositePodGroupTemplates field defines non-leaf templates in a group-template
hierarchy tree. Each entry represents a template for a CompositePodGroup and can contain:
- Child templates: Nested
CompositePodGroupTemplates (for intermediate non-leaf
groups) or PodGroupTemplates (for leaf groups containing Pods).
- Scheduling policy: Specifies how child groups within this composite group are
scheduled:
basic: Child groups are admitted and scheduled independently.
gang: Enforces multi-level all-or-nothing scheduling across child groups.
Requires minGroupCount, which specifies the minimum number of child groups
that must be schedulable simultaneously for the composite group to be feasible.
- Scheduling constraints: Optional
topology constraints
for multi-level topology-aware scheduling.
- Priority, preemption policy and disruption mode: Optional
priorityClassName,
disruptionMode (Single or All) and preemptionPolicy for
workload-aware preemption.
To ensure cluster stability and control-plane efficiency, the group-template hierarchy
enforces the following limits:
- Maximum nesting depth: The group-template hierarchy supports a maximum depth of
4 levels.
- List limit: Every
compositePodGroupTemplates and podGroupTemplates list is strictly
capped at a maximum of 8 items.
Note:
Right now, you cannot add new or remove existing CompositePodGroupTemplates. You can only change
the minCount value in the gang scheduling policy defined in the leaf PodGroupTemplates.
Example
The following example defines a hierarchical Workload with a CompositePodGroup template
that enforces gang scheduling across two child PodGroup templates (minGroupCount: 2),
each specifying its own gang scheduling policy:
apiVersion: scheduling.k8s.io/v1alpha3
kind: Workload
metadata:
name: gang-of-gangs-workload
namespace: default
spec:
compositePodGroupTemplates:
- name: root
schedulingPolicy:
gang:
# Requires both child PodGroups to be schedulable together
minGroupCount: 2
podGroupTemplates:
- name: workers-a
schedulingPolicy:
gang:
# Requires 4 Pods in this group to be schedulable
minCount: 4
- name: workers-b
schedulingPolicy:
gang:
# Requires 4 Pods in this group to be schedulable
minCount: 4
Gang scheduling with Jobs
FEATURE STATE:
Kubernetes v1.36 [alpha](disabled by default)
When the
WorkloadWithJob
feature gate is enabled, the
Job controller compiles a Job's
.spec.scheduling configuration into Workload and PodGroup objects before it
creates any Pods. You opt into gang scheduling by setting
.spec.scheduling.schedulingPolicy.gang on the Job; an omitted gang.minCount
defaults to the Job's .spec.parallelism, so all Pods must be schedulable together
before any of them are bound to nodes.
When .spec.scheduling is omitted, the Job defaults to the basic policy, which
preserves standard pod-by-pod scheduling. Either way the Job controller creates the
Workload and PodGroup for you, so you do not need to create them yourself.
Other workload controllers (such as JobSet) may manage their own Workload and
PodGroup objects independently.
For the full set of scheduling fields and examples, see
Integrate with Workload APIs.
What's next
1 - Pod Group Disruption and Priority
FEATURE STATE:
Kubernetes v1.37 [beta](disabled by default)
PodGroup can declare a disruption mode. This mode dictates how
the scheduler can disrupt a running PodGroup, for example to accommodate
a higher priority PodGroup. A PodGroup also has a priority,
which overrides the priority of the individual pods from the group
for workload-aware preemption events.
Disruption mode types
Note:
In v1.36, the
priority or
disruptionMode fields of the PodGroup are only respected
by
workload-aware preemption.
During the pod scheduling phase, the scheduler does not take into account
the
priority or
disruptionMode fields of the PodGroup. This limitation no longer
applies in v1.37.
The API supports two disruption modes: Single and All.
The default one is Single.
Single
The Single mode instructs the scheduler to treat all Pods in the group as separate entities,
allowing independent disruption of a single pod from a PodGroup.
All
The All mode emphasizes "all-or-nothing" semantics for disruption.
It instructs the scheduler that all pods from the PodGroup have to be disrupted together.
CompositePodGroup
FEATURE STATE:
Kubernetes v1.37 [alpha](disabled by default)
A CompositePodGroup can also declare a disruptionMode in its specification, which controls
how the scheduler disrupts child groups within the composite group during preemption events.
The API supports two disruption modes for CompositePodGroups:
Single: Allows individual child groups within the CompositePodGroup to be disrupted
independently during preemption.
All: Enforces all-or-nothing disruption semantics across the CompositePodGroup hierarchy.
If any Pod contained in the hierarchy below this CompositePodGroup has to be preempted, all of
the Pods from the entire hierarchy must be preempted.
If not specified, the mode defaults to Single.
Note:
In v1.37, a group can set its disruption mode to All and have child groups that have a mode set to
Single. In such case, the top-level All mode overrides the descendant Single modes.
This configuration is discouraged due to unclear semantics.
Pod group priority
PodGroup uses the same concept of PriorityClass as single Pods.
Once you have created one or more PriorityClasses,
you can create a PodGroup that specifies one of those PriorityClass names in its specification.
The priority admission controller uses the priorityClassName field and populates the integer value of the priority.
If the priority class is not found, the PodGroup is rejected.
When priorityClassName is not set for a PodGroup, Kubernetes looks for a default (a PriorityClass with globalDefault set true)
If there is no PriorityClass with globalDefault set true, a PodGroup with no priorityClassName has priority zero.
The priority of the PodGroup is an authoritative priority for all pods in the group during workload-aware preemption events.
This value is also used for the ordering of PodGroups in the scheduling queue.
When the priorities of individual pods forming this PodGroup differ from PodGroup priority
the PodGroup will not be scheduled with all pods in a single pod group should have the same priority as the pod group error.
When the PodGroupPreemptionPolicy
feature gate is enabled, PodGroup has also preemptionPolicy field. This field is also taken from the PriorirtyClass.
It is an authoratitive field for all pods in the group and it decides whether the PodGroup can perform a preemption of
lower priority pods and pod groups to accomodate a place for itself. When the feature gate is enabled all pods in the PodGroup
must have the same preemptionPolicy as PodGroup. Otherwise the PodGroup will not be scheduled with
all pods in a single pod group should have the same preemption policy as the pod group's preemption policy error.
When PodGroup has preemptionPolicy: Never it will not perform workload aware preemption.
If the feature flag is disabled, all pods forming PodGroup must have the same preemptionPolicy.
Otherwise the PodGroup will not be scheduled with
all pods in a single pod group should have the same preemption policy error.
The following YAML is an example of a PodGroup configuration that uses the high-priority PriorityClass,
which maps to the integer priority value of 1000000.
The priority admission controller checks the specification and resolves the priority of the PodGroup to 1000000.
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
namespace: ns-1
name: job-1
spec:
priorityClassName: high-priority
CompositePodGroup priority
FEATURE STATE:
Kubernetes v1.37 [alpha](disabled by default)
CompositePodGroup API has priorityClassName and priority fields as well and their resolution
is performed in the same way as for the PodGroups, through the priority admission controller.
The priority of a root CompositePodGroup acts as the authoritative priority for all child groups
and Pods within its hierarchy during
workload-aware preemption events.
All Pods within a single group hierarchy must share the exact same priority and must be equal to the
priority of the root CompositePodGroup.
The value of priority is also used for the ordering of root CompositePodGroups in the scheduling
active queue.
Note:
In v1.37, the scheduler doesn't validate if the non-root groups have priority value that is equal to
the priority of the root CompositePodGroup.
PreemptionPolicy in CompositePodGroup
FEATURE STATE:
Kubernetes v1.37 [alpha](disabled by default)
The CompositePodGroup API has the preemptionPolicy field as well and its resolution is performed
in the exact same way as for the PodGroup API.
The value of preemptionPolicy of the root CompositePodGroup determines whether
workload-aware preemption can be
invoked to fit its Pods during scheduling if needed:
PreemptLowerPriority policy allows preempting victims with lower priority,
Never policy disables workload-aware preemption for that root CompositePodGroup.
All Pods within a single group hierarchy must share the exact same preemption policy which must be
equal to the preemption policy of the root CompositePodGroup.
If the feature flag is disabled, the root CompositePodGroup will be allowed to perform preemption
unless one of the Pods that belongs to the group hierarchy has preemptionPolicy set to Never.
Note:
In v1.37, when the feature gate is enabled, the scheduler doesn't validate if the non-root groups
have preemption policy that is equal to the preemption policy of the root CompositePodGroup.
What's next
2 - PodGroup Scheduling Policies
FEATURE STATE:
Kubernetes v1.37 [beta](disabled by default)
Every PodGroup must declare a scheduling policy
in its spec.schedulingPolicy field. This policy dictates how the scheduler treats the
collection of Pods in the group.
Policy types
The schedulingPolicy field supports two policy types: basic and gang.
You must specify exactly one.
Basic policy
The basic policy instructs the scheduler to evaluate all Pods on a best-effort basis.
Unlike the gang policy, a PodGroup using the basic policy is considered feasible
regardless of how many of its Pods are currently schedulable.
The primary reason to use the basic policy is to organize Pods into a group for better
observability and management, while still evaluating them together within a single, atomic
PodGroup scheduling cycle.
This policy is suited for groups that do not require simultaneous startup but logically
belong together, or to open the way for group-level constraints that do not imply
"all-or-nothing" placement.
schedulingPolicy:
basic: {}
Gang policy
The gang policy enforces "all-or-nothing" scheduling. This is essential for tightly-coupled
workloads where partial startup results in deadlocks or wasted resources.
This can be used for Jobs
or any other batch process where all workers must run concurrently to make progress.
The gang policy requires a minCount field, which is the minimum number of Pods that must be
schedulable simultaneously for the group to be feasible:
schedulingPolicy:
gang:
# The number of Pods that must be schedulable simultaneously
# for the group to be admitted.
minCount: 4
Setting policies via PodGroupTemplates
When using the Workload API, you define scheduling
policies inside PodGroupTemplates. The workload controller copies the policy from the
template into each PodGroup it creates, making the PodGroup self-contained. Changes to the
Workload's templates only affect newly created PodGroups, not existing ones.
For standalone PodGroups (created without a Workload), you set spec.schedulingPolicy
directly on the PodGroup itself.
Policies in CompositePodGroups
FEATURE STATE:
Kubernetes v1.37 [alpha](disabled by default)
When the CompositePodGroup
feature gate and the scheduling.k8s.io/v1alpha3 API group
are enabled, CompositePodGroupTemplates in a Workload and the CompositePodGroup objects also
declare a scheduling policy.
While a scheduling policy in a PodGroup governs a collection of individual Pods, a
CompositePodGroup scheduling policy governs its direct child groups (which can be both
CompositePodGroup and PodGroup objects).
Policy types for CompositePodGroups
Similar to PodGroups, the spec.schedulingPolicy field of a CompositePodGroup supports two
types:
basic: Child groups within the CompositePodGroup are evaluated and admitted independently.
gang: Enforces multi-level all-or-nothing scheduling across child groups. The
CompositePodGroup is schedulable only if at least minGroupCount child groups can be scheduled
simultaneously.
schedulingPolicy:
gang:
# The minimum number of child groups that must be schedulable
# simultaneously for this composite group to be admitted.
minGroupCount: 2
Setting composite policies via templates
When using the Workload API, scheduling policies for
CompositePodGroups are defined inside CompositePodGroupTemplates. Workload controllers copy the
schedulingPolicy specified in the templates into each CompositePodGroup created at runtime.
Unlike leaf PodGroupTemplates where minCount can be updated, minGroupCount in a
CompositePodGroupTemplate is immutable.
What's next
3 - Topology-Aware Workload Scheduling
FEATURE STATE:
Kubernetes v1.36 [alpha](disabled by default)
Topology-Aware Scheduling (TAS) is a feature of the Workload API that optimizes the placement of
pods within the cluster.
TAS ensures that all pods within a PodGroup are co-located into a specific topology domain,
such as a single server rack or zone. This minimizes inter-pod communication latency and prevents
workload fragmentation across the cluster infrastructure.
Topology-aware scheduling with gang scheduling policy
When applied to PodGroups with gang scheduling policy, TAS simulates the potential assignment
(placement) of the full group of pods at once. It guarantees that at least the specified
minCount pods can fit together into the same topology domain before committing resources.
If no feasible placement is found, the entire PodGroup becomes unschedulable.
This is the recommended approach for workloads like distributed AI and ML training that strictly
require proximity to minimize inter-pod communication latency.
If new pods are added to the PodGroup where some pods are already scheduled (for example, if pods
are recreated), the scheduler will force all new incoming pods to land on the exact same topology
domain where the existing pods currently reside. If that specific domain lacks sufficient capacity
for the new pods, the pods will remain pending - even if it means that less than minCount pods
are scheduled at this point.
Note:
As of v1.36 Topology-Aware Scheduling does not trigger workload or pod preemption. If no
feasible placement can be found without triggering preemption, the PodGroup becomes unschedulable.
Topology-aware scheduling with basic scheduling policy
Using TAS with basic scheduling policy may exhibit inconsistent behavior. The scheduler may only
observe a subset of pods when entering the PodGroup scheduling cycle - therefore placement
feasibility is only evaluated for the observed pods, rather than the entire PodGroup. To partially
mitigate this limitation, you can use scheduling gates to hold off PodGroup scheduling until all
pods within the PodGroup are in the scheduling queue.
If no feasible placement is found for the entire PodGroup, only a subset of pods may be scheduled,
and they are guaranteed to meet the scheduling constraints.
If new pods are added to the PodGroup where some pods are already scheduled, the scheduler will act
the same as in case of gang policy - forcing the new pods into the same domain, unless there is
insufficient capacity (in which case the new pods will remain pending).
API configuration: scheduling constraints
Every PodGroup (or PodGroupTemplate) may optionally declare the schedulingConstraints field,
which is interpreted by the placement-based PodGroup scheduling algorithm.
If constraints are defined in PodGroupTemplate, they will be copied to referencing PodGroups.
As of Kubernetes v1.36, the API supports topology constraints.
Note:
As of Kubernetes v1.36, you can specify only a single topology constraint in each PodGroup.
Topology constraint
To define a topology constraint for a PodGroup you need to set a key, which corresponds to
a Kubernetes node label, representing the target topology domain (for example, a rack or a zone).
The scheduler strictly enforces that all pods within the PodGroup are placed onto nodes that share
the exact same value for this specified label.
Here is an example of a PodGroup configured with a topology constraint:
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
name: example-podgroup
spec:
schedulingPolicy:
gang:
minCount: 4
schedulingConstraints:
topology:
- key: topology.example.com/rack
Multi-level topology-aware scheduling
FEATURE STATE:
Kubernetes v1.37 [alpha](disabled by default)
Complex workloads might require co-location of their Pods at different levels of the cluster
infrastructure. For example, an entire workload may need to run within a single availability zone,
while different parts of that workload may require strict co-location within specific server racks.
Such multi-level co-location requirements can be expressed using the CompositePodGroup API and
by specifying topology constraints at different levels of a group hierarchy.
Using the CompositePodGroup API requires enabling the
CompositePodGroup
feature gate and the
scheduling.k8s.io/v1alpha3 API group.
Multi-level topology constraints resolution
Every group inside a CompositePodGroup hierarchy can specify a topology constraint which
guarantees that all descendant Pods of that group will be scheduled in the same topology domain,
matching that group's constraint.
During hierarchical scheduling, the
scheduler resolves these constraints in a top-down manner. Specifically, topology domains that
are considered during scheduling of a child group are confined within a topology domain that
corresponds to the placement assumed by the parent group.
Kubernetes does not impose any strict requirements on the physical hierarchy of topology labels - topology
keys are arbitrary node labels. However, the order in which you specify topology constraints from
parent to child determines the order in which the scheduler subdivides topology domains.
Note:
As of Kubernetes v1.37, you can specify only a single topology constraint in each
CompositePodGroup.
Example
The following example configures a Workload where the parent CompositePodGroupTemplate
constrains the entire workload to a single availability zone (topology.example.com/zone), while
two child PodGroupTemplate entries (workers and driver) constrain their respective
Pods to server racks (topology.example.com/rack) within that zone:
apiVersion: scheduling.k8s.io/v1alpha3
kind: Workload
metadata:
name: example-workload
spec:
compositePodGroupTemplates:
- name: root
schedulingPolicy:
gang:
minGroupCount: 2
schedulingConstraints:
topology:
- key: topology.example.com/zone
podGroupTemplates:
- name: workers
schedulingPolicy:
gang:
minCount: 8
schedulingConstraints:
topology:
- key: topology.example.com/rack
- name: driver
schedulingPolicy:
gang:
minCount: 1
schedulingConstraints:
topology:
- key: topology.example.com/rack
After creating the Workload object, the corresponding group objects are created as follows:
- Root
CompositePodGroup referencing the root template.
- Two child
PodGroup objects (workers and driver), each referencing the root
CompositePodGroup as their parent group.
apiVersion: scheduling.k8s.io/v1alpha3
kind: CompositePodGroup
metadata:
name: workload-root
spec:
workloadRef:
workloadName: example-workload
templateName: root
schedulingPolicy:
gang:
minGroupCount: 2
schedulingConstraints:
topology:
- key: topology.example.com/zone
---
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
name: workload-workers
spec:
parentCompositePodGroupName: workload-root
workloadRef:
workloadName: example-workload
templateName: workers
schedulingPolicy:
gang:
minCount: 8
schedulingConstraints:
topology:
- key: topology.example.com/rack
---
apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
name: workload-driver
spec:
parentCompositePodGroupName: workload-root
workloadRef:
workloadName: example-workload
templateName: driver
schedulingPolicy:
gang:
minCount: 1
schedulingConstraints:
topology:
- key: topology.example.com/rack
During scheduling, the scheduler first selects an availability zone for workload-root. It then
subdivides the nodes in that zone by rack to find feasible rack placements for workload-workers
and workload-driver within the selected zone.
For example, consider a cluster with five nodes labeled as follows:
| Node |
topology.example.com/zone |
topology.example.com/rack |
node-a |
zone-1 |
rack-1 |
node-b |
zone-1 |
rack-1 |
node-c |
zone-1 |
rack-2 |
node-d |
zone-2 |
rack-1 |
node-e |
zone-2 |
rack-3 |
When processing workload-root, the scheduler evaluates candidate placements across all cluster
nodes based on the topology.example.com/zone topology key:
| Evaluated candidate placement |
Nodes in candidate placement |
zone-1 |
node-a, node-b, node-c |
zone-2 |
node-d, node-e |
When evaluating candidate placements for workload-workers, the scheduler subdivides only the nodes
within the placement assumed by workload-root based on the topology.example.com/rack topology key:
| Parent placement |
Evaluated candidate placement |
Nodes in candidate placement |
zone-1 |
rack-1 |
node-a, node-b |
zone-1 |
rack-2 |
node-c |
zone-2 |
rack-1 |
node-d |
zone-2 |
rack-3 |
node-e |
Candidate placements generated for the sibling workload-driver PodGroup are identical to those
generated for workload-workers, since both groups specify the same topology key
(topology.example.com/rack).
What's next
4 - Scheduling Building Block APIs and the workloadbuilder Library
Reusable scheduling API primitives that in-tree and out-of-tree workload controllers embed in their own APIs, and the shared workloadbuilder library that compiles them into Workload, PodGroup, and CompositePodGroup objects.
FEATURE STATE:
Kubernetes v1.37 [beta](disabled by default)
Workload-aware Scheduling defines a set of reusable API building blocks under the
scheduling.k8s.io API group.
Controller authors embed these primitives into their own APIs so that users express
scheduling intent (gang scheduling, topology, disruption behavior) with a consistent
schema across the ecosystem, and the shared workloadbuilder library compiles that
intent into the scheduler-facing Workload,
PodGroup, and CompositePodGroup objects.
The built-in consumer of these building blocks today is the
Job controller, gated by the
WorkloadWithJob feature gate.
Reusable building blocks
The building blocks are strongly-typed Go structs in the scheduling.k8s.io/v1alpha3
API group. They are meant for controller authors: a controller embeds these structs into
its own API type as fields, and the workloadbuilder library compiles them. The type names
follow two conventions: leaf-level types are prefixed WorkloadPodGroup... (for example,
WorkloadPodGroupSchedulingPolicy) and the multi-level variants - WorkloadCompositePodGroup....
Each controller chooses the field names and structure that are idiomatic for its own API, so
these blocks impose no fixed top-level shape. Although not strictly enforced, reusing the
standard field names and structure is recommended, so that a user who has configured gang
scheduling on one controller's resource recognizes the same options on another's.
To adopt the building blocks, a controller adds the ones it wants to support as fields on
its own types. For example, the Job API groups
all four of them into a single type reachable at spec.scheduling:
type JobSchedulingConfiguration struct {
SchedulingPolicy *schedulingv1alpha3.WorkloadPodGroupSchedulingPolicy
SchedulingConstraints *schedulingv1alpha3.WorkloadPodGroupSchedulingConstraints
DisruptionMode *schedulingv1alpha3.WorkloadPodGroupDisruptionMode
ResourceClaims []schedulingv1alpha3.WorkloadPodGroupResourceClaim
}
A different controller might nest the same blocks per component of a multi-part workload,
or support only a subset of them.
Scheduling policy
The scheduling policy block carries the same basic and gang policies as a PodGroup's
spec.schedulingPolicy. See
PodGroup scheduling policies
for what each policy means and how the scheduler applies it.
The one difference is that the block's gang minimum count is optional. Users may leave
minCount unset, in which case the controller supplies a default that makes sense for its
own domain; the Job controller, for example, uses the Job's parallelism.
Scheduling constraints
The scheduling constraints block carries the topology constraints documented in
Topology-aware workload scheduling:
a node label key naming the domain (such as a rack or a zone) that every Pod in the group
must share, with at most one topology constraint per group. Controllers should freeze the
field after creation, since constraints are immutable in the compiled Workload.
Disruption mode
The disruption mode block selects whether the group's Pods may be disrupted individually
(single) or only as a unit (all), corresponding to the Pod and PodGroup disruption
modes documented in
Pod group disruption and priority.
The library rejects combinations that are not meaningful. For example, prevent all disruption mode
for PodGroups with BasicSchedulingPolicy, because the preemption unit must not be larger than the
scheduling unit - a group scheduled pod-by-pod has no group-level unit to preempt or disrupt.
Resource claims
The resource claims block expresses which
dynamic resource allocation
claims are shared by every Pod in the group rather than allocated per Pod. Each entry names
the claim within the group and points at either an existing
ResourceClaim or a
ResourceClaimTemplate
from which one is generated. A group may declare at most four claims.
Pods consume the devices allocated to the group by declaring a matching claim in their own
spec, using the same name and referring to the same object.
Composite building blocks
Multi-level controllers that orchestrate other controllers (for example, JobSet
creating Jobs) coordinate a group of groups. For that layer, the API provides an
analogous set of primitives prefixed with WorkloadCompositePodGroup...
(for example WorkloadCompositePodGroupSchedulingPolicy). They follow the same shapes as
the leaf-level blocks, except the composite gang policy uses minGroupCount (the minimum
number of child groups that must be schedulable together) in place of the leaf's
minCount. Keeping leaf and composite types distinct lets each hierarchy level evolve
independently.
Example: the Job integration
The Job controller is the built-in example of these blocks in use. A user fills in a Job's
spec.scheduling, and the controller compiles it into a Workload and PodGroup. See
Integrate with Workload APIs
for a complete Job manifest, the defaults that apply when spec.scheduling is omitted, and
which fields you can change after creation.
The workloadbuilder library
workloadbuilder is a shared Go library that turns a controller's scheduling intent into
the scheduler-facing Workload and its runtime PodGroup/CompositePodGroup objects, so each
controller does not reimplement defaulting, validation, and template compilation. It is designed for both
in-tree controllers (such as the Job controller) and out-of-tree controllers (such as
JobSet or Kubeflow TrainJob), which vendor it like any other Go dependency. It ships from
k8s.io/component-helpers/scheduling/schedulingv1/workloadbuilder.
The library consumes the scheduling.k8s.io/v1alpha3 building blocks and compiles them into
scheduling.k8s.io/v1beta1 Workload and PodGroup objects, while CompositePodGroup
objects remain scheduling.k8s.io/v1alpha3.
How a controller uses it
A controller describes its workload as a tree of WorkloadItem nodes, one per logical
component. A node with no children becomes a single PodGroupTemplate, while a node with
children becomes a CompositePodGroupTemplate over them, which is how a multi-level
controller represents a group of groups. Each node carries:
- a default config, the controller's own defaults for anything the user leaves unset. This
is where a controller decides, for example, that an unconfigured Job stays on
basic
scheduling.
- an input, the user's intent taken from the controller's API. The controller records each
building block together with the field path it lives at, so validation errors point at the
exact field the user set.
- optional callbacks, which adjust the merged configuration. This is how a controller
supplies a context-specific default, such as filling in an unset gang minimum count from
the Job's parallelism.
The controller then hands that tree to a Builder and works through four calls:
NewBuilder constructs the builder from the tree, along with the name, namespace, and
owner reference for the object to be produced. The owner becomes the Workload's
controller reference, which is used for discovery and garbage collection.
Validate resolves the tree and reports any problems as a list of field errors, which a
controller returns from its own API validation.
BuildWorkload compiles the tree into a Workload. The result is cached, so several
PodGroups can be created from one compiled result.
NewPodGroup creates a runtime PodGroup from one of the compiled templates, naming the
template it should be built from.
builder := workloadbuilder.NewBuilder(item, opts)
if errs := builder.Validate(ctx, workloadbuilder.ValidationInput{}); len(errs) > 0 {
// reject the request
}
workload, err := builder.BuildWorkload()
podGroup, err := builder.NewPodGroup("trainer-pg", item.Name)
For complete, runnable versions of this flow, including how the Job controller wires it up,
see the examples in the
package reference.
Validating a scheduling configuration
Validate checks a configuration in two layers:
- Structural validation of the building blocks themselves: required fields, value ranges,
the rule that exactly one member of a union is set, and immutability. These checks come from
declarative validation rules generated
from the API types.
- Controller-policy checks that declarative validation cannot express: the allow-lists
described below, and cross-field rules such as rejecting the
all disruption mode alongside
the basic policy.
Validation also differs between creating and updating an object. On an update, the library
additionally enforces the fields that are frozen after creation, which means the controller
has to supply the previously stored configuration along with the new one. On a create there is
nothing to compare against, so those checks do not apply.
Opting out of declarative validation
Whether you want the first layer depends on where your controller runs, and one option
controls it:
- Out-of-tree controllers leave declarative validation enabled, which is the default.
Nothing else applies those structural rules to a custom resource, so a single
Validate
call covers both layers.
- In-tree controllers set
DisableDeclarativeValidation, because the API server already
runs declarative validation on the embedded blocks while validating the parent object.
Skipping the first layer avoids checking the same fields twice, leaving Validate to run
only the controller-policy checks.
Opting in to scheduling options
Because the building-block types are shared across controllers, future releases may add
scheduling options that do not make sense for every controller. To keep new options from
silently leaking in, workloadbuilder uses an allow-list model: a controller declares the
policies and disruption modes it supports, and Validate rejects anything outside that set,
reporting the error at the offending block's field path.
Options are therefore denied by default. When a new policy is introduced, an existing
controller keeps rejecting it until its maintainers extend the allow-list, which for an
out-of-tree controller means updating its vendored copy of the library as well.
builder := workloadbuilder.NewBuilder(item, workloadbuilder.BuildOptions{
Owner: owner,
AllowedPolicies: []workloadbuilder.SchedulingPolicyOption{workloadbuilder.BasicPolicy, workloadbuilder.GangPolicy},
AllowedDisruptionModes: []workloadbuilder.DisruptionModeOption{workloadbuilder.SingleMode, workloadbuilder.AllMode},
})
allErrs := builder.Validate(ctx, workloadbuilder.ValidationInput{})
Generating PodGroups from an existing Workload
When the Workload already exists, whether compiled by a parent controller or created manually,
a child controller that only manages the runtime PodGroup uses NewBuilderFromExistingWorkload
instead. That builder creates PodGroup objects from the supplied Workload using its own owner
reference. It does not validate or compile anything, so the existing Workload is never
recompiled.
builder := workloadbuilder.NewBuilderFromExistingWorkload(parentWorkload, workloadbuilder.BuildOptions{Owner: owner})
podGroup, err := builder.NewPodGroup("trainer-pg", "trainer-pgt-0")
What's next