ANDREW/
選單觀點
中文EN
← 回到文章庫

SYSTEMS / INCIDENT EVIDENCE / HANDOFF

故障恢復了,線索還在嗎?

Andrew Yuan · 技術實驗

系統好了,調查才剛開始

「剛剛明明不能用。」工程師問:「現在呢?」對方說:「好像又好了。」再問錯誤訊息或 Log,得到的答案是:「沒有,但我真的有看到。」

於是,事故調查從一份目擊證詞開始。使用者沒有說謊,工程師也沒有忽略問題;只是等到調查開始,最需要看的那段過程已經過去了。

我做的 Pathquill,是一個設有明確儲存與匯出預算的 Linux 網路事故記錄服務。它持續保留近期觀察,在事故觸發後收集後續資訊,再產生經過去識別化的 JSON/HTML 證據套件。我想研究的是:短暫故障結束後,能否留下一段可以檢查的脈絡,而不是只剩一句「剛才真的壞了」。

留下資料,仍然不等於找到根因。監視器拍到辦公室突然變黑,可以證明燈熄了,沒有順便取得電機技師資格。這篇文章也遵守同一條界線:把觀察、可能解釋和還缺的證據分開。

觀察紙帶穿過琥珀色視窗,框住一段異常附近的紀錄。
概念插圖:框住異常附近的一段觀察,不是實際介面,也不代表已找到根因。

先說清楚:這份紀錄看見了什麼?

Pathquill 的正式服務重建受支援的 Linux link、address 與 route 狀態投影,並依設定執行有界的 TCP/DNS 探測。它不是封包側錄工具,也不是把所有網路行為完整錄下來。本文主要引用的安裝服務驗證,使用的是受控的本機 TCP 目標。

一次探測失敗,可以支持「這個目標在這次探測中沒有成功」;不能直接推成「整個網路斷了」。後來一次成功,也只證明那次觀察,不能補上兩次探測之間所有未知的時刻。

狀態重建也有條件。實作要求兩次相同的 dump 投影之間沒有插入通知,並限制嘗試次數;持續變動、資料遺失或解碼失敗,會妨礙它發布可信的重建結果。這不是跨表原子快照,也不保證任意持續變動下都能收斂。

對調查來說,「我不知道這一段是否完整」本身就是重要資訊。Pathquill 的 coverage 保留 UNKNOWN/PARTIAL 等狀態,不把一份成功產出的套件當成完整現場。

沿著一條事故時間線,檢查前後是否接得起來

故障發生前,服務已在有限的 ring 裡保存近期觀察。觸發時,仍在保留範圍內的內容可以複製到事故記錄;之後再收集窗口內的觀察。資料預算可能讓較早的 ring 內容先被淘汰,因此不能只看設定值,就承諾一定有完整的故障前時段。

後續資料也必須真正進入持久化進度。系統不接受呼叫端自行聲稱「已經記完」的 watermark;封存需要檢查實際進度與尚未完成的探測。資料不足或重啟留下的缺口,不能靠把時間等完來消除。

恢復判斷同樣要看證據的時效。探測結果會帶著啟動時的狀態版本(generation);舊版本的結果不能回頭改寫目前健康狀態,事故開始前的舊成功,也不能當作這次已恢復。受測邏輯要求設定目標符合目前狀態版本,且成功探測的開始時間不早於事故開始。

匯出保留的是記錄器的寫入順序與相對時間,不是不同來源事件的因果順序。舊資料若沒有觀察時間,會保留為 null,不補出一個看似合理的時間。因此,看見兩件事先後出現,仍不能直接判定前者造成後者。

因此,我會按下面四個位置閱讀套件。這是驗收時的閱讀順序,不是所有事故都必然遵循的完整時間線。

  1. 觸發之前看實際還留在 ring 裡的觀察,不把設定窗口當成完整保留證據。
  2. 觸發與後續找到失敗探測及後續觀察,同時讀取 coverage、拒絕接納或重啟造成的缺口。
  3. 觀察到恢復核對目前 generation 的成功探測與開始時間,不能拿事故前的舊成功交差。
  4. 封存與匯出打開實際套件,核對順序與覆蓋範圍;產生檔案不代表已判定原因。

約 330 秒、九次循環,證明到哪裡?

這次最終驗證在新建立的隔離 Colima Linux VM 中進行,使用實際安裝的套件與 systemd 服務,以專用的無特權帳號執行。VM 使用 Colima 的 Ubuntu 24.04.4 minimal containerd 映像;Pathquill 本身以 systemd 服務執行。

最後一次同來源觀察歷時 329.93 秒,完成九次真實本機 TCP 目標的故障、恢復、匯出與退役循環。期間服務的 PID、invocation 與 epoch 維持一致,沒有重啟;161 個資源樣本保留在報告裡。

