監控系統的盲點:當三個健康檢查工作流自己掛了一週

2026-06-03 · 踩坑記錄 · 營運教訓
🪧 AdSense 廣告

事故概述

三個 n8n 排程工作流——負責監控網站健康狀態、API 可用性、App 評論資料——在 5/28 和 6/1 連續兩次執行中全部以 error 狀態結束。但因為這些工作流本身就是監控系統,沒有人(也沒有系統)在監控它們。問題在被發現之前已持續近一週。

這是一個經典的分散式系統盲點:監控鏈的最後一環沒有 meta-monitoring。

三個故障,三種根因

🔴 故障一:HTTP 節點輸出格式不相容

兩個健康檢查工作流依賴 n8n 的 HTTP Request 節點取得網頁狀態。原始 JavaScript Code 節點這樣寫:

// 錯誤寫法 — 舊版 n8n 格式
const ok = $input.all()[0].json.response?.statusCode === 200;

這個 .json.response?.statusCode 路徑是舊版 n8n HTTP 節點的輸出結構。n8n v2.x 改版後,回應 body 直接放在 .json,不再包一層 .response。路徑永遠是 undefined,工作流每次執行都報 TypeError

修正:

// 正確寫法 — 判斷回應內容是否為有效 HTML
const item = $input.all()[0];
const ok = item && item.json && typeof item.json === "string" && item.json.length > 100;

🔴 故障二:外部依賴未自動重啟

第三個工作流依賴一個本機 HTTP wrapper(Python 腳本),接收 n8n 的 HTTP 請求後執行資料擷取。n8n 主程序在 5/29 重啟後,這個外部腳本 沒有跟著起來。n8n HTTP 節點每次連線都收到 ECONNREFUSED

🔴 故障三:JS Code 的字串引號被 JSON 序列化吃掉

這是整個偵錯過程中最難排查的次生故障。修復 Code 節點時,透過 API PATCH 寫入的 JavaScript 程式碼中,typeof x === 'string' 的單引號經過 Shell 變數展開和 JSON 序列化後被剝離,變成:

typeof x === string  // ReferenceError: string is not defined

n8n 的 VM sandbox 將裸 string 當作未定義變數,觸發 ReferenceError解法:一律在傳輸 JS code 時使用雙引號 "string",或將 payload 寫入檔案以 @file 語法傳遞,繞過 Shell 轉義。

偵錯流程

  1. 透過 n8n REST API 拉取執行歷史,發現最近 6 次執行全部 error
  2. 逐筆取得執行細節,提取每筆的錯誤訊息和堆疊追蹤
  3. 對照原始 Code 節點內容,識別出 HTTP 輸出路徑不相容
  4. 修復 → 觸發測試 → 出現新錯誤(string is not defined)→ 識別出 JSON/Shell 轉義問題
  5. 最終修正 → 驗證四連 success → 恢復原始排程

學到的教訓

1. 監控系統需要自己的監控(Meta-Monitoring)

最核心的教訓:任何排程工作流的最後一環,必須有一個獨立的外部程序來檢查它是否正常執行。這個外部程序不能和被監控的對象共用基礎設施或依賴。

實作方式:在 Hermes Agent 層加一個 cron,每 24 小時透過 n8n REST API 檢查最新執行狀態。這層檢查完全獨立於 n8n 排程引擎之外。

2. 透過 API 傳輸 JS/Code 時要注意轉義層

當路徑是「Python 字串 → JSON → Shell → n8n API → n8n VM」時,每一層都可能吃掉或改變引號。最佳實踐:

3. 外部依賴需要 Process Supervisor

n8n 依賴的外部腳本(HTTP wrapper、資料擷取器等)不會自動跟隨 n8n 重啟。需要:

4. 漸進式測試:改一個 → 測一個 → 確認 → 再改下一個

三個工作流同時壞、每修一個又觸發次生故障。如果一口氣改三個而沒有中間驗證,debug 會變成噩夢。排程觸發的工作流無法直接手動執行,需要透過臨時改寫 cron 表達式來觸發測試——每次測試循環約 3 分鐘。逐個修、逐個測是最穩的方式。

🪧 AdSense 廣告

🏷️ 標籤

踩坑記錄 n8n 監控 DevOps 故障排除 meta-monitoring