「Kueue」と「Volcano」で分散学習JobのGang Schedulingを実現する
第24回の今回は、分散学習Jobの「All or Nothing」配置を実現するため、「Kueue」のTopology Aware Schedulingと「Volcano」によるGang Schedulingを検証・比較しながら解説します。
6:30
はじめに
スリーシェイクの Sreake 事業部に所属する李(LinkedIn)です。
近年、LLM をはじめとするモデルの大規模化が進んでいます。1 台の GPU では扱いきれない規模の学習を複数の GPU に分散して実行する「分散学習」も、いまや一般的な手法です。
分散学習は、モデルやデータを複数のワーカーに分割し、ワーカーごとに GPU を割り当てて並列に計算する方式です。各ワーカーはステップごとに計算結果を交換し、全員が揃うのを待ってから次のステップへ進みます。つまり、 1 つでも欠ければ残りのワーカーはそこで停止してしまいます。Kubernetes 上ではこのワーカー 1つひとつが Pod となり、それらをまとめた単位が 1 つの Job になります。
そのため、分散学習の Job は、すべての Pod がそろって配置されるか、それが不可能であれば 1 つも配置されないか、のどちらかでなければなりません(いわゆる「All or Nothing」の概念)。
Kubernetes でこれを扱うためのツールとして「Kueue」があります。Kueue は、アドミッションコントロール(admission control)によって Job をクラスタに受け入れるかどうかを判断します。クラスタの GPU クォータが不足している場合、Job は admit されず、空きが出るまでキューで待機します。
しかし、受け入れが許可されたとしても、その Job が実際にどのノードへ配置されるかは別の問題です。例えば、GPU を 1 枚ずつ要求する 4 個の Pod で構成され、4 個すべてが起動してはじめて意味を持つ Job を考えてみます。
クォータはクラスタ全体の「総量」を管理するだけなので、実際のノードの空き状況と一致するとは保証されません。Kueue の管理外で消費されているリソースがあったり、空きが複数のノードに細かく分かれていたりすると、クォータ上は足りていても全 Pod を配置できないことがあります。その場合、4 個のうち 3 個だけが Running となり、残る 1 個は Pending のまま止まります。All or Nothing が崩れた状態です。
これを防ぐには、全 Pod を同時に配置できるときだけ配置する仕組みが必要です。これを「gang scheduling」と呼び、実現する方法は大きく 2 つあります。
- Kueue の Topology Aware Scheduling(以下TAS): クラスタのトポロジーを Kueue に教え、全 Pod が収まらなければ admit しない
- Volcano による gang scheduling: 配置を Volcano に任せ、全 Pod がそろうときだけ配置する
本記事では、Kueue単体と上記2つの解法について、実際の環境で検証・比較します。
検証環境
本記事では kind でクラスタを作成し、各ノードに extended resource(example.com/gpu)を付与して環境を構築します。
検証したいのは GPU 演算そのものではなく「スケジューラが Pod をどう配置するか」なので、実際の GPU がなくても、Fake GPU を持つノードを用意した環境で十分です。
Jobは「はじめに」で挙げた例に合わせ、GPU を 1 枚ずつ要求する 4 個の Pod を 1 つの分散学習Jobと見立てます。4 個すべてがそろって起動しなければ意味を持たない、という前提です。
- Kubernetes: v1.36.1(kind のノードイメージ)
- コントロールプレーン 1 台
- ワーカーノード 2 台(example.com/gpu: 2 を付与)
- 合計 4 枚の Fake GPU
- Kueue ClusterQueue の example.com/gpu
- nominalQuota = 4
- 分散 Job
- parallelism: 4、Pod ごとに GPU 1 枚を要求(合計 4 枚が必要)
クラスタの作成と Fake GPU の設定
kind を用いて Kubernetes クラスタを構築します。
kind create cluster --config kind-config.yaml --name kueue-volcano-test
kubectl get nodes
NAME STATUS ROLES AGE VERSION
kueue-volcano-test-control-plane Ready control-plane 34s v1.36.1
kueue-volcano-test-worker Ready <none> 19s v1.36.1
kueue-volcano-test-worker2 Ready <none> 19s v1.36.1
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.36.1
- role: worker
image: kindest/node:v1.36.1
- role: worker
image: kindest/node:v1.36.1各ワーカーノードに extended resource を付与します。実際の GPU はなくても、スケジューラの配置動作は再現されます。
for n in kueue-volcano-test-worker kueue-volcano-test-worker2; do
kubectl patch node "$n" --subresource=status --type=json \
-p '[{"op":"add","path":"/status/capacity/example.com~1gpu","value":"2"}]'
done
kubectl get nodes -o custom-columns='NODE:.metadata.name,GPU:.status.capacity.example\.com/gpu'
NODE GPU
kueue-volcano-test-control-plane <none>
kueue-volcano-test-worker 2
kueue-volcano-test-worker2 2
Kueue のインストールとクォータ構成
まず Kueue をインストールします。
VERSION=v0.18.1
kubectl apply --server-side -f \
https://github.com/kubernetes-sigs/kueue/releases/download/${VERSION}/manifests.yaml
# コントローラーが Running になるまで待機
kubectl wait --for=condition=Available -n kueue-system \
deployment/kueue-controller-manager --timeout=120s
deployment.apps/kueue-controller-manager condition met
続いて、Kueue のクォータを構成します。ResourceFlavor → ClusterQueue → LocalQueue の階層で構成し、ここでは example.com/gpu の nominalQuota を 4 に指定します。
- ResourceFlavor: リソースの「種類」を定義します。今回はラベル指定なしで、すべてのノードにマッチさせています
- ClusterQueue: クラスタ全体で使えるクォータの総量(CPU・メモリ・GPU)を決めます
- LocalQueue: 各 Namespace(チーム)から利用する入り口です。ここに Job を投入し、その先の ClusterQueue のクォータを消費します
kubectl apply -f kueue-setup.yaml
# kueue-setup.yaml
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: default-flavor
# nodeLabels なし = すべてのノードにマッチ
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: gpu-queue
spec:
namespaceSelector: {} # すべての namespace から参照可能
resourceGroups:
- coveredResources: ["cpu", "memory", "example.com/gpu"]
flavors:
- name: default-flavor
resources:
- name: "cpu"
nominalQuota: 16
- name: "memory"
nominalQuota: 32Gi
- name: "example.com/gpu"
nominalQuota: 4 # クォータは 4 枚
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-a
namespace: default
spec:
clusterQueue: gpu-queue
作成されたリソースを確認します。まだJobを生成していないため、ClusterQueue と LocalQueue の PENDING WORKLOADS / ADMITTED WORKLOADS はどちらも 0 になっています。
kubectl get resourceflavor
NAME AGE
default-flavor 6m22s
kubectl get clusterqueue gpu-queue -o wide
NAME COHORT STRATEGY PENDING WORKLOADS ADMITTED WORKLOADS
gpu-queue BestEffortFIFO 0 0
kubectl get localqueue team-a -n default
NAME CLUSTERQUEUE PENDING WORKLOADS ADMITTED WORKLOADS
team-a gpu-queue 0 0
Kueue 単体での限界の検証
アドミッションは通るが、配置は保証されない
検証のため、まず GPU を 1 枚占有するダミー Pod をデプロイします。これにより実際に空いている GPU は worker: 1 枚 + worker2: 2 枚 = 3 枚 となります。この状態なら、GPU を 4 枚必要とする Job は admit されないはずです。
kubectl apply -f dummy-hog.yaml
# dummy-hog.yaml
apiVersion: v1
kind: Pod
metadata:
name: gpu-hog
namespace: default
spec:
nodeName: kueue-volcano-test-worker
containers:
- name: hog
image: registry.k8s.io/pause:3.9
resources:
requests:
example.com/gpu: "1"
limits:
example.com/gpu: "1"
kubectl get pod gpu-hog -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
gpu-hog 1/1 Running 0 3s 10.244.1.2 kueue-volcano-test-worker <none> <none>
その後、GPU が 4 枚必要なJobをデプロイします。
kubectl apply -f distributed-job.yaml
# distributed-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: distributed-training
namespace: default
labels:
kueue.x-k8s.io/queue-name: team-a # Kueue: この LocalQueue へ投入
spec:
parallelism: 4
completions: 4
suspend: true # Kueue が admit するまで待機
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: trainer
image: registry.k8s.io/pause:3.9 # 実際の演算はせずリソースだけ占有(スケジューリング検証用)
resources:
requests:
cpu: "500m"
memory: "128Mi"
example.com/gpu: "1"
limits:
example.com/gpu: "1"
ところが Workload を確認すると、ADMITTED = True になっています。空きが足りないはずなのに、Kueue はこの Job を受け入れました。
kubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-distributed-training-ad89a team-a gpu-queue True 5s
ここで問題になるのは、先ほどデプロイした gpu-hog が Kueue の管理外にあるという点です。gpu-hog は LocalQueue を経由していないため、Kueue はこの Job によるリソース消費を認識しません。Kueue がクォータの消費として数えるのは、ClusterQueue でクォータを予約した Workload だけだからです。
そのため、クラスタの実際の空きが 3 枚に減っていても、Kueue から見れば GPU の 4 枚はすべて未使用であり、このJobは admit されます。この「クォータ上の空き」と「実際の空き」のズレが、これから見る部分配置を引き起こします。
kubectl get pods -n default -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
distributed-training-6hx2f 0/1 Pending 0 33s <none> <none> <none> <none>
distributed-training-854j2 1/1 Running 0 33s 10.244.1.3 kueue-volcano-test-worker <none> <none>
distributed-training-pd7q5 1/1 Running 0 33s 10.244.2.4 kueue-volcano-test-worker2 <none> <none>
distributed-training-qff6b 1/1 Running 0 33s 10.244.2.3 kueue-volcano-test-worker2 <none> <none>
gpu-hog 1/1 Running 0 99s 10.244.1.2 kueue-volcano-test-worker <none> <none>
Workload は Admitted(True)であるにもかかわらず、起動できたのは 3 個だけで、残りの 1 個は Pending のままです。Kueue が admit したことで Pod は 4 個作成されましたが、kube-scheduler は Pod を 1 つずつ独立に配置していくため、空いている 3 枚に 3 個だけを載せ、4 個目の Pod はどのノードにも配置できませんでした。結果として、3 個の Pod が GPU 3 枚を占有したまま、4 個目の Pod が配置されるのを待ち続ける状態になります。
分散学習はすべての Pod が起動してはじめて意味を持つため、3 個だけが起動しているこの状態は、GPU 3 枚を無駄にしながら処理も進められないという非効率を生みます。Kueue の役割はアドミッションまでで、その先の配置には関与しないからです。
Kueue の Topology Aware Scheduling による Gang Scheduling
前回の問題は、Kueue がクラスタ全体の総量しか見ていないことでした。TAS を有効にすると、Kueue はノードやトポロジードメイン単位の空き容量を追跡し、全 Pod が収まる配置を見つけられたときにだけ admit します。配置先もそのとき確定します。
まず、ワーカー 2 台を同じ rack として扱うラベルを付けます。
or n in kueue-volcano-test-worker kueue-volcano-test-worker2; do
kubectl patch node "$n" --subresource=status --type=json \
-p '[{"op":"add","path":"/status/capacity/example.com~1gpu","value":"2"}]'
kubectl label node "$n" cloud.provider.com/topology-rack=rack-1
done
kubectl get nodes -o custom-columns='NODE:.metadata.name,GPU:.status.capacity.example\.com/gpu,RACK:.metadata.labels.cloud\.provider\.com/topology-rack'
NODE GPU RACK
kueue-tas-test-control-plane <none> <none>
kueue-tas-test-worker 2 rack-1
kueue-tas-test-worker2 2 rack-1
Kueue 単体との違いは、Topology の追加と ResourceFlavor の topologyName の 2 点だけです。
kubectl apply -f kueue-tas-setup.yaml
# kueue-tas-setup.yaml
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: Topology
metadata:
name: gpu-topology
spec:
levels:
- nodeLabel: cloud.provider.com/topology-rack
- nodeLabel: kubernetes.io/hostname
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
name: tas-flavor
spec:
nodeLabels:
cloud.provider.com/topology-rack: rack-1
topologyName: gpu-topology # ★ これで TAS が有効になる
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
name: gpu-queue
spec:
namespaceSelector: {}
resourceGroups:
- coveredResources: ["cpu", "memory", "example.com/gpu"]
flavors:
- name: tas-flavor
resources:
- name: "cpu"
nominalQuota: 16
- name: "memory"
nominalQuota: 32Gi
- name: "example.com/gpu"
nominalQuota: 4
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
name: team-a
namespace: default
spec:
clusterQueue: gpu-queue
Job 側は Podのテンプレートにアノテーションを 1 行足すだけです。
kubectl apply -f distributed-training-tas.yaml
# distributed-training-tas.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: distributed-training-tas
namespace: default
labels:
kueue.x-k8s.io/queue-name: team-a
spec:
parallelism: 4
completions: 4
suspend: true
backoffLimit: 0
template:
metadata:
annotations:
kueue.x-k8s.io/podset-required-topology: cloud.provider.com/topology-rack # ここ
spec:
restartPolicy: Never
containers:
- name: trainer
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: "500m"
memory: "128Mi"
example.com/gpu: "1"
limits:
example.com/gpu: "1"
kubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-distributed-training-tas-29aa5 team-a 10s
Podの状態を確認するとgpu-hogのみです。Job の Pod は 1 つも作られていません。TAS はノードの容量からKueue 管理外の Pod による使用量も差し引いて空きを計算します。クォータ上は 4 枚空いていても、配置できないと判断して admit を見送ります。
kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
gpu-hog 1/1 Running 0 2m16s
kubectl describe workload -n default job-distributed-training-tas-29aa5 | grep -A 10 Events
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Pending 19m kueue-admission couldn't assign flavors to pod set main: topology "gpu-topology" allows to fit only 3 out of 4 pod(s)
gpu-hog を削除してGPU 4 枚をすべて確保します。
kubectl delete pod gpu-hogところが、しばらく待っても admit されず、Pod も作れません。GPU は 4 枚とも空いているのに Pending のままです。Kueue は管理外の Pod が消えても何も反応しないためです。
kubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-distributed-training-tas-29aa5 team-a 3m26s
kubectl get pods -n default -o custom-columns='POD:.metadata.name,NODE:.spec.nodeName,PHASE:.status.phase'
POD NODE PHASE
ノードにラベルを付けて更新イベントを起こすと2 秒ほどで admit され、4 個の Pod がすべて Running になりました。
kubectl label node kueue-volcano-test-worker probe=1
kubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED FINISHED AGE
job-distributed-training-tas-29aa5 team-a gpu-queue True 3m34s
kubectl get pods -n default -o custom-columns='POD:.metadata.name,NODE:.spec.nodeName,PHASE:.status.phase'
POD NODE PHASE
distributed-training-tas-6jj86 kueue-volcano-test-worker Running
distributed-training-tas-k2gb5 kueue-volcano-test-worker Running
distributed-training-tas-khmv6 kueue-volcano-test-worker2 Running
distributed-training-tas-rj698 kueue-volcano-test-worker2 Running
Volcano を用いた Gang Scheduling
Gang schedulingを実現するもう 1 つの方法は、admit したうえで配置を別のスケジューラに任せる方法です。Volcano は PodGroup という単位で「最低 N 個そろわなければ 1 つも配置しない」を実現します。
その前に: TAS を無効化する
TAS を有効にしたまま進むと Kueue のadmit段階で止まり、Volcano の出番が来ません。ResourceFlavor を default-flavor に戻し、改めてGPU1枚を占有するdummy Podを生成します。
kubectl apply -f kueue-setup.yaml
clusterqueue.kueue.x-k8s.io/gpu-queue configured
kubectl apply -f dummy-hog.yaml
Volcano のインストール
helm repo add volcano-sh https://volcano-sh.github.io/helm-charts
helm repo update
helm install volcano volcano-sh/volcano -n volcano-system --create-namespace
# scheduler / controllers / admission の確認
kubectl get pods -n volcano-system
NAME READY STATUS RESTARTS AGE
volcano-admission-7798f5c6bc-9n9kl 1/1 Running 0 21s
volcano-controllers-64c9d4fdc7-bbjsf 1/1 Running 0 21s
volcano-scheduler-84f4b669dd-8fvrq 1/1 Running 0 21s
Volcano は 3 つのコンポーネントで構成されます。実際にgang scheduling を行う volcano-scheduler、PodGroup などのカスタムリソースを管理する volcano-controllers、admission webhook として検証やデフォルト値の補完を行う volcano-admission です。3 つとも Running になっていれば準備完了です。
Jobのマニフェスト修正
Volcano に配置を任せるため、Job のマニフェストを次のように変更します。
- suspend: true: Kueue が admit したあとに Volcano が動作するようにします
- minMember: 4 の PodGroup を明示的に作成: 4 個未満では配置しないようにします
- schedulerName: volcano: 配置を Volcano に委ねます
kubectl apply -f volcano-job.yaml
# volcano-job.yaml
---
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: distributed-training-pg
namespace: default
spec:
minMember: 4 # ★ 4 個未満では配置しない
---
apiVersion: batch/v1
kind: Job
metadata:
name: distributed-training-volcano
namespace: default
labels:
kueue.x-k8s.io/queue-name: team-a # Kueue: この LocalQueue へ投入
spec:
parallelism: 4
completions: 4
suspend: true # Kueue が admit するまで待機
template:
metadata:
annotations:
scheduling.k8s.io/group-name: distributed-training-pg
spec:
schedulerName: volcano # 配置は Volcano が担当
restartPolicy: Never
containers:
- name: trainer
image: registry.k8s.io/pause:3.9
resources:
requests:
cpu: "500m"
memory: "128Mi"
example.com/gpu: "1"
limits:
example.com/gpu: "1"
クォータ上は 4 枚が空いているため admit されます。ここまでは Kueue 単体と同じです。
ubectl get workloads -n default
NAME QUEUE RESERVED IN ADMITTED AGE
job-distributed-training-volcano-28a5c team-a gpu-queue True 22s
ただし、空きは 3 枚しかないため Volcano は minMember(4)を満たせず、Pod は 1 つもスケジュールされません。PodGroup は Inqueue かつ Unschedulable です。
kubectl get podgroup -n default
NAME STATUS MINMEMBER RUNNINGS AGE
distributed-training-pg Inqueue 4 4m27s
kubectl describe podgroup -n default distributed-training-pg
...
Message: 1/4 tasks in gang unschedulable: pod group is not ready, 4 Pending, 4 minAvailable; Pending: 1 Unschedulable, 3 Schedulable. Origin reason is distributed-training-volcano-kc2xq: 0/3 nodes are unavailable: 3 Insufficient example.com/gpu.
Reason: NotEnoughResources
Status: True
Type: Unschedulable
Phase: Inqueue
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unschedulable 1s (x9 over 9s) volcano 4/4 tasks in gang unschedulable: pod group is not ready, 4 Pending, 4 minAvailable; Pending: 1 Unschedulable, 3 Schedulable. Origin reason is distributed-training-volcano-rd2jp: 0/3 nodes are unavailable: 3 Insufficient example.com/gpu.
Pod は「3 個 Running / 1 個 Pending」ではなく、全 Pod が Pending です。
kubectl get pods -n default -l job-name=distributed-training-volcano -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
distributed-training-volcano-c46lh 0/1 Pending 0 65s <none> <none> <none> <none>
distributed-training-volcano-j8pfk 0/1 Pending 0 65s <none> <none> <none> <none>
distributed-training-volcano-jdlqj 0/1 Pending 0 65s <none> <none> <none> <none>
distributed-training-volcano-kc2xq 0/1 Pending 0 65s <none> <none> <none> <none>
schedulerName も確認します。kube-scheduler なら 3 個が Running になっていたはずです。
kubectl get pods -n default -l job-name=distributed-training-volcano -o custom-columns='POD:.metadata.name,SCHED:.spec.schedulerName,NODE:.spec.nodeName,PHASE:.status.phase'
POD SCHED NODE PHASE
distributed-training-volcano-c46lh volcano <none> Pending
distributed-training-volcano-j8pfk volcano <none> Pending
distributed-training-volcano-jdlqj volcano <none> Pending
distributed-training-volcano-kc2xq volcano <none> Pending
gpu-hog を削除して 4 枚すべてを空けると、Job は正常に配置されます。
kubectl delete -f dummy-hog.yaml
kubectl get pods -n default -l job-name=distributed-training-volcano -o custom-columns='POD:.metadata.name,SCHED:.spec.schedulerName,NODE:.spec.nodeName,PHASE:.status.phase'
POD SCHED NODE PHASE
distributed-training-volcano-c46lh volcano kueue-volcano-test-worker2 Running
distributed-training-volcano-j8pfk volcano kueue-volcano-test-worker2 Running
distributed-training-volcano-jdlqj volcano kueue-volcano-test-worker Running
distributed-training-volcano-kc2xq volcano kueue-volcano-test-worker Running
PodGroup 側も Unschedulable が解消され、minMember の 4 個がそろって配置されたことが分かります。
kubectl get podgroup -n default
NAME STATUS MINMEMBER RUNNINGS AGE
distributed-training-pg Running 4 4 5m31s
kubectl describe podgroup -n default distributed-training-pg
...
Reason: tasks in gang are ready to be scheduled
Status: True
Type: Scheduled
Phase: Running
Running: 4
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unschedulable 41s (x25 over 65s) volcano 4/4 tasks in gang unschedulable: pod group is not ready, 4 Pending, 4 minAvailable; Pending: 1 Unschedulable, 3 Schedulable. Origin reason is distributed-training-volcano-rd2jp: 0/3 nodes are unavailable: 3 Insufficient example.com/gpu.
Normal Scheduled 11s
これにより部分配置の問題は解消されました。しかし、この待機中も Workload のステータスは ADMITTED = True のままとなります。つまり、Pod が 1 つも稼働していない状態でも、Kueue 上ではすでに GPU 4 枚が消費されていると見なされている点に注意が必要です。
3 つの構成の比較
| 構成 | Admit | 判断の時点 | クォータ消費 | Podの結果 |
| Kueue単体(クォータのみ) | True | admit時の1回(総量のみ) | 4枚 | 3 Running / 1 Pending(部分配置) |
| Kueue + TAS | False | admit時の1回(総量 + 配置) | 0枚 | Pod が 1 つも作られない |
| Kueue + Volcano | True | スケジューリングのたび | 4枚(占有したまま何も進まない) | 0 Running / 4 Pending |
注目すべきは Kueue + Volcano の行です。All or Nothing は守られますが、クォータ 4 枚を占有したまま Pod は 0 個で、その間ほかの Job はこのクォータを使えません。Kueue はクォータだけを見て admit し、Volcano は配置できないと判断します。2 つのツールが互いを知らないからです。TAS では判断が 1 つなので、この隙間がありません。
もう 1 つは「判断の時点」です。TAS は一度計算して nodeSelector で配置先を固定します。Volcano はスケジューラそのものなので判定はループになり、クラスタの状態が変われば追随します。admit のあとに状態が変わると、TAS では Pod が Pending のまま取り残されることがあります。
おわりに
今回はkind 環境の Fake GPU で 3 つの構成を比較しました。Kueue 単体ではクラスタ全体の総量しか見ないため、「admit されたのに配置できない」という状態が発生してしまうことが分かりました。
Kueue の TAS は、admit の判断にノード単位の配置検査を加えます。配置できない Job は admit されないので、クォータは空いたままです。追加のコンポーネントも要りません。ただし判断は admit 時の 1 回きりで、配置は nodeSelector で固定されます。スケジューリングレイヤーの機能ではないため、状態変化による隙間が生じうる「近似的(擬似的)な Gang Scheduling」と言えます。
Volcano は配置の実行そのものを担います。全 Pod がそろうかどうかをスケジューリングのたびに判定するため、クラスタの状態が変われば追随します。Kueue の管理外のワークロードにも適用できます。ただし Kueue の admit とは独立に動くため、クォータを押さえたまま何も進まない時間が生まれます。
Kueue は「いつ実行を始めてよいか」を、Volcano は「どこに配置するか」を担当します。この分担は TAS を入れても変わりません。TAS が変えたのは、admit の判断が配置まで見るようになったことでした。KueueとVolcanoは担当するレイヤーがそもそも異なります。どちらが優れているという話ではなく、適材適所で使い分けるのがよいでしょう。
【参考資料】
この記事をシェアしてください