暖機後主程序取樣 RSS 為 20,860–21,888 KiB,約 20.4–21.4 MiB;後段與前段暖機後十筆取樣視窗的中位數增加 872 KiB。FD 為 7–8,執行緒為 2。這些是主程序與指定時窗的觀察,不是整台 VM 成本,也不能證明長期沒有累積。

Linux 上共 111 項不重複的專案測試通過。七組回歸流程重跑的是同一組測試,仍按 111 項計算。本文引用並核對既有紀錄,沒有重新執行實驗。

紀錄也保留失敗嘗試。早期一次捕捉產生 4,150 列,超過沒有放寬的 4,096 列匯出輸入上限;後續修正把多餘的閒置 progress 提交合併到 100ms,並未把通知或探測處理全部降為這個頻率。最後通過的是修正後的有限工作負載,不是第一個嘗試就成功,也不代表高事件量問題已消失。

兩種滿載,需要兩種處置

第一種是觀察與匯出內容超出預算。ring 可能縮短保留的故障前區間;資料接納被拒絕時,捕捉會留下 PARTIAL 等缺口。事件名額用滿會拒絕新事故。這些結果應讓操作人員知道少了什麼,而不是照樣畫一條看起來完整的時間線。

匯出另有 4,096 列/1 MiB 輸入與 256 KiB snapshot 上限。高事件量或延長捕捉仍可能超出限制,讓匯出失敗。設定預算是限制成本的設計選擇,也會限制能留下的證據;它不是任意負載下的完整記錄保證。

第二種是每個 Store 的 32 筆永久匯出請求歷史名額。取消請求或退役套件,不會讓那個歷史 ID 重新變成空位;第 33 個新請求會被拒絕。這不同於檔案/snapshot 的空間額度,不能以為刪掉套件就能繼續開新請求。

這些也都是各自的邏輯預算,不是整個檔案系統配額,更不是程序 RSS 的硬上限。SQLite 頁面、journal、索引,以及人工保留的其他 Store,都需要另外計入部署成本。

去識別化套件,也要有人檢查才能交出去

匯出把可分享格式與本機管理狀態分開。識別欄位依允許清單轉換,JSON 與 HTML 呈現同一份受限的事故資料。未知欄位會被省略;受支援欄位的值若格式不合法,快照建立會失敗。因此,套件不是原始網路資料的完整副本。原始 Store、管理狀態與封存收據仍是私人資料。

去識別化不等於完全匿名。時間順序、拓撲與其他可比對資訊,仍可能讓接收者推知環境。發布前要按接收對象檢查內容,不應把「工具能匯出」當成「可以直接公開」。

套件的雜湊可以核對內容一致性,不能證明發布者身分或判定根因。另一方面,刪除 SQLite 列或退役套件,也不等於安全抹除磁碟、journal 或已交給別人的副本。證據產生與資料治理,需要各自有人負責。

歷史名額用完,交接不是按一下清空

Pathquill 提供明確的 seal/archive/new-store 流程,沒有自動輪替。操作人員需要先處理進行中的捕捉、hold、匯出與未完成清理,再核對 Store 身分與目前 epoch,封存舊 Store。

完成封存後,服務退出;離線封存操作核對狀態與既有內容,保留原始資料並建立完整性收據。接著在另一個原本不存在的路徑建立新 Store,以新的 Store 身分與 epoch 啟動服務。舊請求與保留參照不會自動複製過去,服務設定也不會自動替你換好。

這意味著維運計畫要有名額監看、交接時間與負責人。舊封存還要保留多久?由誰可以讀取或刪除?停止舊服務與啟動新 Store 之間,哪些觀察可能缺席?這次沒有證明交接全程零空窗,也不能把每個 Store 有預算,說成全主機封存總量永遠固定。

驗收要打開報告,也要有人接手

我會把一次完整演練放進驗收:製造宣告範圍內的故障、等待恢復、產出並打開套件。逐段核對觸發附近的失敗與之後的成功、時間順序、實際保留範圍及 coverage。只看到「匯出成功」,還不知道收到的資料能不能支持調查。

接著測試限制如何被看見:故障前的 ring 被縮短時,讀者能否辨認?匯出超出預算或歷史名額將滿時,誰會收到訊息並採取行動?如果需要另一套告警或維運程序,必須明列,不能因為報告有狀態欄位就當作交接完成。

目前最新安裝服務的 VM 重啟復原、突然斷電、開機初期持續記錄,以及長期穩定性仍未驗證。較早版本的正常重啟試驗,不能代替目前安裝服務的驗證。正式部署還需要核對自己的環境、資料敏感性與接手機制。

系統恢復正常,是好消息。報告也可以產生得很快,根因卻不一定配合交稿時間。Pathquill 讓我把下一步問得更具體:我們實際觀察到什麼,哪些解釋仍是假設,還缺什麼證據,以及接下來由誰負責取得它。

來源與保留證據

程式碼:GitHub / Pathquill · MIT. 證據連結固定到核對版本;本文沒有新增實測。

分享這篇