doctor --deep 當成服務發現掃描:先對帳、再清標籤、再以連線埠切片承載金絲雀,最後用Webhook 失敗摘要廣播把多節點狀態壓成一則可對帳的訊息。
與《多可用區金絲雀、技能切片與探針合併》(流量比例、權重階梯)及《滾動升級與 npm 順序》互補:彼處談怎麼切流量,本文談本機控制面是否只有一份真實來源。片段合併與網關邊界見《模板合併與工作流金絲雀》;租戶探針與 Webhook 摘要模板見《租戶切片與 Webhook 失敗摘要》與《多可用區網關與 Webhook》。完整索引在技術部落格首頁。
為何需要 deep:與「只談金絲雀流量」的分工
金絲雀文假設每臺 Mac 只有一組權威程序監聽約定連線埠;實務上 CI、手動 bootstrap 與舊版 plist 常疊出雙註冊。openclaw doctor --deep(旗標名稱請以實際二進位為準)把設定語意、監聽套接字、launchd 單元與磁碟路徑交叉比對,輸出可機器讀的 JSON,讓「哪一顆 plist 在說謊」變成可排序清單,而不是 grep 大賽。
doctor --deep:最小對帳欄位
演練前約定三欄寫進稽核:label_authority(plist 絕對路徑與 SHA256)、listeners(PID、位址、連線埠)、canary_slice(金絲雀環境變數或次要連線埠)。三台 clustervps Mac 各自產出 doctor-deep-${HOST}.json,用 jq 做集合差分;若僅一臺出現重複 Label,優先懷疑手動拷貝的 LaunchDaemons而非應用程式碼。
set -euo pipefail
/usr/local/bin/openclaw doctor --deep --json >"/var/db/openclaw/audit/doctor-deep-${HOSTNAME}-$(date -u +%Y%m%dT%H%M%SZ).json"
/usr/bin/lsof -nP -iTCP -sTCP:LISTEN | /usr/bin/grep -E 'openclaw|gateway' || true
清理重複 launchd 標籤(最小可復現)
原則:同一 Label 只允許一個權威 plist;其餘一律 launchctl bootout 後移出 /Library/LaunchDaemons(或團隊約定目錄)到冷儲存,再 bootstrap 單一檔案。禁止在不明狀態下連續 kickstart -k 兩次而不比對 plist 指紋,否則容易把競態藏進「偶發成功」。
- ① 列出衝突:
launchctl print system | grep -A2 com.openclaw(Label 前綴請替換為實際值)與 deep JSON 並列。 - ② 凍結寫入:暫停合併器與自動 reload,避免清標籤途中寫入半截設定。
- ③ bootout 非權威單元:保留
tar備份路徑與操作者於工單。 - ④ bootstrap 權威 plist:再跑一輪
doctor --deep,確認listeners唯一。
金絲雀連線埠切片:與 LB 權重的第二道閘門
在網關行程啟動前注入 OPENCLAW_GATEWAY_PORT(名稱示意)或設定片段覆寫,使金絲雀綁定次要連線埠;本機反代或 sidecar 只把小比例上游轉到該埠。這樣即使 launchd 曾重複註冊,也能用連線埠隔離把爆炸半徑壓在單一 slice。合併探針 JSON 應同時帶 primary_port 與 canary_port,避免監控只看預設埠而誤判全綠。
export OPENCLAW_GATEWAY_PORT="${CANARY_PORT:-18088}"
export OPENCLAW_PROBE_TAGS="canary_slice=port:${OPENCLAW_GATEWAY_PORT}"
/usr/local/bin/openclaw doctor --deep --json | /usr/bin/tee /tmp/deep.json
Webhook 失敗摘要廣播:帶上 deep 與連線埠欄位
延續租戶文的五分鐘視窗去重,在摘要第一行 JSON 增加 duplicate_labels 計數、canary_port 與 doctor_deep_ok。值班用手機即可判斷「要回滾權重」還是「要登入某臺清 plist」,而不必翻完整堆疊。完整事件仍寫本機 JSONL 供單筆重播。
演練步驟清單(三台 clustervps Mac)
- R0:各節點備份
LaunchDaemons目錄與 LB/反代權重 JSON;記錄當前監聽表。 - ① 全節點跑
doctor --deep --json,匯總重複 Label 與連線埠衝突清單。 - ② 在金絲雀節點依序 bootout→冷儲→bootstrap 權威 plist;再 deep 驗證 listeners 唯一。
- ③ 啟用連線埠切片與小比例轉發;跑合併探針兩個週期。
- ④ 人為製造一次出站 Webhook 4xx,確認通知器只廣播單則摘要且含 deep/連線埠欄位。
- ⑤ 其餘節點一次一臺重複 ②—④;每臺結束寫入
promotions.jsonl一行指紋。
回滾矩陣(R1/R2)
| 觸發條件 | 影響範圍 | 動作 |
|---|---|---|
| 僅金絲雀 deep 仍報重複 Label 或連線埠衝突 | 單節點+切片流量 | R1:bootout 實驗 plist、還原 R0 tar、取消切片環境變數、權重回到凍結快照。 |
| 已推廣至多節點且探針大面積紅燈 | 全叢集 | R2:對每台依序還原 plist、bootstrap 舊單元、還原監聽環境;廣播「R2 已啟動」含主機清單。 |
| Webhook 摘要風暴或誤報 | 通知通道 | 關閉聚合器五分鐘、降級為「每節點一行心跳」直至 deep 全綠。 |
doctor --deep,並以 jq 斷言 duplicate_labels == 0;否則視為回滾失敗,禁止重新加權。FAQ
deep 會不會太慢拖垮探針? 為 deep 設硬逾時與快取欄位;超時標記 degraded,但重複 Label仍應硬失敗。
使用者域與系統域 plist 衝突怎麼辦? 先凍結使用者登入項目,再以 deep 輸出決定哪一側為權威;禁止兩側同 Label 並存。
連線埠切片要開防火牆洞嗎? 金絲雀埠預設只給本機反代;對外仍只有單一入口,邊界與網關文一致。
doctor --deep、環境變數名稱與 Label 前綴請以貴團隊安裝包為準;殼層與 jq 片段為示意,非可直接貼上的機密。