💰 Calculate Gross-Net Salary→

Gang Scheduling Reaches Beta in Kubernetes v1.37: Workload, PodGroup and the New CompositePodGroup

· Aloka Technology· 0 views· 7 min read
Gang Scheduling Reaches Beta in Kubernetes v1.37: Workload, PodGroup and the New CompositePodGroup

Kubernetes v1.37 promotes the Workload and PodGroup APIs to Beta and adds CompositePodGroup for hierarchical all-or-nothing placement. This analysis covers how it works and what is still unfinished.

A distributed training job needs 8 workers and 1 driver. The default Kubernetes scheduler places Pods one at a time, so if the cluster has room for 6 workers, those 6 start, hold their GPUs, and wait for 2 that never arrive. Meanwhile another job is stuck in the same state, and neither can finish. This is the partial-placement deadlock that gang scheduling exists to prevent. In Kubernetes v1.37 (released August 26, 2026) the native answer, the Workload and PodGroup APIs, reached Beta, and a new CompositePodGroup API extends it to hierarchies. The question: how does the scheduler treat a whole tree of Pods as one atomic unit, and what is still unfinished?

Background: from Pods to workloads

The official v1.37 post by SIG Scheduling contributors from Google and Microsoft describes a multi-release series: an introduction in v1.35 (December 2025), an advance in v1.36 (May 2026), and this release. In v1.36 the project separated a static template (Workload) from runtime group state (PodGroup), but policies were limited to one flat group. The post lists these v1.37 changes:

  • Workload and PodGroup APIs, gang scheduling, workload-aware preemption and shared DRA ResourceClaims for PodGroups graduate to Beta. Both APIs are promoted to v1beta1.
  • A new CompositePodGroup API for tree-shaped hierarchies.
  • Controller integration APIs and a workloadbuilder Go library for out-of-tree controllers, plus an upgraded native Job controller integration.

How it works

The all-or-nothing contract

A gang policy declares minCount, the minimum number of Pods that must be placeable together. Until the scheduler can place that many, none are bound. In v1.37 the PodGroup becomes a first-class queue entry: previously member Pods were queued individually, now only the top-level PodGroup object is queued, so all members share one queueing behavior. minCount is also now mutable, so a controller can shrink or grow a gang without interrupting already-scheduled Pods.

Hierarchy with CompositePodGroup

A Workload gains spec.compositePodGroupTemplates. A controller stamps out one root CompositePodGroup and child PodGroup objects linked through parentCompositePodGroupName. The scheduler evaluates the tree recursively from root to leaves: a parent is schedulable only when enough child groups satisfy its policy (minGroupCount for gangs), and each leaf must place its own minCount Pods. Once a valid combination exists, Pods across the whole hierarchy are bound atomically. If the root cannot be satisfied, nothing is bound.

Preemption and topology

Workload-aware preemption now lives under the GenericWorkload feature gate; the separate WorkloadAwarePreemption gate was merged into it. A disruptionMode of single allows child groups to be preempted independently, while all evicts the entire hierarchy if any Pod in the subtree must be preempted. (The post notes the older values PodGroup and Pod were renamed to all and single.) Topology constraints are resolved top-down: a child group's candidate domains are confined to the domain the parent was placed in. The post's example confines a whole workload to one zone and each sub-group to one rack inside it.

Deep analysis

Queueing is the quiet change. If Pods are queued individually, a gang is only a filter over a stream of single-Pod decisions, and fairness between a large gang and many small Pods is accidental. Queuing the group as one item is what lets the scheduler reason about the unit it must place. The post itself says it lays groundwork for more advanced PodGroup queueing strategies, which signals that current queueing is a foundation, not a finished policy.

Preemption cost. To decide whether preemption helps, the scheduler simulates removing all potential victims, re-runs the scheduling algorithm, then tries to reprieve victims. The post states v1.36 re-ran the algorithm for every victim reprieval, while v1.37 runs it once, assumes the preemptor placement from its output, and then checks whether each victim can still run in place. The post presents this as a performance and optimality change but gives no benchmark, and I found none elsewhere, so treat any speed claim as unmeasured.

