0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kubernetes v1.36でWorkload-Aware Schedulingを動かしてみる

0
Last updated at Posted at 2026-08-09

はじめに

横浜で行われたKubeCon・CloudNativeCon Japan2026にて、SIG Scheduling Update:
Transition from Pod to Workload Scheduling
のセッションを聞いて内容に興味を持ち、Workload-Aware Schedulingについて実際に動かしながら理解を深めてみました。

検証にあたって、以下のブログも参考にしました。

検証内容について

AI/MLワークロードやバッチ処理ワークロードには、単純なPod単位のスケジューリングでは対応できない特有のスケジューリング課題が存在することを受け、v1.36で拡張されたWorkload-Aware Schedulingに関する以下の仕組みを検証しました。

  • Pod GroupとGang Scheduling
    • 複数PodをPodGroupとして扱い、minCountを配置できる場合のみ、まとめてスケジューリングできる。
      • 分散学習において、Podがすべて揃わないと学習を開始できない場合に、一部のPodだけがスケジュールされ、GPUやメモリなどのリソースを占有し続ける状態を防げる。
    • Pod単位の配置で発生するDeadlockを、PodGroupにおけるschedulingPolicy.gang を定義して利用可能なGang Schedulingで回避できる。
  • Job controller連携
    • Indexed JobからWorkloadとPodGroupが自動生成され、各PodにPodGroupへの参照が設定される。
  • Workload-Aware Preemption
    • 高優先度のPodGroupを配置できない場合に、低優先度のPodまたはPodGroupをvictimとして退避し、PodGroup全体の配置を試みる。
    • priorityでPodGroupの優先度を表し、disruptionModeでvictim側のPodを個別に退避できるか、PodGroup全体で退避するかを指定できる。
  • Topology-Aware Scheduling
    • PodGroupにTopology制約を設定し、Podを同じzoneなどのTopology Domainへまとめて配置できる。
    • AI/MLトレーニングやバッチ処理といった複雑な分散ワークロードの場合においてネットワーク遅延を最小化するために有効。

検証環境の準備

Feature GateとWorkload APIの有効化

まず、Kubernetesクラスタがv1.36であることを確認します。

❯ kubectl get nodes
NAME           STATUS   ROLES           AGE    VERSION
tryu1-cp       Ready    control-plane   122d   v1.36.2
tryu1-worker   Ready    <none>          122d   v1.36.2
tryu2-cp       Ready    control-plane   122d   v1.36.2
tryu2-worker   Ready    <none>          122d   v1.36.2
tryu3-cp       Ready    control-plane   122d   v1.36.2
tryu3-worker   Ready    <none>          122d   v1.36.2

そして、以下のFeature Gateを有効化します。私はKubesprayを使ってクラスタ構築をしているのでKubesprayを用いて反映しました。

kube_api_runtime_config:
  - scheduling.k8s.io/v1alpha2=true

kube_apiserver_feature_gates:
  - GenericWorkload=true
  - WorkloadWithJob=true

kube_scheduler_feature_gates:
  - GenericWorkload=true
  - GangScheduling=true
  - WorkloadAwarePreemption=true
  - TopologyAwareWorkloadScheduling=true

kube_controller_feature_gates:
  - WorkloadWithJob=true

PodGroupとGang Schedulingの検証

PodGroupによりPodをまとめて配置する

複数のPodを同じグループとして扱うPodGroupを作成します。PodGroupにGang policyを設定し、Gang Schedulingを行います。
ここではPodGroupを手動で作成し、minCount: 2としています。つまり、2つのPodがスケジュール可能な状態になって初めてまとめてNodeに配置します。

Podを2種類作成し、うち一つを存在しないnodeSelectorによりUnschedulableにします。

apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: gang-demo
  namespace: was-gang-test
spec:
  schedulingPolicy:
    gang:
      minCount: 2
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-ok
  namespace: was-gang-test
  labels:
    app: gang-demo
