模型會換,而且比程式碼換得頻繁。
如果換一個權重檔要走完整套「重 build image → 推 registry → 掃描 → 部署」,
兩週換一次還好,每週換兩次就受不了。
Day 10 講過的 STORAGE_URI 就是為了這個:權重不進 image,換模型只是換 S3 上的檔案。
| 換法 | 停機 | 能回滾 | 有核准紀錄 | 適合 |
|---|---|---|---|---|
A 改 STORAGE_URI |
短暫 | 改回去 | ❌ | lab、內部服務 |
| B 建新 ISvc + 切 Route | 無 | 切回去 | ❌ | 需要零停機時 |
| C canary 流量分配 | 無 | 調比例 | ❌ | 要漸進驗證 |
| D 改 git,讓 Argo 部署 | 依底下用 A 或 B | revert commit | ✅ | 正式環境 |
⚠️ 注意最後一欄。 A、B、C 都是某個人在終端機打指令——
做得到,但沒有任何東西記得是誰、什麼時候、根據什麼做的。
正式環境要的是 D,而 D 底下跑的還是 A 或 B。
最直接的:
oc patch isvc my-model --type=merge \
-p '{"spec":{"predictor":{"model":{"storageUri":"s3://models/my-model/v3/"}}}}'
KServe 會滾動更新 pod,新 pod 的 storage-initializer 抓新版。
⚠️ 只改 S3 上的檔案、不改 URI 是不會生效的——storage-initializer 只在 pod 啟動時跑一次,要刪 pod 才會重來。
而「同一個路徑換內容」這種做法要盡量避免:它讓 Day 16 講的血緣鏈直接斷掉,
而且你會有一個顯示正常、內容早就換過的服務。每一版一個路徑。
# 舊的留著,新開一個
metadata:
name: my-model-v3
spec:
predictor:
model:
storageUri: s3://models/my-model/v3/
新的起來、驗過之後才切:
oc patch route my-model --type=merge \
-p '{"spec":{"to":{"name":"my-model-v3-predictor"}}}'
好處是回滾只要把 Route 切回去,舊的一直都在;代價是同時跑兩份,資源要兩倍。
KServe 支援按比例分流:
spec:
predictor:
model:
storageUri: s3://models/my-model/v3/
canaryTrafficPercent: 10
10% 的請求打到新版。
⚠️ 兩個要先想清楚的:10% 的流量要累積多久才有結論(統計問題,低頻服務可能要好幾天);
以及你要用什麼指標判斷新版比較好——延遲和錯誤率看得到,
「預測比較準」要等真實標籤回來,那可能是幾個月後。
canary 對前者有效,對後者無效。 別把它當成模型品質的驗證機制。
這是正式環境的做法,前三種是它底下的實作。
git 上只改兩行——storageUri 指到新版路徑、model/sha256 換成新的 digest——
開 PR、有人 review、merge,Argo 同步。
換到的東西是紀錄:誰核准=PR 的 approver、回滾=git revert、
上線前檢查=PR 上的 CI。
⚠️ 界線:Argo 保證「叢集上的宣告 == git 上的宣告」,
但權重在 S3 不在 git——有人換掉 S3 上的檔案,Argo 全綠而服務已經變了。
下面那些檢查照樣要做。 完整寫法在 Day 25。
這是本篇最重要的一節。 我為了寫它真的換了一次,
第一次就失敗了——而且四個檢查點全是綠的。
lab 上有兩個模型:線上的 v1(32 MB)和 gate 放行過但沒人上線的候選(5.5 MB)。
我把候選複製到一個有版本的路徑 s3://models/llm-v2/,然後改 STORAGE_URI。
storage-initializer 下載成功——1.43 秒,兩個檔案。
Found S3 object: llm-v2/ckpt.pt (5784727 bytes)
Found S3 object: llm-v2/tokenizer.json (37097 bytes)
Successfully copied s3://models/llm-v2/ to /mnt/models
Model downloaded in 1.4338307090074522 seconds.
然後服務起不來。
File "/app/serve/app.py", line 95, in _load_model
drift=DriftMonitor.from_artifacts(ART),
FileNotFoundError: [Errno 2] No such file or directory: '/mnt/models/clean_corpus.txt'
新版本的路徑少了一個檔案——而且不是模型檔,是漂移監控用的基準語料。
我複製權重的時候只想到「模型」,沒想到「這個服務啟動需要什麼」。
教訓比我原本寫的那條更嚴格:不是「每一版一個路徑」,
是每一版的路徑要裝得下那一版服務啟動所需要的全部東西。
| 你會看的 | 顯示 | 真相 |
|---|---|---|
oc get isvc |
READY True | 舊 pod 還在服務 |
| ISvc 的 conditions | Ready / PredictorReady / IngressReady 全 True | 同上 |
| 打 API | 正常回應 | 回的是舊版 |
oc get deploy |
READY 1 / UPDATED 1 / AVAIL 1 / DESIRED 1 | 這幾欄各自數的不是同一個 pod |
最後一列最陰險:UPDATED 1 數的是正在 CrashLoop 的新 pod,READY 1/AVAIL 1 數的是還在服務的舊 pod。每一欄都沒說謊,合起來卻是假的。
只有兩個訊號抓得到:
# ① 有兩個 pod,而且一個沒 ready
oc get pods -l serving.kserve.io/inferenceservice=<name> -o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,RESTARTS:.status.containerStatuses[0].restartCount'
# llm-scratch-predictor-6786d546b4-5tfgh false,true 6 ← 新的,重啟 6 次
# llm-scratch-predictor-868c589678-6k9cm true,true 1 ← 舊的,還在服務
# ② 服務回報的 digest 沒有變
補上缺的檔案、刪掉 pod 讓它重抓:
| 動作 | 實測 |
|---|---|
| 換版 rollout(5.5 MB 權重) | 13 秒 |
| 回滾 rollout(32 MB 權重) | 14 秒 |
storage-initializer 下載 5.8 MB |
1.43 秒 |
| 這期間的服務中斷 | 0(見下) |
我第一次補完檔案之後,是用 oc delete pod -l <selector> 讓它重抓的。
那一條指令把新舊兩個 pod 都刪掉了,服務真的斷了。
正確做法是什麼都不刪,只 patch。 因為 KServe 產的 Deployment 本來就有滾動策略:
oc get deploy <name>-predictor -o jsonpath='{.spec.strategy}'
# {"rollingUpdate":{"maxSurge":"25%","maxUnavailable":"25%"},"type":"RollingUpdate"}
replicas: 1 時 k8s 這樣換算:maxSurge 25% → 進位成 1,maxUnavailable 25% → 捨去成 0。
也就是說:新 pod 必須先 Ready,舊 pod 才會被收掉。零停機是預設行為。
我重做了一次來驗證——patch 之後什麼都不碰,同時開一個探針每 0.3 秒打一次 /health,
連續 150 秒,中間完成一次換版加一次回滾:
RESULT ok=465 fail=0
465 次請求,0 次失敗。
所以真正該注意的是:
| 情況 | 做法 |
|---|---|
| 一般換版 | 只 patch,什麼都不刪——滾動更新會處理 |
| 新 pod CrashLoop、修好了要它重試 | 只刪那個新的 pod(oc delete pod <新pod名>),不要用 label selector |
| 只改了 S3 上的內容、URI 沒變 | 這時才需要刪 pod——而且要一個一個刪 |
⚠️ oc delete pod -l <selector> 在單副本服務上就是一次停機。
這個指令常被當成「讓它重讀設定」的萬用招,但它不區分新舊,
會把正在服務的那個一起帶走。(副本數拉到 2 以上就連刪錯都還有另一個頂著。)
換到 v2 之後打服務的 /model:
{"serving_digest":"sha256:bb614beb…", "in_registry":false,
"status":"UNREGISTERED", "metrics":null}
digest 換對了,但這個模型不在台帳上——因為我是手動 promote 的,跳過了註冊。
如果這是正式環境,現在沒有人答得出「線上這版的評估指標是多少」。
回滾之後才變回 "status":"production" 加上完整 metrics。
手動換版最貴的代價不是停機,是斷掉證據鏈。
前三個上面的實測已經示範過了,這裡只列指令:
# ① 只剩一個 pod,而且是新的
oc get pods -l serving.kserve.io/inferenceservice=my-model \
-o custom-columns='NAME:.metadata.name,READY:.status.containerStatuses[*].ready,AGE:.metadata.creationTimestamp'
# ② 權重檔對(時間戳與大小)
oc exec <pod> -c kserve-container -- ls -la /mnt/models
# ③ digest 對
oc exec <pod> -c kserve-container -- sha256sum /mnt/models/ckpt.pt
⚠️ 第 ② 項要看的是整個目錄,不是只看模型檔——
我今天就是因為少複製了一個非模型的檔案,服務起不來。
第 ④ 個是新的:
在你的服務上開一個回報身分的端點,像上面那個 /model:
一次回答三題——跑哪一版、在不在台帳上、那一版的指標是多少。
⚠️ 這是你要自己寫的,平台不會給你。
但它是我今天唯一一眼就看出「換成功了但沒登記」的訊號。
如果只能加一個東西到模型服務裡,我會加這個。
| 檢查 | 通過條件 | |
|---|---|---|
| 1 | 只剩一個 pod,而且是新的 | ⚠️ 兩個 pod 就是沒換成功,不管 ISvc 說什麼 |
| 2 | ❌ 這條沒有用——換版失敗時它也是 True | |
| 3 | 權重檔對 | ls -la /mnt/models 時間戳與大小 |
| 4 | digest 對 | sha256sum 等於預期 |
| 5 | 服務自報的身分對 | /model 之類的端點(檢查四) |
| 6 | 在台帳上 | ⚠️ 換對了不代表登記了 |
| 7 | 回滾演練過 | ⭐ 見下 |
第 7 項:在你需要回滾之前,先回滾一次。
換版當下就是最好的演練時機——新版剛上、舊版還在、你人還在。
沒演練過的回滾等於沒有回滾。
我今天做完換版就立刻回滾了一次,15 秒,digest 與台帳狀態都回到原樣。
知道這個數字,跟猜「應該很快吧」是兩件事。
Q:換版要不要重跑資安掃描?
A:image 沒變就不用重掃 image。但模型檔本身該不該掃是另一個問題——
pickle 格式是可執行內容,資安審會問。
Q:能不能自動換?資料更新就自動重訓上線?
A:技術上可以。但「自動上線」要先有自動的品質閘門,
否則你只是把出錯的速度加快了。Day 28 專講這個。
Q:舊版本要留多久?
A:至少留到你確定不用回滾為止。S3 上的舊權重很便宜,留著。
Q:怎麼確認換版真的沒有斷線?
A:自己開探針量,不要相信「應該不會斷」。 我用的方法:
# 在叢集內起一個 pod,每 0.3 秒打一次 /health,數成功與失敗
oc run probe --image=curlimages/curl --restart=Never -- sh -c '
ok=0; fail=0; end=$(( $(date +%s) + 150 ))
while [ $(date +%s) -lt $end ]; do
curl -fsS -m 2 http://<svc>/health >/dev/null 2>&1 && ok=$((ok+1)) || { fail=$((fail+1)); echo FAIL; }
sleep 0.3
done
echo "RESULT ok=$ok fail=$fail"'
這也是驗收清單上該有的一條——「換版不停機」如果沒量過,它就只是個說法。
Q:換版之後監控的基準要跟著換嗎?
A:要,而且很容易忘。用舊版基準看新版會噴一堆假警報,
然後大家就開始忽略告警。Day 29 會講。
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-*)。指令邏輯可照用,字串要自己對一次。
=
你們換模型版本要走什麼流程?跟換程式一樣嗎? 留言或到原文留言都可以,我會回。