Kueue:CRD 說可以,webhook 說不行
1. 這是什麼
Kueue 是 Kubernetes 的工作排隊器。
一般的 k8s 排程是「有資源就跑,沒資源就 Pending」。 Kueue 多了一層:先排隊,輪到你而且資源夠了才送進去跑。
Java 類比:
ThreadPoolExecutor的那個 queue。 沒有它,每個任務都直接搶執行緒;有了它,任務先排隊, 而你可以決定誰先誰後、每個團隊最多能佔多少。
在 AI 平台上這件事特別重要,因為訓練工作又大又長—— 一個沒排隊機制的叢集,先送出的大任務會把 GPU 全部佔住, 後面的人只能等,而且不知道要等多久。
2. 什麼時機需要它
當「誰能用 GPU」開始需要規則的時候。
具體的訊號:
- 有人抱怨「我的訓練排不進去」,而你查不出是誰佔住的
- 兩個團隊共用一批 GPU,開始為了先後順序吵架
- 有人一次送十個任務把卡佔滿,其他人整天做不了事
如果你只有一個團隊、GPU 也只有一兩張——ResourceQuota 就夠了,
不需要 Kueue。 排隊機制的價值在「多方競用」,不在「資源不夠」。
3. ⭐ 怎麼用:以及它為什麼一開始就擋你
照直覺,開一個元件就是把它設成 Managed:
oc patch dsc default-dsc --type=merge \
-p '{"spec":{"components":{"kueue":{"managementState":"Managed"}}}}'
Error from server (Forbidden): admission webhook
"datasciencecluster-v2-validator.opendatahub.io" denied the request:
Managed is no longer supported as a managementState
「不再支援」——但它沒告訴你該用什麼。
而更混亂的是,去看 CRD 的 schema:
oc get crd datascienceclusters.datasciencecluster.opendatahub.io -o json | \
jq -r '.spec.versions[] | select(.name=="v2") |
.schema.openAPIV3Schema.properties.spec.properties.components.properties
| to_entries[] | "\(.key)\t\(.value.properties.managementState.enum)"'
kueue ["Managed","Unmanaged","Removed"] ← Managed 還在 enum 裡
kserve ["Managed","Removed"]
aipipelines ["Managed","Removed"]
...
CRD 的 enum 裡明明列著 Managed。
所以你會遇到這個狀況:照 schema 寫是合法的,但 webhook 會擋。
正解是 Unmanaged
oc patch dsc default-dsc --type=merge \
-p '{"spec":{"components":{"kueue":{"managementState":"Unmanaged"}}}}'
# patched
Unmanaged 的意思是:「我要用 Kueue,但 operator 不負責裝它,我自己裝。」
在 RHOAI 上,你要另外裝 RHBOK(Red Hat Build of Kueue)這個獨立 operator。
設完之後:
KueueReady False PreConditionFailed: pre-conditions not met
這是正確的狀態——它在說「你選了 Unmanaged,但我還沒看到你裝的那個 Kueue」。
4. ⭐ 這件事真正的教訓:兩層驗證會不一致
Kubernetes 的資源驗證有兩層:
| 層 | 誰做 | 依據 |
|---|---|---|
| ① Schema 驗證 | API server | CRD 的 OpenAPI schema(enum、required、型別) |
| ② Admission webhook | operator 自己寫的程式 | 任意邏輯 |
第二層可以拒絕第一層允許的東西,而且兩層可能不同步—— CRD schema 是宣告在 YAML 裡的,webhook 是程式碼, 改了程式碼忘了更新 schema,就會出現這種情況。
對你的實際影響
你不能只靠 oc explain 或 CRD schema 來決定怎麼寫 YAML。
oc explain dsc.spec.components.kueue.managementState
# 會告訴你可以填 Managed —— 而那是錯的
唯一可靠的驗證是 --dry-run=server:
oc apply -f my-dsc.yaml --dry-run=server
--dry-run=server 會真的送到 API server 跑完整個 admission 流程
(包含所有 webhook),只是不寫進 etcd。
⚠️ 注意不要用 --dry-run=client——那個只做本機的 schema 檢查,
完全不會碰到 webhook,也就抓不到這個問題。
這一條可以直接寫進你的變更流程: 所有要進正式環境的 YAML,先在測試叢集
--dry-run=server過一次。 那比人工 review 有效得多,因為它跑的是真正會擋你的那段程式。
5. 關鍵指標
| 「跑完了」 | ⭐「做對了」 | |
|---|---|---|
| Kueue | KueueReady=True |
送一個超過配額的任務,它真的排隊而不是直接跑 |
第二欄跟這個系列其他篇是同一個模式:排隊機制的價值在它會擋東西。 沒擋過任何東西的排隊器,跟沒有排隊器一樣。
6. 什麼時候不需要它
- 單一團隊、GPU 不需要搶
- 用
ResourceQuota就能表達你的規則(例如「這個 namespace 最多兩張卡」)
Kueue 解決的是「順序」與「公平」,不是「上限」。
如果你要的只是上限,ResourceQuota 更簡單而且不用多裝一個 operator。
7. 我沒驗過的部分
我沒有實際裝 RHBOK 也沒有跑過排隊——我的叢集只有一個節點、 沒有 GPU、CPU 還已經用到 99%,跑不了有意義的排隊實驗。
所以這篇能給你的是:怎麼開、為什麼被擋、正解是什麼, 以及那個兩層驗證的教訓。排隊行為本身我沒有證據。
你們有在用 Kueue 或其他排隊機制嗎?還是靠 namespace quota 硬擋?
🧪 這篇的實驗環境與 lab 檔案(最後更新 2026-08-30)
叢集
- CRC 2.63.0 · OpenShift 4.22.7 · Kubernetes v1.35.6
- 單節點:13 vCPU / 40 GiB RAM / 120 GB disk
- 宿主:Framework Laptop 16(Ryzen AI 7 350 · 8C/16T · 64 GB RAM · 1 TB NVMe · RTX 5070 顯卡模組)
- ⚠️ 叢集內看不到 GPU(CRC 是 VM,RTX 5070 未 passthrough)
Operator
opendatahub-operator.v3.5.0← 即 RHOAI 3.x 的上游開源版cert-manager-operator.v1.20.0(3.x 的必要相依;2.x 不需要)openshift-pipelines-operator-rh.v1.23.2、openshift-gitops-operator.v1.21.3
DataScienceCluster 開啟的元件
kserve、aipipelines、dashboard、workbenches、modelregistry、kueue(Unmanaged)- 其餘(
ray/trustyai/feast/aigateway…)為Removed
叢集外的依賴(跑在宿主的 podman 上,crc start 不會帶起來)
- Harbor v2.15.2(私有 registry)· MinIO(S3)
模型端
- Python 3.12.13 · PyTorch 2.11.0+cu128 · FastAPI + uvicorn · prometheus-client
lab 檔案
- YAML/Containerfile/腳本:github.com/ryanGTR/openshift-ai-30days
(含 Day 對照表;主機名是佔位符,跑
set-lab-host.sh換成你自己的) - 服務的那個模型:llm-from-scratch(從零手刻的小 GPT)
⚠️ ODH ≠ RHOAI:元件同源,但 namespace 與部分名稱不同
(我這裡是 opendatahub,商用版是 redhat-ods-* 那一套)。
指令的邏輯可以照用,字串要自己對一次。
留言與指正
我特別想知道:你們有在用 Kueue 或其他排隊機制嗎?還是靠 namespace quota 硬擋?