spec:
  schedulingGroup:
    podGroupName: gang-demo
  restartPolicy: Never
  containers: # 省略
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-ng
  namespace: was-gang-test
  labels:
    app: gang-demo
spec:
  schedulingGroup:
    podGroupName: gang-demo
  nodeSelector:
    was.example.com/target: unavailable
  restartPolicy: Never
  containers: # 省略

この時、通常であればスケジュールできるgang-ok PodもPodGroupのminCountの制限によりPendingとなることがわかります。

$ kubectl -n was-gang-test get pods -o wide
NAME      READY   STATUS    NODE
gang-ng   0/1     Pending   <none>
gang-ok   0/1     Pending   <none>

$ kubectl -n was-gang-test get events --sort-by=.lastTimestamp
LAST SEEN   TYPE      REASON             OBJECT        MESSAGE
3m29s       Warning   FailedScheduling   pod/gang-ok   pod group is unschedulable, waiting for minCount pods from a gang to be scheduled, one or more plugins asked to wait and no plugin rejected pod
45m         Warning   FailedScheduling   pod/gang-ng   0/6 nodes are available: 3 node(s) didn't match Pod's node affinity/selector, 3 node(s) had untolerated taint(s). no new claims to deallocate, preemption: 0/6 nodes are available: 6 Preemption is not helpful for scheduling.

Deadlockの再現と回避

従来、Pod単位でスケジューリングの判断を行う場合、スケジューリング時にDeadlockが発生するリスクがありました。v1.36ではスケジューラがPodGroupを単一の処理単位として評価するように設計したため、この問題を解消することができます。この様子を見てみます。

Deadlockが発生する場合

以下では、nodeAffinityにより配置先のWorker Nodeは2台に限定されます。
さらに、podAntiAffinityによって各NodeへPodを1個だけ配置できるようにしています。
このような設定のPodを4つapplyします。

apiVersion: v1
kind: Pod
metadata:
  name: gang-a-1
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: a
spec:
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker]
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              test: deadlock-slot
          topologyKey: kubernetes.io/hostname
  containers: # 省略
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-b-1
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: b
# 同上
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-a-2
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: a
# 同上
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-b-2
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: b
# 同上

ここでは、Podは以下の順に作成されます:

  • Gang A Pod 1
  • Gang B Pod 1
  • Gang A Pod 2
  • Gang B Pod 2

配置可能なWorkerは2台だけで、podAntiAffinityにより各Nodeへ1 Podしか配置できません。
そのため、以下のようになるはずです:

tryu1-worker: Gang A Pod 1
tryu2-worker: Gang B Pod 1

Gang A Pod 2: Pending
Gang B Pod 2: Pending

実際の様子を確認します。
AとBから1 PodずつがRunningになり、残りがPendingになることで、双方が一部の配置枠を占有して必要数2を満たせない状態になっていることがわかります。
これでは両方のgangが動作せずDeadlockのような状態になってしまいます。

❯ kubectl -n was-deadlock-test get pods -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
gang-a-1   1/1     Running   0          10s   10.233.67.229   tryu2-worker   <none>           <none>
gang-a-2   0/1     Pending   0          9s    <none>          <none>         <none>           <none>
gang-b-1   1/1     Running   0          10s   10.233.65.81    tryu1-worker   <none>           <none>
gang-b-2   0/1     Pending   0          9s    <none>          <none>         <none>           <none>
❯ kubectl -n was-deadlock-test get events --sort-by=.lastTimestamp
LAST SEEN   TYPE      REASON             OBJECT         MESSAGE
34s         Normal    Scheduled          pod/gang-a-1   Successfully assigned was-deadlock-test/gang-a-1 to tryu2-worker
34s         Warning   FailedScheduling   pod/gang-a-2   0/6 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 2 node(s) didn't match pod anti-affinity rules, 3 node(s) had untolerated taint(s). no new claims to deallocate, preemption: 0/6 nodes are available: 2 No preemption victims found for incoming pod, 4 Preemption is not helpful for scheduling.

この様子を図で表すと、以下のようになります:

Screenshot 2026-08-09 at 7.14.04 PM.png
(セッション内のスライドp.41より)

Gang SchedulingによるDeadlockの回避

では、PodGroupを用いてDeadlockを回避する様子を見てみます。先ほどと同じように、Pod定義をA1 → B1 → A2 → B2の順にしています。

apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: gang-a
  namespace: was-deadlock-test
spec:
  schedulingPolicy:
    gang:
      minCount: 2
---
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: gang-b
  namespace: was-deadlock-test
spec:
  schedulingPolicy:
    gang:
      minCount: 2
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-a-1
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: a
spec:
  schedulingGroup:
    podGroupName: gang-a
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker]
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              test: deadlock-slot
          topologyKey: kubernetes.io/hostname
  containers: # 省略
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-b-1
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: b
spec:
  schedulingGroup:
    podGroupName: gang-b
# 同上
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-a-2
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: a
spec:
  schedulingGroup:
    podGroupName: gang-a
# 同上
---
apiVersion: v1
kind: Pod
metadata:
  name: gang-b-2
  namespace: was-deadlock-test
  labels:
    test: deadlock-slot
    gang: b
spec:
  schedulingGroup:
    podGroupName: gang-b
# 同上

今回はgang-aがうまくいっていることがわかります。
以下の順にPodを作成しているものの、Gang B Pod 1 はPendingでGang A Pod 2 がRunningになっています。これは、gang-a PodGroupをスケジュールできるようにするためです。

  • Gang A Pod 1
  • Gang B Pod 1
  • Gang A Pod 2
  • Gang B Pod 2

gang-a PodGroupの2 PodがまとめてRunningになり、もう一方のgang-b PodGroupの2 PodがまとめてPendingになっており、複数gangが配置枠を分け合って双方とも必要数を満たせなくなる状態を回避できています。

❯ kubectl -n was-deadlock-test get podgroups,pods -o wide
NAME                                POLICY   WORKLOAD   STATUS          AGE
podgroup.scheduling.k8s.io/gang-a   Gang     <none>     Scheduled       7s
podgroup.scheduling.k8s.io/gang-b   Gang     <none>     Unschedulable   7s

NAME           READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
pod/gang-a-1   1/1     Running   0          6s    10.233.65.82    tryu1-worker   <none>           <none>
pod/gang-a-2   1/1     Running   0          6s    10.233.67.230   tryu2-worker   <none>           <none>
pod/gang-b-1   0/1     Pending   0          6s    <none>          <none>         <none>           <none>
pod/gang-b-2   0/1     Pending   0          6s    <none>          <none>         <none>           <none>

❯ kubectl -n was-deadlock-test get events --sort-by=.lastTimestamp
LAST SEEN   TYPE      REASON             OBJECT         MESSAGE
32s         Normal    Scheduled          pod/gang-a-1   Successfully assigned was-deadlock-test/gang-a-1 to tryu1-worker
32s         Normal    Scheduled          pod/gang-a-2   Successfully assigned was-deadlock-test/gang-a-2 to tryu2-worker
32s         Warning   FailedScheduling   pod/gang-b-1   0/6 nodes are available: 1 node(s) didn't match Pod's node affinity/selector, 2 node(s) didn't match pod anti-affinity rules, 3 node(s) had untolerated taint(s). no new claims to deallocate, preemption: 0/6 nodes are available: 2 No preemption victims found for incoming pod, 4 Preemption is not helpful for scheduling.

Indexed Jobとの連携

WorkloadWithJobが有効であれば、次のIndexed JobからWorkloadとPodGroupが自動作成されます。
手動でPodGroupを作ったりPodからPodGroupを参照するといった記述から解放されます。

apiVersion: batch/v1
kind: Job
metadata:
  name: indexed-gang
  namespace: was-gang-test
spec:
  completionMode: Indexed
  parallelism: 2
  completions: 2
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: worker
          image: registry.k8s.io/e2e-test-images/agnhost:2.53
          args: ["pause"]
          resources:
            requests:
              cpu: 10m
              memory: 16Mi

