Day 8:Workbench——把 notebook 搬到叢集上
這是什麼、解決什麼問題
Workbench = 跑在叢集上的 JupyterLab。
聽起來只是換個地方開 notebook,但換了地方之後這幾件事就變了:
| 筆電上的 notebook | Workbench | |
|---|---|---|
| 資料 | 要先下載一份 | 直接讀叢集裡的儲存 |
| GPU | 有就有,沒有就沒有 | 跟叢集要 |
| 環境 | 每個人不一樣 | 平台給的 image,大家一樣 |
| 資料落地 | 在你筆電上 | 在叢集裡 |
| 你關機之後 | 停了 | 還在跑 |
金融業通常是為了倒數第二列才導這個東西。 「資料不落地」寫在規範裡的時候,人在哪裡寫 code 就不是個人偏好問題。
底層是一個叫 Notebook 的 CR。它會展開成 StatefulSet + PVC + Service,
對外那一段則跟版本有關——3.x 不再給每個 workbench 開 Route,
而是在 opendatahub namespace 裡建一條 Gateway API 的 HTTPRoute:
oc get httproute -n opendatahub | grep '^nb-'
# nb-llm-serve-demo-my-workbench
# nb-llm-serve-demo-ryan-workbench
(命名是 nb-<namespace>-<workbench 名>。照舊文件去 oc get route 找會找不到。)
什麼時候你會用到
- 資料不能下載到個人設備
- 要用 GPU,而 GPU 在叢集裡
- 想要「大家的環境一樣」,不要再處理「我這邊跑得動啊」
- 訓練要跑幾小時,不想開著筆電
不需要的時候:只是寫寫 code、資料是公開的、也不用 GPU。 那 workbench 只是讓你多按幾次滑鼠。
前置條件
- ODH/RHOAI 裝好,DSC 裡
workbenches是Managed - 一個 Data Science Project(就是一個 namespace)
- 想接資料的話:一份 Connection
步驟一:看有哪些 image 可以選
平台預先準備好一組 notebook image,以 ImageStream 的形式放著:
oc get is -n opendatahub | grep -E 'jupyter|code-server'
ODH 上有 11 個(3.5 和 3.6 都是這 11 個):
| image | 裡面有什麼 |
|---|---|
jupyter-minimal-notebook |
最小,只有 Jupyter |
jupyter-datascience-notebook |
pandas / sklearn / matplotlib |
jupyter-pytorch-notebook |
PyTorch(CPU/CUDA) |
jupyter-tensorflow-notebook |
TensorFlow |
jupyter-minimal-gpu-notebook |
最小 + CUDA |
jupyter-rocm-* |
AMD GPU 版(minimal / pytorch / tensorflow) |
jupyter-trustyai-notebook |
加了 TrustyAI(偏誤與可解釋性) |
jupyter-pytorch-llmcompressor |
模型壓縮/量化 |
code-server-notebook |
不是 Jupyter,是 VS Code |
最後一個常被忽略。習慣 IDE 的人給 Jupyter 會很痛苦, 有 VS Code 這個選項這件事本身就值得先講。
每個 image 有多個版本標籤(3.5、3.6):
oc get is jupyter-datascience-notebook -n opendatahub \
-o jsonpath='{range .spec.tags[*]}{.name}{"\n"}{end}'
⚠️ 選版本不是選新的就好。 平台升級時舊 tag 可能被移除, 你的 workbench 會起不來。正式用途要把用到的 image 版本記下來, 跟記 base image 版本是同一件事。
步驟二:開一個
Dashboard → 你的專案 → Workbenches → Create workbench。要填的是:
| 欄位 | 怎麼決定 |
|---|---|
| Image | 上一節那張表 |
| Container size | Small / Medium / Large(這是 requests/limits) |
| Accelerator | 要 GPU 才選 |
| Cluster storage | PVC 大小(表單會帶一個預設值,建之前看一眼;我這台上手動建的兩個是 10Gi 和 2Gi) |
| Connections | 勾了就整包注入環境變數 |
Container size 那格最容易出事。 它對應的是 CPU 和記憶體的 requests/limits。單節點叢集上選 Large,可能就直接 Pending, 而且畫面上不會說是為什麼:
oc get pods -n <ns> | grep <workbench 名>
# NAME READY STATUS ...
# my-workbench-0 0/1 Pending
oc describe pod <pod> | tail -5
# Warning FailedScheduling ... Insufficient cpu
驗證這一步:
oc get notebooks -n <ns>
oc get pods -n <ns> -l notebook-name=<workbench 名>
⚠️ 選擇器要用 notebook-name,不要用 app。
app 這個 label 在 CR 上有、在 pod 上沒有,
-l app=<名字> 會回你 No resources found,看起來像 workbench 沒建起來:
oc get pods -n llm-serve-demo -l app=my-workbench
# No resources found in llm-serve-demo namespace. ← 其實它好好地跑著
oc get pods -n llm-serve-demo -l notebook-name=my-workbench
# my-workbench-0 1/1 Running
⚠️ 幾個容器算「起來了」跟版本有關。
舊版會在 pod 裡塞一個 OAuth proxy 側車,所以要看到 2/2;
3.x 把驗證收到 gateway(openshift-ingress 裡的 kube-auth-proxy)之後,
我這台上兩個 workbench 都是 1/1 Running——
即使其中一個的 notebooks.opendatahub.io/inject-oauth 還寫著 true。
別背數字,看你自己叢集上正常的那個長什麼樣:
oc get pod <workbench>-0 -o jsonpath='{range .spec.containers[*]}{.name}{"\n"}{end}'
步驟三:接資料
方法一:Connection(推薦) 建立時勾選,環境變數就注入好了:
import os, boto3
s3 = boto3.client("s3", endpoint_url=os.environ["AWS_S3_ENDPOINT"],
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"])
方法二:PVC 資料很大、或本來就在企業儲存上時用這個。掛進來就是一個目錄。
⚠️ 兩者的差別不只是方便: S3 上的物件有版本、有存取紀錄;PVC 裡的檔案誰改了都不會留下痕跡。 要做資料血緣的話,這個差別是關鍵的。
步驟四:⭐ 存檔的位置
只有 PVC 掛載的那個目錄會留下來。
⚠️ 那個目錄是哪個,不要用猜的。 從 dashboard 建的通常掛在
/opt/app-root/src(JupyterLab 的預設目錄),
但手寫 YAML 建的很可能掛在別的地方。看 CR 最準:
oc get notebook <name> -n <ns> \
-o jsonpath='{range .spec.template.spec.containers[0].volumeMounts[*]}{.mountPath}{"\n"}{end}'
我這台上兩個 workbench 就掛在不同地方——
ryan-workbench(dashboard 建的)在 /opt/app-root/src,
my-workbench(我自己寫 YAML 建的)在 /opt/app-root/src/work。
在 notebook 裡直接看,一眼分辨:
!df -h /opt/app-root/src /opt/app-root/src/work
Filesystem Size Used Avail Use% Mounted on
overlay 120G 100G 21G 84% / ← 這層是暫時的
/dev/vda4 120G 100G 21G 84% /opt/app-root/src/work ← 這層才是 PVC
看到 overlay 就是寫在容器裡,pod 一重啟就沒了。
上面這個例子特別值得記:/opt/app-root/src 看起來像家目錄、
JupyterLab 一開也是停在這裡,但它在這台 workbench 上不是 PVC。
而 workbench 會因為閒置自動停止,這是預設行為—— 所以「pod 重啟就沒了」不是理論上的風險,是每天都會發生的事。
還有:pip install 裝的東西不會留下來。 那些進的是 image layer,
不是 PVC。要固定環境有兩條路:
requirements.txt放在 PVC 裡,每次開起來自己裝(簡單,但每次要等)- 自己 build 一個 notebook image(正式做法,也是可審計的那條路)
怎麼確認做對了
| 檢查 | 怎麼看 | |
|---|---|---|
| 1 | pod 起來了(容器數看你的版本) | oc get pods -l notebook-name=<name> |
| 2 | 打得開 | Dashboard 按 Open |
| 3 | 資料讀得到 | notebook 裡真的 list 一次 bucket |
| 4 | 檔案存得住 | 存檔 → 停掉 → 再開 → 檔案還在 |
| 5 | GPU 真的拿到(若有) | 見下方 |
第 4 項要真的做一次。很多人是在丟了三小時的工作之後才知道自己寫錯目錄。
GPU 的驗法——nvidia-smi 有卡不代表你的模型在用卡:
import torch; print(torch.cuda.is_available(), torch.cuda.device_count())
兩個都對才算數。
常見問題
Q:建 workbench 一直失敗,訊息看不懂。
A:ODH 3.5.0 有一個 admission webhook 會擋掉部分 Notebook 建立。
先看 oc get events -n <ns> --sort-by=.lastTimestamp | tail。
細節和我的處理方式寫在
這篇。
那組 webhook 在 3.6 上還在,名字可以自己查:
oc get validatingwebhookconfigurations,mutatingwebhookconfigurations \
| grep notebook
# odh-notebook-controller-validating-webhook-configuration
# odh-notebook-controller-mutating-webhook-configuration
Q:關掉瀏覽器,訓練會停嗎? A:不會,pod 還在跑。但閒置逾時會把 workbench 停掉(預設有這個機制), 跑很久的訓練建議別放 notebook,改用 pipeline。
Q:可以裝自己的套件嗎?
A:可以,但只活到 pod 重啟。要長久就自己 build image。
離線環境更是只有這條路——pip install 出不去。
Q:多人可以用同一個 workbench 嗎? A:技術上可以(給 Route 權限),但不要。 每個人一個,資源歸屬和存取紀錄才對得起來。
你們資料科學家現在在哪裡寫 code?自己筆電、共用機器,還是平台上?
🧪 這篇的實驗環境與 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-* 那一套)。
指令的邏輯可以照用,字串要自己對一次。
留言與指正
我特別想知道:你們資料科學家現在在哪裡寫 code?自己筆電、共用機器,還是平台上?