假綠 = 檢查點顯示成功,但事情沒有發生。
它比失敗危險,因為失敗會叫你,假綠不會。
你會在幾個月後、通常是在別人問你的時候,才發現。
這篇把我在自己 lab 上撞到的五種整理出來——
它們有共同的結構,認得那個結構,就能自己看出新的。
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 也是我設的。
這不是別人的問題,是設計檢查的人容易犯的問題。
本篇在 ODH 3.6.0-ea.1 上對帳(共用區塊寫的 3.5.0 是 Day 1–9 的版本)。
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-*)。指令邏輯可照用,字串要自己對一次。
你們最近一次「以為做完了其實沒有」,是怎麼被發現的? 留言或到原文留言都可以,我會回。