Day 27:綠燈不等於做完
這是什麼、解決什麼問題
假綠 = 檢查點顯示成功,但事情沒有發生。
它比失敗危險,因為失敗會叫你,假綠不會。 你會在幾個月後、通常是在別人問你的時候,才發現。
這篇把我在自己 lab 上撞到的五種整理出來—— 它們有共同的結構,認得那個結構,就能自己看出新的。
什麼時候你會用到
- 設計驗收清單(Day 26)
- 檢討「為什麼這個問題沒被抓到」
- 每次你要寫一個檢查點的時候
形態一:看 log,不看產物
pipeline 印了 ALL DONE,退出碼 0,UI 上綠色。
但那個 step 的訓練根本沒跑。
因為 ALL DONE 是程式最後一行 print,
它證明程式跑到最後一行,不證明中間做了什麼。
檢查要改成:去看產物存不存在、大小合不合理、時間戳對不對。
# ❌ 看這個
oc logs <pod> | grep "DONE"
# ✅ 看這個
aws s3 ls s3://models/output/ --recursive
形態二:看結果,不看門檻是誰填的
品質閘門顯示「通過」。但門檻是這次自己填進去的。
threshold = current_score - 0.01 # ❌ 永遠會過
這種寫法看起來很合理(「不要比上次差太多」), 但它讓每一次都合格,只要退步得夠慢。
檢查要改成:門檻的來源是什麼?誰核定的?什麼時候改的? 能不能在這次執行中被改?
形態三:檢查的範圍小於系統的範圍
你檢查了 A、B、C 三個元件,全綠。 而系統其實有 A 到 F 六個。
這種最難發現,因為檢查本身是對的,只是不完整。
檢查要改成:從系統盤點出發,不從既有的檢查清單出發。
# 系統上實際有什麼
oc get pods -A --field-selector status.phase=Running -o name | wc -l
# 你的清單涵蓋幾個
形態四:檢查在資料層,壞在呈現層
資料是對的、API 查得到、資料庫裡也有。 但畫面上是空白的。
我撞到的版本是:Prometheus 有資料,
Grafana 九個面板全空——因為 datasource 的 uid 沒指定,
Grafana 自己生了一個,而 dashboard JSON 裡寫死的是 prometheus。
⚠️ 我原本寫「隨機的」,那是錯的(Day 17 對帳時查清楚了): 那個 uid 是從 datasource 的名字算出來的,同一個名字每次都一樣。
這個差別會改變你的處置方式:
| 如果是 | 那你要 |
|---|---|
| 隨機 | 每次部署後去查一次,寫進 dashboard |
| 從名字算出來(實際) | 可以事先算好寫死,或乾脆在 datasource 裡明確指定 uid |
「查一次」和「可以固定」是兩種完全不同的維運成本。
檢查要改成:用眼睛看最終的呈現。 API 通不代表使用者看得到。
形態五:寫入成功、讀得回來、內容是空的
API 回 201 Created,GET 回來也有那個物件。 但你送的欄位不見了。
原因是 API 對不認識的欄位不報錯,直接忽略—— 我用 camelCase 送了幾個欄位,它期待的是別的形式, 於是我得到一個成功建立、但少了一半資料的紀錄。
檢查要改成:寫進去之後讀回來比對內容,不只比對存在。
resp = create(payload)
got = get(resp["id"])
assert got["author"] == payload["author"] # ← 這一行
五種的共同結構
每一種都是同一件事:檢查的對象,比你以為的淺一層。
| 形態 | 你檢查了 | 應該檢查 |
|---|---|---|
| 一 | 程式跑完 | 產物存在 |
| 二 | 有沒有通過 | 門檻是誰定的 |
| 三 | 清單上的 | 系統上的 |
| 四 | 資料在 | 使用者看得到 |
| 五 | 物件在 | 內容對 |
左欄都是「有沒有發生」,右欄都是「發生的是不是我要的」。
認得這個結構之後,寫任何一個檢查點都可以自問: 我檢查的是「有沒有」,還是「對不對」?
怎麼把它變成可操作的規則
寫檢查點的時候,套這三個問題:
- 這個訊號,在事情沒發生的時候會不會也出現? 會 → 換一個訊號。
- 這個判定的標準,能不能被被檢查的東西自己改? 能 → 標準要外部化。
- 這個檢查,涵蓋了系統的全部還是一部分? 一部分 → 說清楚哪一部分。
⭐ 一個誠實的補充
我把 lab 的檢查寫成一支腳本(37 個檢查點)跑第一次,
顯示 5 個失敗。查下去,其中 4 個是腳本自己的錯——
jsonpath 在雙引號裡的巢狀引號沒跳脫,還有一個是
oc logs deploy/X 只讀了三個 pod 中的一個。
這也是一種假綠的反面:假紅。 而它們的成因一樣——你以為你在檢查 A,實際上檢查的是別的東西。
所以清單修好之後我加了一條:檢查工具本身也要被檢查。 最簡單的做法是故意弄壞一個東西,確認腳本會抓到。
怎麼確認你沒有假綠
| 做法 | 說明 | |
|---|---|---|
| 1 | 每個檢查點套上面三個問題 | |
| 2 | 故意弄壞,確認會被抓到 | ⭐ 最有效的一項 |
| 3 | 檢查工具跑在乾淨環境一次 | 排除工具自身的 bug |
| 4 | 找人重跑一次 | 發現隱含的前提 |
第 2 項是所有方法裡投報率最高的: 護欄要驗過它會擋,才算存在。
常見問題
Q:這樣檢查會不會太慢? A:會慢一點。但假綠的代價是「幾個月後在別人面前爆開」, 兩個成本不在同一個量級。
Q:能不能用測試框架處理?
A:可以,而且該做。
但框架不會替你決定要檢查什麼——
一個測試 assert response.status_code == 200 一樣是形態五。
Q:五種以外還有嗎? A:一定有。這五種是我自己撞到的,不是完整的分類。 但共同結構應該是穩定的,用那個結構去看新的情況比背這五種有用。
Q:其中幾種是你自己犯的? A:兩種。形態二那個「門檻減 0.01」是我寫的, 形態四那個 Grafana uid 也是我設的。 這不是別人的問題,是設計檢查的人容易犯的問題。
你們最近一次「以為做完了其實沒有」,是怎麼被發現的?
🧪 這篇的實驗環境與 lab 檔案(最後更新 2026-08-30)
本篇在 ODH 3.6.0-ea.1 上對帳(共用區塊寫的 3.5.0 是 Day 1–9 的版本)。
叢集
- 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-* 那一套)。
指令的邏輯可以照用,字串要自己對一次。
留言與指正
我特別想知道:你們最近一次「以為做完了其實沒有」,是怎麼被發現的?