# KEP-2724: Topology Aware Scheduling
[Kubernetes Enhancement Proposal (KEP) 2724](https://github.com/kubernetes-sigs/kueue/blob/main/keps/2724-topology-aware-scheduling/README.md)。[[Kueue]](sig-scheduling 系のジョブキューイングコントローラ)の KEP。2024-07-30 作成、2026-08-19 時点で v0.19 系のリリースまで継続的に改訂が入っている「生きた」設計文書である。DRA の [[@2024__KEP__KEP-4381 Dynamic Resource Allocation with Structured Parameters]] がノードローカルなデバイス割当のオーケストレーション層を扱うのに対し、本 KEP はノード集合全体の**ネットワークトポロジ**に基づく配置制約を扱う、Kubernetes バッチスケジューリングのもう一つの拡張軸である。
## 動機:AI/ML ワークロードの Pod 間通信帯域
AI/ML の分散訓練・推論ワークロードは Pod 間で大量のデータを交換するため、同一ラック・同一ブロック内に Pod が収まっているかどうかで実行時間・コストが最大 2 倍変わりうる。しかし従来の Kueue には、ユーザーが「同一ブロック内にすべての Pod を配置してほしい」と要求する手段がなかった。Goal は (1) 配置制約を必須(required)として要求できること、(2) 配置制約を優先(preferred、満たせなければ自動的に緩和)として要求できること、(3) Cluster Autoscaler を ProvisioningRequest 経由でサポートすることの 3 点であり、MultiKueue 管理クラスタでの対応・ProvisioningRequest なしでの Cluster Autoscaler 対応・PodSet Slice の preferred トポロジは明示的に Non-Goal とされる。
## 階層表現とアドミン向け API
データセンターの階層構造(ラック・ブロック等)はノードラベルの集合として表現される。ラベル値はグローバルに一意である必要はなく、同じ "rack-1" ラベルでも異なる "block" に属していれば別ドメインとして扱われる。管理者は `Topology` CRD で `levels`(最大 8 レベルのノードラベルキーのリスト、最上位から最下位の順)を定義し、`ResourceFlavor.spec.topologyName` でこれを紐づける。TAS を使う ResourceFlavor は誤設定によるノード占有リスクを減らすため、最低 1 つのノードラベルを持つことが必須化されている。
## ユーザー向け API:PodTemplate アノテーション
ユーザーは PodTemplate に以下のアノテーションを付与して要求を表現する。
- `kueue.x-k8s.io/podset-required-topology` — 指定レベルのトポロジドメイン内に全 Pod を収めることを必須とする。満たせない場合はワークロードがキューで待機し続ける。
- `kueue.x-k8s.io/podset-preferred-topology` — 指定レベルから開始し、収まらなければ上位レベルへ段階的に緩和する。
- `kueue.x-k8s.io/podset-unconstrained-topology` — トポロジ制約なしで空き容量があれば admission する(TAS 全体の会計精度は活かしつつ配置は制約しない)。
- `kueue.x-k8s.io/podset-slice-required-topology` + `kueue.x-k8s.io/podset-slice-size` — PodSet を固定サイズのスライスに分割し、スライス単位でトポロジ要求を課す(JobSet の ReplicatedJob インスタンス単位の co-location などに使う)。
- `kueue.x-k8s.io/podset-slice-required-topology-constraints` — 上記スライスの多層拡張。coarsest から finest の順に `{topology, size}` の JSON 配列(最大 3 層)を指定し、"gang-of-gang-of-gang" 的な多層シンメトリー配置(例: datacenter → aizone 64台単位 → block 32台単位 → rack 16台単位)を GB200/GB300 のような複雑なトポロジ向けに表現する。
これら required/preferred/unconstrained は相互排他で、TAS 対象 ResourceFlavor 使用時に明示アノテーションがない PodSet は暗黙に unconstrained として扱われる(implicit defaulting、採用の摩擦を減らすための後付け仕様)。
## PodSetAssignment は常に最下位レベル単位
TAS は割当を必ずトポロジ階層の**最下位レベル**単位で確定させる。これは、block 単位で割り当てたワークロードが実際には 2 つの rack にまたがって kube-scheduler にバインドされ、その後 rack 単位で要求する別ワークロードが同じ rack に空き容量なしと判定される、というレース条件を防ぐための設計判断である。この制約は割当データ構造(`TopologyAssignment`)全体の設計を貫く前提になっている。
## 配置アルゴリズムとエンコーディング
Kueue はノードの空き容量をキャッシュし、`BestFit`(空き容量が多いドメインから選び最後のドメインだけ最適化)・`LeastFreeCapacity`(空き容量が少ないドメインから優先的に埋める)・`BalancedPlacement`(2 隣接レベルでのみ、選んだドメイン集合に Pod を均等分配)の 3 アルゴリズムを feature gate と PodSet アノテーションの組み合わせで選択する。v0.15 以降のデフォルトは `TASProfileMixed`(preferred/required は BestFit、unconstrained は LeastFreeCapacity)。BalancedPlacement は Allgather のような all-to-all 通信パターンで有効な、GPU 向けネットワーキングに特化した設計である。
`TopologyAssignment` の内部表現は v1beta1(ドメインごとに 1 エントリの `Domains` リスト)から v1beta2(`Slices` + 共通接頭辞/接尾辞の抽出による圧縮)へ刷新された。動機は etcd の 1.5MiB 単一オブジェクト上限であり、v1beta1 では現実的なノード命名規則で約 2〜3 万ノードが限界だったのに対し、v1beta2 の単一スライス圧縮で約 6 万ノード、ヒューリスティックなプレフィックスツリー剪定を伴う複数スライスで 10 万ノード超まで拡張できる。v0.19 で Beta 昇格した `TASAssignmentsEncodingByHostnamePrefix` フィーチャーゲートは、ホスト名の共通プレフィックス(`gke-c-pool-a-hash1-` のような `-` 区切り)を検出してスライスをグループ化するエンコーダで、ドメイン順序を保持しエンコード・デコードが可逆になるよう設計されている。
## ノード障害への対応
ノードが NotReady になる、または削除されると、専用コントローラが影響を受けた TAS ワークロードに情報を伝搬し、単一ノード障害かつプリエンプション不要な場合に限りスケジューラが新しい割当を探索して置換する(`kubernetes.io/hostname` を最下位レベルに持つトポロジでのみ機能)。マーキングのヒューリスティックは v0.19 で「固定時間 NotReady」方式から「Pod 終了確認」方式(`TASReplaceNodeOnPodTermination`)へ一本化された。前者は、ノードが後で Ready に復帰しても既に別割当が計算されてしまい per-node 容量会計が破損する原理的な問題を抱えていたためである。単一 Pod 所有(bare pod・Deployment レプリカ)のワークロードは、置換された割当を消費できる将来の Pod が存在しないため、`SkipReassignmentForPodOwnedWorkloads` フィーチャーゲート(v0.19 Beta)で再割当そのものをスキップし、Pod のライフサイクル(終了→コントローラによる新規 Pod 作成→新規 Workload として再 admission)に回復を委ねる設計に変わった。`NoExecute`/`NoSchedule` taint も v0.17 以降段階的にノード不健全のシグナルとして扱われるようになっている。
## ProvisioningRequest との統合(Cluster Autoscaler)
ProvisioningRequest AdmissionCheck が設定された ClusterQueue では、スケジューラは quota だけ予約して TAS 割当を保留し、AdmissionCheck が全て Ready になった時点で "second pass" として TAS 割当を計算する(`DelayedTopologyRequest` フィールドで待機状態を表現)。新規プロビジョニングされたノードは `ProvisioningRequest.status.provisioningClassDetails` から得られるノードセレクタでターゲティングされる。新規ノードが GPU ドライバインストール待ちなどで一時的に NotReady のままな場合、指数バックオフ(基準 10 秒、上限 5 分)で second pass を再試行し、上限超過でワークロードを evict する。
## スケジューリングサイクル内の再計算
Kueue の通常のスケジューリングは nomination フェーズ(各 Workload を独立に ResourceFlavor へ割当候補として評価)と evaluation フェーズ(順に処理し実際に admission)の 2 段階に分かれる。TAS は個々のノード単位で決定論的な配置(主に BestFit)を行うため、通常の quota スケジューリングよりもはるかに高い頻度で、nomination 時に fit と判定された Workload が evaluation 時には(先行 Workload にトポロジ容量を消費されて)fit しなくなる衝突が起きる。この衝突は多数の ClusterQueue を持つ環境で Workload が数時間 suspended のまま停滞する障害を実際に引き起こした。v0.19 で導入された `TASRecomputeAssignmentWithinSchedulingCycle` フィーチャーゲート(Beta、既定有効)は、quota 上は fit するが TAS 配置チェックで失敗した Workload について、PodSet→ResourceFlavor の割当は sticky に保ったまま TAS 割当だけをサイクル内で再計算することでこの障害を修正する。
## 出典
- kubernetes-sigs/kueue, "KEP-2724: Topology Aware Scheduling", `keps/2724-topology-aware-scheduling/README.md`, creation-date 2024-07-30, 参照日 2026-08-19。