這是《OpenShift AI 入門 30 天》系列的第 13 篇。
Hardware Profile 是平台用來定義「一份工作可以要多少資源」的物件——
CPU、記憶體、加速器,各自的最小值、預設值、上限。
使用者在 dashboard 上選 workbench 大小時,選單裡的選項就是這些 profile。
❖❖❖
這篇原本只打算講「怎麼設」。發文前我照自己寫在文末的檢查表去驗,
結果那個上限根本不擋。於是它變成兩個問題:
規則寫在哪裡,以及誰真的在執行它——而這兩個不是同一個東西。
⚠️ 2.x 叫 Accelerator Profile,3.x 改名並擴大了範圍(不只加速器)。
任何文件裡出現「Accelerator Profile」,那是 2.x 的材料。
我的 CRC 叢集裡沒有 GPU(CRC 是 VM,筆電上的卡沒有 passthrough 進去)。
所以這篇分兩半:規則與執行那條線(步驟一到三)全部是在這個叢集上撞出來的,
你沒有 GPU 也做得完;步驟四的 GPU 訊號我是在宿主機驗的,當方法看,別當配置抄。
(同樣的理由,lab repo
裡沒有 GPU 相關的 manifest。)
oc describe node 上看得到 nvidia.com/gpu 這類資源⚠️ 順序不能反。 節點上沒有那個資源,Hardware Profile 寫了也沒用——
pod 會 Pending,而畫面上不會說原因。
先確認節點認得卡:
oc get nodes -o json | jq -r '.items[] | .metadata.name,
(.status.allocatable | with_entries(select(.key|test("gpu"))))'
oc get hardwareprofile -A
# NAMESPACE NAME AGE
# opendatahub default-profile 5d
oc get hardwareprofile default-profile -n opendatahub -o jsonpath='{.spec}' | jq
{"identifiers":[
{"identifier":"cpu", "displayName":"CPU", "resourceType":"CPU",
"minCount":1, "defaultCount":2, "maxCount":4},
{"identifier":"memory", "displayName":"Memory", "resourceType":"Memory",
"minCount":"2Gi","defaultCount":"4Gi","maxCount":"8Gi"}]}
⚠️ displayName 每個 identifier 都要有,它是必填的——
下一節那份 YAML 我第一次就是漏了它被打回來。
三個數字各有各的意思:
| 欄位 | 意思 |
|---|---|
minCount |
使用者最少要拿這麼多(也是 requests 的下限) |
defaultCount |
沒選的話給這個 |
maxCount |
上限。這是真正在做治理的那個數字 |
apiVersion: infrastructure.opendatahub.io/v1 # ← 不是 v1alpha1,見下
kind: HardwareProfile
metadata:
name: gpu-small
namespace: opendatahub
spec:
displayName: "GPU Small(1 卡)"
identifiers:
- identifier: cpu
displayName: CPU # ← 必填,漏了會被 API server 打回來
resourceType: CPU
minCount: 2
defaultCount: 4
maxCount: 8
- identifier: memory
displayName: Memory
resourceType: Memory
minCount: "8Gi"
defaultCount: "16Gi"
maxCount: "32Gi"
- identifier: nvidia.com/gpu # ← 節點上的資源名稱要一模一樣
displayName: GPU
resourceType: Accelerator
minCount: 1
defaultCount: 1
maxCount: 1
漏掉 displayName 的話,錯誤長這樣(不會告訴你是哪個欄位語意上該填什麼):
* spec.identifiers[1].displayName: Required value
* spec.identifiers[2].displayName: Required value
⚠️ 關於 apiVersion:網路上(和我原本寫的)多半是 v1alpha1。
它還能用,但 API server 會回一句警告:
Warning: infrastructure.opendatahub.io/v1alpha1 is deprecated;
please use infrastructure.opendatahub.io/v1
而且存回來會變成 v1。 你寫進 git 的和讀出來的又對不起來了——
跟 Day 10 那個 RawDeployment → Standard 是同一件事。
⭐ 我的叢集裡沒有 GPU,但這個物件照樣建得起來。HardwareProfile 只是一份數字的宣告,它不驗證節點上有沒有那個資源。
這是分開的兩件事——真正會出問題的是用它的時候(pod Pending)。
驗證這一步:
oc get hardwareprofile -n opendatahub
然後去 dashboard 建 workbench,選單裡應該多一個「GPU Small」。
沒出現就是 namespace 錯了——profile 要放在平台的 namespace。
maxCount 到底會不會擋?寫這篇初稿時,我在最後的檢查表裡放了一句話:
第 5 項很多人不做,但沒被驗過的上限等於沒有上限。
發文前對帳,我照自己寫的去驗了。結果是:上限確實不存在。
❖❖❖
default-profile 的上限是 4 CPU / 8Gi。我送一個
16 CPU / 64Gi 的 workbench 進去:
oc apply -f oversize-notebook.yaml
# notebook.kubeflow.org/d13-oversize created
建起來了。 而且資源原封不動存進去:
oc get notebook d13-oversize -o jsonpath='{.spec.template.spec.containers[0].resources}'
{"limits":{"cpu":"16","memory":"64Gi"},"requests":{"cpu":"16","memory":"64Gi"}}
沒有被拒絕、沒有被改寫、沒有警告。
叢集上確實有 webhook 在管 notebook,我一個一個看:
oc get mutatingwebhookconfigurations -o json | jq -r '.items[]
| .metadata.name as $n | .webhooks[] | "\($n)\t\(.name)"' | grep -i hardware
workbenches-operator-... hardwareprofile-notebook-injector.opendatahub.io
它對所有 notebook 的 CREATE/UPDATE 生效(objectSelector 空、failurePolicy: Fail)。也就是說——我那份 16 CPU 的 YAML 確實經過它,
而它放行了。
因為它是 mutating(注入預設值),不是 validating(擋)。
那 validating 的呢?有一個,但:
dashboard-hardwareprofile-validator.opendatahub.io
apiGroups: [dashboard.opendatahub.io] ← 注意這裡
resources: [hardwareprofiles]
它管的是 dashboard.opendatahub.io 這個 group 底下的 hardwareprofiles。
而我們用的是 infrastructure.opendatahub.io。去問叢集那個 group 有什麼:
oc api-resources --api-group=dashboard.opendatahub.io
# NAME KIND
# odhapplications OdhApplication
# odhdocuments OdhDocument
沒有 hardwareprofiles。 這個 validator 指著一個已經不存在的資源
(旁邊那個 dashboard-acceleratorprofile-validator 更明顯,acceleratorprofiles 這個 CRD 在 3.x 根本已經拿掉了)。
maxCount 是選單範圍,不是護欄它決定 dashboard 上那個下拉選單能拉到多大。
任何繞過 dashboard 的路徑——oc apply、GitOps、pipeline——它都不在。
要真的擋,得用 k8s 自己的東西。這兩個我都實際撞過一次:
ResourceQuota:管一個 namespace 的總量oc create quota team-quota -n <ns> --hard=limits.cpu=2,limits.memory=4Gi
送一個要 4 CPU 的 pod 進去:
Error from server (Forbidden): pods "too-big" is forbidden: exceeded quota: team-quota,
requested: limits.cpu=4,limits.memory=8Gi,
used: limits.cpu=0,limits.memory=0,
limited: limits.cpu=2,limits.memory=4Gi
這才是「會擋」長什麼樣子:當場、明確、告訴你要多少、用了多少、上限多少。
跟上一節那個一聲不吭就收下 16 CPU 的對比很清楚。
同樣的 4 CPU,包在 Deployment 裡:
oc apply -f too-big-deploy.yaml
# deployment.apps/too-big-deploy created ← 成功
oc get deploy too-big-deploy
# NAME READY AVAILABLE
# too-big-deploy 0/1 0 ← 永遠不會好
oc get pods
# No resources found ← 一個 pod 都沒有
錯誤在 ReplicaSet 的 events 裡,而且它會一直重試:
Warning FailedCreate replicaset/too-big-deploy-84dcb4f947
Error creating: pods "...-blmtq" is forbidden: exceeded quota: team-quota, ...
Warning FailedCreate (combined from similar events): ... "...-gpfmf" is forbidden ...
workbench、InferenceService、pipeline 走的都是這條路——
使用者只會看到「一直起不來」,不會看到原因。oc get events 要成為反射動作。
LimitRange:管單一 container,順便給預設值spec:
limits:
- type: Container
max: { cpu: "1", memory: 1Gi }
default: { cpu: "200m", memory: 256Mi }
defaultRequest: { cpu: "100m", memory: 128Mi }
超過 max 的當場被拒(即使還在 quota 額度內):
Error from server (Forbidden): pods "over-limitrange" is forbidden:
[maximum cpu usage per Container is 1, but limit is 2,
maximum memory usage per Container is 1Gi, but limit is 2Gi]
而完全沒寫 resources 的 pod,會被塞進預設值——我送了一個空的進去,讀回來是:
{"limits":{"cpu":"200m","memory":"256Mi"},
"requests":{"cpu":"100m","memory":"128Mi"}}
這很重要:沒有 LimitRange 的話,沒寫 requests 的 pod 在排程器眼中是「不佔資源」,
於是節點會被塞爆。
| 管什麼 | 會擋嗎 | 錯誤看得到嗎 | |
|---|---|---|---|
HardwareProfile |
dashboard 選單的範圍 | ❌ | — |
LimitRange |
單一 container 的上下限+預設值 | ✅ | 直接建 pod 時當場看到 |
ResourceQuota |
一個 namespace 的總量 | ✅ | 同上;經過 controller 就只在 events |
平台給了你一個地方寫下規則,執行規則的人要自己從 k8s 那邊接過來。
Q:CPU 上跑 LLM 可行嗎?
A:小模型可以(我 lab 就是),但吞吐量差一到兩個數量級。
拿來學習和驗證流程完全夠,拿來評估效能會得到錯的結論。
⚠️ 上面寫的 v3.5.0 是這台叢集長期以來的版本;本篇所有指令與輸出是在自動升級後的 3.6.0-ea.1 上重跑的(升版經過寫在 Day 5 的補記)。
opendatahub-operator.v3.5.0(即 RHOAI 3.x 的上游開源版)、cert-manager-operator.v1.20.0(3.x 的必要相依,2.x 不需要)kserve、aipipelines、dashboard、workbenches、modelregistry
⚠️ ODH ≠ RHOAI:元件同源,但 namespace 與部分名稱不同
(這裡是 opendatahub,商用版是 redhat-ods-*)。指令邏輯可照用,字串要自己對一次。
你們叢集上的資源上限,有人實際撞過一次確認它會擋嗎? 留言或到原文留言都可以,我會回。