2 PodにschedulingGroupが設定され、Job所有のWorkloadとPodGroupが生成されることを確認できます。

$ kubectl -n was-gang-test get jobs,pods,workloads,podgroups
NAME                     STATUS    COMPLETIONS   DURATION   AGE
job.batch/indexed-gang   Running   0/2           90s        90s

NAME                       READY   STATUS    RESTARTS   AGE
pod/indexed-gang-0-477ms   1/1     Running   0          90s
pod/indexed-gang-1-zfwdj   1/1     Running   0          89s

NAME                                                 AGE
workload.scheduling.k8s.io/indexed-gang-68f4bc9659   90s

NAME                                                                               POLICY   WORKLOAD                  STATUS      AGE
podgroup.scheduling.k8s.io/indexed-gang-68f4bc9659-indexed-gang-pgt-0-6764d77859   Gang     indexed-gang-68f4bc9659   Scheduled   90s

Workload単位のスケジューリング制御

Workload-Aware Preemption

優先度の高いPodGroupがスケジュールできるようにするために、優先度の低いPodGroupをPreemptする様子を確認します。

まず、優先度の低いPriorityClassでPodGroupを作成します。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: was-preemption-low
value: 1000
globalDefault: false
description: Low priority used only by the WAS preemption test.
---
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: victim
  namespace: was-preemption-test
spec:
  schedulingPolicy:
    gang:
      minCount: 2
  priorityClassName: was-preemption-low
  disruptionMode: PodGroup
---
apiVersion: v1
kind: Pod
metadata:
  name: victim-1
  namespace: was-preemption-test
  labels:
    app: was-preemption-victim
spec:
  priorityClassName: was-preemption-low
  schedulingGroup:
    podGroupName: victim
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker]
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.10
      ports:
        - name: exclusive-test
          containerPort: 31991
          hostPort: 31991
      resources:
        requests:
          cpu: 10m
          memory: 16Mi
---
apiVersion: v1
kind: Pod
metadata:
  name: victim-2
  namespace: was-preemption-test
  labels:
    app: was-preemption-victim
spec:
  priorityClassName: was-preemption-low
  schedulingGroup:
    podGroupName: victim
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker]
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.10
      ports:
        - name: exclusive-test
          containerPort: 31991
          hostPort: 31991
      resources:
        requests:
          cpu: 10m
          memory: 16Mi

優先度の低いvictim PodGroupの様子です:

❯ kubectl -n was-preemption-test get podgroups,pods -o wide
NAME                                POLICY   WORKLOAD   STATUS      AGE
podgroup.scheduling.k8s.io/victim   Gang     <none>     Scheduled   26s

NAME           READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
pod/victim-1   1/1     Running   0          27s   10.233.65.84    tryu1-worker   <none>           <none>
pod/victim-2   1/1     Running   0          27s   10.233.67.232   tryu2-worker   <none>           <none>

次に、高い優先度のPriorityClassでPodGroupを作成します。

低優先度のvictim Podは、配置対象の2台のWorker NodeでそれぞれhostPort: 31991を使用しています。高優先度のpreemptor Podも同じhostPortを要求するため、そのままでは配置できません。
そこで、Workload-Aware Preemptionにより低優先度のvictimを退避し、preemptorを配置できるか確認します。

通常のPod単位のpreemptionでは、配置に必要なvictim Podだけが退避対象になります。
しかし、victim PodGroupにはdisruptionMode: PodGroupを指定しているため、preemptorが必要とする空きが1 Pod分だけでも、victim PodGroup全体がまとめて退避されます。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: was-preemption-high
value: 10000
globalDefault: false
description: High priority used only by the WAS preemption test.
---
apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: preemptor
  namespace: was-preemption-test
spec:
  schedulingPolicy:
    gang:
      minCount: 1
  priorityClassName: was-preemption-high
  disruptionMode: Pod
---
apiVersion: v1
kind: Pod
metadata:
  name: preemptor-1
  namespace: was-preemption-test
  labels:
    app: was-preemption-preemptor
