健康檢查全綠,模型呼叫全掛:一次「Port 活著」的假保險
上週四凌晨兩點,每日更新流水線卡在翻譯步驟,三語發布一行都沒進去。值班腳本吐出來的日誌只有一句話:router 連線逾時。幾乎所有健康檢查都是綠的——Port 在、行程在、/health 回傳 200。

健康檢查全綠,模型呼叫全掛:一次「Port 活著」的假保險
上週四凌晨兩點,每日更新流水線卡在翻譯步驟,三語發布一行都沒進去。值班腳本吐出來的日誌只有一句話:router 連線逾時。幾乎所有健康檢查都是綠的——Port 在、行程在、`/health` 回傳 200。
現場還原
那台機器跑的推論服務前一週剛換過模型版本,上游把模型名從舊版 renaming 成了新版。我們的判斷程式沒有跟上:
- 健康檢查腳本只 `curl /health`,Port 活著就回傳 `healthy`
- 真正的呼叫路徑是 `/v1/chat/completions` 加具體模型名,這條路徑沒人碰過
- 舊模型名其實已經下線,呼叫全部 404,錯誤訊息裡寫著 `model_not_found`
還有一層僥倖:換版本的 changelog 其實當天早上就到了群組裡,有兩個同事看到了,都覺得「路由層應該會自動適配」,沒人去翻我們自己的設定清單。事後看,這是典型的「介面契約變更,兩端各改一半」——上游改了名字,我們還在按舊名字呼叫,中間的監控又恰好只盯著「行程在不在」,三層全都漏。
更麻煩的是,發布腳本對翻譯失敗的備援邏輯是「重試三次後寫佔位內容繼續跑」。也就是說,如果沒人盯警報,當天三語文章會帶著佔位文案直接發上線,而且每個環節的單元測試都是綠的。佔位備援本來是給網路抖動用的,結果被一個「模型名錯了」這種確定性錯誤鑽了空子。
復盤裡定下來的三件事
**1. 健康檢查改成功能自檢。** 新腳本不再只查 Port,而是走和生產環境完全相同的路徑:同一個介面、同一個模型名(從設定讀,不寫死)、同一份最小 prompt、同一個金鑰載入鏈。一分鐘一次,一次完整請求算一次心跳。任何一步 4xx/5xx 直接紅。換模型那天我們已經吃過虧,所以模型名統一放在設定裡,檢查腳本和業務腳本讀同一份,誰也不許在自己程式碼裡寫死名字。
**2. 備援重試只給確定性錯誤用。** 把「重試後繼續」收窄成:只對 5xx 和逾時做有限重試,4xx(參數錯、模型不存在、鑑權失敗)立即失敗並打紅,絕不寫佔位。原則很簡單——網路抖動可以兜,設定錯誤不能兜,兜上去就是拿線上內容替一條錯誤設定買單。
**3. 成功率進日報,而不是出事才看。** 每天彙整各步驟呼叫成功率、P95 延遲、4xx 明細,低於閾值直接警報。這次是凌晨兩點人工發現的,如果當天流量更大或者警報延遲再久一點,佔位內容就會發出去。事後沒有「發現得好」這個說法,只能說監控沒兜住,不能把運氣當設計。
落地比聽起來快:功能自檢腳本就一個檔案,複用發布腳本自己的金鑰載入鏈和設定讀取函式,不重新發明一套,避免「檢查鏈路」和「業務鏈路」各用各的金鑰設定。日報任務掛在已有的 cron 裡,輸出一張三欄小表:步驟、成功率、最慢一档延遲。閾值先拍得糙一點(成功率低於 95% 警報),跑兩週再收緊,別一上來就精細化調參,那屬於過度設計。
給同跑道的人
如果你也是多節點流水線、模型名散落在各機器設定裡,建議花半小時做兩件事:先把所有「模型名」抽到一處設定,檢查腳本和業務共用;再把健康檢查從 `/health` 升級成一個真實的最小請求。前者是一次性的,後者能防住一整類「Port 活著、服務全殘」的問題。上次 404 的報錯原文還留在警報歸檔裡,格式、模型名、參數一個不缺——擺著提醒自己:綠的不一定是好的。
留言區
歡迎分享你的想法!
載入留言中…