Closing a hole. In v1.36, default single-Pod preemption ignored PodGroups and could disrupt individual Pods even when the group had disruptionMode: {all: {}}. v1.37 makes default preemption respect the field. That fix matters if you relied on all semantics in v1.36.

Comparison with alternatives

I did not research external batch schedulers for this article, so the table compares only what the post itself says about scope.

LayerPlacement unitStatus in v1.37 per the post
Default scheduler, plain PodsSingle PodUnchanged; no all-or-nothing guarantee
Workload plus PodGroup (flat gang)One PodGroupBeta, v1beta1
CompositePodGroup (hierarchy)Whole tree, multi-level topologyNew; shown as v1alpha3 in the post's YAML
JobSet, LeaderWorkerSetHigher-level controllersNamed as structures this API enables native scheduling for; integration is via the new controller APIs

Practical example

This is the post's two-level template, reproduced with its API versions. Untested by me; the mix of v1beta1 and v1alpha3 is as published, so check the versions served by your cluster with kubectl api-resources before applying.

apiVersion: scheduling.k8s.io/v1beta1
kind: Workload
metadata:
  name: example-workload
spec:
  compositePodGroupTemplates:
    - name: workload-root
      schedulingPolicy:
        gang:
          minGroupCount: 2
      podGroupTemplates:
        - name: workers
          schedulingPolicy:
            gang:
              minCount: 4
        - name: driver
          schedulingPolicy:
            gang:
              minCount: 1

A controller then creates the runtime objects. A child group links to its parent like this:

apiVersion: scheduling.k8s.io/v1beta1
kind: PodGroup
metadata:
  name: example-workload-workers
spec:
  parentCompositePodGroupName: example-root-group
  workloadRef:
    workloadName: example-workload
    templateName: workers
  schedulingPolicy:
    gang:
      minCount: 4

Multi-level topology is added by putting schedulingConstraints.topology with a key such as topology.kubernetes.io/zone on the root template and a rack label on the children.

Limitations and failure modes

  • Breaking alpha changes. The post says v1alpha2 was replaced by v1alpha3 with breaking changes around disruptionMode. Manifests from v1.36 need migration.
  • Mixed maturity. The hierarchy API is new in this release and is not described as Beta. Do not build a platform contract on it yet.
  • Gang fragmentation. A large minCount plus a fragmented cluster means the group may stay pending indefinitely. That is the intended trade (no partial start) but it needs capacity planning and alerts on pending PodGroups.
  • Eviction blast radius. disruptionMode: all means one preempted Pod evicts the whole hierarchy.
  • Ecosystem gaps. Out-of-tree controllers must adopt the integration APIs; the post only confirms the native Job controller integration.

Engineering recommendations

  1. Use a flat PodGroup with gang scheduling for jobs that deadlock on partial placement today, and test on a non-production v1.37 cluster.
  2. Keep minCount at the true minimum viable size, not the preferred size, and use the new mutability for elastic jobs.
  3. Choose disruptionMode deliberately: all for tightly coupled training, single where workers tolerate loss.
  4. Pin the API versions in version-controlled manifests and review them at every minor upgrade while parts remain pre-Beta.
  5. Measure scheduling latency and pending time before and after, since no published benchmark covers your topology.

Conclusion

Kubernetes now has a native, Beta-level gang scheduling primitive and an early hierarchical one. The value is not speed but the removal of a deadlock class, at the cost of waiting for full capacity. Next step: take one job that currently starts partially, model it as a Workload with a single PodGroup, and compare time-to-full-start and stuck-Pod incidents over a week.

Sources

Verification note: all behavior and YAML come from the official v1.37 post, which I read up to its topology section; I did not read the KEPs, run a cluster, or find any scheduler benchmark.

Share:

Leave a comment

Explore More

Discover More Articles

Browse Articles
↑Zalo