spec:
  priorityClassName: was-preemption-high
  schedulingGroup:
    podGroupName: preemptor
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker]
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.10
      ports:
        - name: exclusive-test
          containerPort: 31991
          hostPort: 31991
      resources:
        requests:
          cpu: 10m
          memory: 16Mi

preemptorを適用した後の様子です:

❯ kubectl -n was-preemption-test get podgroups,pods -o wide
NAME                                   POLICY   WORKLOAD   STATUS      AGE
podgroup.scheduling.k8s.io/preemptor   Gang     <none>     Scheduled   8s
podgroup.scheduling.k8s.io/victim      Gang     <none>     Scheduled   23s

NAME              READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
pod/preemptor-1   1/1     Running   0          8s    10.233.67.237   tryu2-worker   <none>           <none>

高優先度のpreemptor-1を配置するために低優先度victimがpreemptionされていることがわかります。victimのdisruptionMode: PodGroupにより、高優先度Podが必要とする枠が1つだけでもvictim PodGroup全体がpreemption対象となっています。

Topology-Aware Scheduling

同じPodGroupのPodを、同じZoneやRackといったtopology domainへまとめて配置できることを確認します。
まず、Worker Nodeへ検証用zoneラベルを設定します。

kubectl label node tryu1-worker topology.kubernetes.io/zone=zone-a --overwrite
kubectl label node tryu2-worker topology.kubernetes.io/zone=zone-a --overwrite
kubectl label node tryu3-worker topology.kubernetes.io/zone=zone-b --overwrite
❯ kubectl get nodes -L topology.kubernetes.io/zone
NAME           STATUS   ROLES           AGE    VERSION   ZONE
tryu1-cp       Ready    control-plane   121d   v1.36.2   
tryu1-worker   Ready    <none>          121d   v1.36.2   zone-a
tryu2-cp       Ready    control-plane   121d   v1.36.2   
tryu2-worker   Ready    <none>          121d   v1.36.2   zone-a
tryu3-cp       Ready    control-plane   121d   v1.36.2   
tryu3-worker   Ready    <none>          121d   v1.36.2   zone-b

以下を適用し、PodGroup内のPodが同じZoneに配置されるかを確認します。

apiVersion: scheduling.k8s.io/v1alpha2
kind: PodGroup
metadata:
  name: same-zone
  namespace: was-topology-test
spec:
  schedulingPolicy:
    gang:
      minCount: 2
  schedulingConstraints:
    topology:
      - key: topology.kubernetes.io/zone
---
apiVersion: v1
kind: Pod
metadata:
  name: same-zone-1
  namespace: was-topology-test
spec:
  schedulingGroup:
    podGroupName: same-zone
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker, tryu3-worker]
  containers: # 省略
---
apiVersion: v1
kind: Pod
metadata:
  name: same-zone-2
  namespace: was-topology-test
spec:
  schedulingGroup:
    podGroupName: same-zone
  restartPolicy: Never
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: [tryu1-worker, tryu2-worker, tryu3-worker]
  containers: # 省略

確認してみると、2 Podともzone-bのtryu3-workerへ配置され、PodGroupがScheduledとなっていることがわかりました。

❯ kubectl apply -f k8s/sig-scheduling/topology-aware.yaml
❯ kubectl -n was-topology-test get podgroups,pods -o wide
NAME                                   POLICY   WORKLOAD   STATUS      AGE
podgroup.scheduling.k8s.io/same-zone   Gang     <none>     Scheduled   28m

NAME              READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
pod/same-zone-1   1/1     Running   0          28m   10.233.69.198   tryu3-worker   <none>           <none>
pod/same-zone-2   1/1     Running   0          28m   10.233.69.199   tryu3-worker   <none>           <none>

まとめ

v1.36で拡張されたWorkload-Aware Schedulingの機能を動かしながら試してみました。セッションを聴いているだけだと分かりづらいような箇所も、実際に動かしてみると理解が深まりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?