自動化本地 LLM 推論伺服器存活監控的經驗

自動化本地 LLM 推論伺服器存活監控的經驗

2 min read

2026.04 / Tech Blog / BASTION

自動化本地 LLM 推論伺服器存活監控 #

GPUStack 會偵測到「看似正在執行,但無法進行推論」的狀態。我們建置了一套系統,每 5 分鐘執行 2 階段健康檢查+Slack 通知+Zabbix 整合。

問題出在哪裡? #

前一篇文章介紹的 BASTION(以本地 LLM 建置自動分析基礎設施日誌的系統)是一套每 15 分鐘使用本地 LLM(Qwen2.5-14B)自動分析基礎設施日誌的機制。

這套機制的單點故障(single point of failure)就是 GPUStack(LLM 推論伺服器)。當 GPUStack 停止運作時,日誌收集仍會持續,但分析與通知全部中止。而且,要察覺它已經停止還需要一段時間。每 15 分鐘就會送來一次的 Slack 通知「沒有送達」,有時要花上好幾個小時才會被人發現。

GPUStack 本身具備模型自動重啟功能。如果處理程序當機,會自動重新啟動。但光靠這個功能,無法偵測到某些情況。

GPUStack 自動重啟無法偵測到的情況:
・API 處理程序仍存活,但模型因 OOM 而被卸載
・API 回應 200,但模型不回應推論請求
・從網路角度來看,BASTION 伺服器無法連線到 GPUStack

換句話說,「處理程序存活」與「推論實際可用」是兩回事。我們需要一套能定期從外部確認「是否真的能執行推論」的機制。

設計方向 #

任務很單純:每 5 分鐘呼叫一次 GPUStack API,正常就不做任何事,異常就通知 Slack。不過,其中有幾個設計上的決策。

2 階段檢查 #

我們將健康檢查分成 2 個階段。

階段檢查內容偵測項目失敗時
CHECK1對 /v1/models API 發出 HTTP 請求GPUStack 處理程序是否存活Exit 1(CRITICAL)
CHECK2對 chat/completions 發出實際推論請求模型是否確實已載入並能回應Exit 2(WARNING)

CHECK1 通過但 CHECK2 失敗的情況相當重要。這是「API 存活,但模型無法推論」的狀態,光靠處理程序監控絕對無法偵測到。

在 CHECK2 中,我們送出單一個詞 "healthcheck",並以 max_tokens: 5 接收回應。由於只需確認推論是否可用,我們使用最少的 token 數量。

結束代碼(Exit code)設計 #

我們將腳本的結束代碼設計成 3 種數值。

結束代碼意義Slack 通知Zabbix 觸發條件
0正常僅在復原時通知—
1API 停止CRITICAL(紅色)嚴重故障
2模型停止WARNING(橘色)警告

我們將 1 與 2 分開,是因為應對方式不同。若是 Exit 1,就需要檢查 GPUStack 處理程序本身;若是 Exit 2,只要查看 GPUStack 主控台上的模型狀態即可。收到通知的人,光憑結束代碼就能理解接下來該做什麼。

連續告警抑制 #

以每 5 分鐘的間隔執行,意味著若 GPUStack 停止 30 分鐘,同一則 CRITICAL 通知就會送出 6 次。到了第三次,就沒有人會再去看 Slack 了。

為了防止這種情況,我們將前一次的狀態儲存到檔案中,並只在狀態改變時才發送通知。

狀態轉換通知
ok → api_down發送 CRITICAL 通知
ok → model_down發送 WARNING 通知
api_down → ok發送 RECOVERED 通知
api_down → api_down不通知
model_down → model_down不通知

在復原時發送 RECOVERED 通知也同樣重要。收到 CRITICAL 通知的人,不必再開啟主控台去確認「現在還沒好,還是已經修復了」。

實作 #

全部功能都以單一個 shell 腳本完成。它與其他 BASTION 腳本(summarize.sh、analyze.sh 等)放在同一目錄下,並由 cron 每 5 分鐘執行一次。

CHECK1:確認 API 回應 #

MODELS_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
    --max-time 15 \
    -H "Authorization: Bearer ${API_KEY}" \
    "${GPUSTACK_HOST}/v1/models")

由於 GPUStack 提供與 OpenAI 相容的 API,我們只需對 /v1/models 端點送出 GET 請求即可。若回傳 HTTP 200,代表 API 存活。逾時設定為 15 秒。

CHECK2:實際推論測試 #

INFERENCE_RESPONSE=$(curl -s --max-time 15 \
    -H "Authorization: Bearer ${API_KEY}" \
    -H "Content-Type: application/json" \
    "${GPUSTACK_HOST}/v1/chat/completions" \
    -d '{"model":"qwen2.5-14b-instruct",
         "messages":[{"role":"user","content":"healthcheck"}],
         "max_tokens":5,"temperature":0}')

若回應的 JSON 中含有 "choices",即代表推論成功;若沒有,則視為模型停止並進行處理。

狀態持久化 #

# Read previous state
STATUS_FILE="/tmp/gpustack-health-status"
PREV_STATUS="ok"
[ -f "$STATUS_FILE" ] && PREV_STATUS=$(cat "$STATUS_FILE")

# Save current state
echo "ok" > "$STATUS_FILE"      # or "api_down" or "model_down"

只需將狀態寫入單一個檔案即可,不需要複雜的資料庫。即使重啟後也一樣:第一次執行時因為沒有檔案,會以 ok 作為預設值;若有異常,則在第一次執行時就會發送通知。

cron 註冊 #

*/5 * * * * bash /opt/oc-seclogs/gpustack-healthcheck.sh >> /var/log/gpustack-healthcheck.log 2>&1

我們將間隔設定為 5 分鐘,比 BASTION 的定期分析間隔(15 分鐘)更短。因為若在 LLM 停止的狀態下執行定期分析,會導致失敗,所以我們希望能在此之前就先偵測到問題。

實際遇到的狀況 #

忽略了 API 金鑰驗證 #

第一次執行時,CHECK1 立刻回傳 HTTP 401。GPUStack 已啟用 API 金鑰驗證,但我沒有在 curl 中加入 Authorization 標頭。這是個很明顯的失誤,但我認為在測試環境(無驗證)中撰寫健康檢查腳本,再原封不動搬到正式環境的情況相當常見。我們透過修改,改為從 .env 檔案讀取 API 金鑰來解決這個問題。

CHECK1 通過但 CHECK2 失敗的情況確實存在 #

GPUStack 的 API 處理程序正在執行,但模型載入失敗。/v1/models 回傳 200,但 chat/completions 回傳 500。若只靠處理程序監控,會判定為「正常」。採用 2 階段檢查的決策,在實際營運中得到了驗證。

Slack 通知格式應包含「接下來該做什麼」 #

一開始我們只寫「GPUStack API 沒有回應」,但收到通知的人(也就是我自己),每次接下來要做的事都一樣。因此,我們在通知文字中加入了確認指令(curl -s http://<endpoint>/v1/models)。收到通知後,只要複製貼上到終端機,就能快速確認狀態。

Zabbix 整合 #

以 cron 為基礎的 Slack 通知即時性很高,但缺乏「過去一個月的正常運作時間」或「回應時間的趨勢」這類長期觀點。這正是 Zabbix 的強項,因此我們也註冊了 UserParameter,讓 Zabbix 一併進行監控。

UserParameter #

我們在 Zabbix Agent 的設定檔中新增了 3 個 UserParameter。

Key內容資料型別
gpustack.health健康檢查腳本的結束代碼(0/1/2)整數
gpustack.response_timeAPI 回應時間(毫秒)整數
gpustack.models模型清單 JSON文字

gpustack.health 會呼叫健康檢查腳本本身並回傳結束代碼。透過在 cron 與 Zabbix 中重複使用同一個腳本,可避免判斷邏輯重複撰寫。

觸發條件(Trigger) #

我們設定了 3 個等級的觸發條件。

嚴重程度條件意義
嚴重故障gpustack.health = 1API 停止。LLM 推論完全中斷。
警告gpustack.health = 2API 存活,但模型無法進行推論。
資訊gpustack.response_time > 5000API 回應耗時 5 秒以上。GPU 高負載的徵兆。

第三項「回應延遲」觸發條件,可作為演變成 CRITICAL 之前的前兆偵測。若在圖表中看到回應時間逐漸增加,就能在發生 OOM 之前,先調整模型的批次大小或並行請求數量。

cron 與 Zabbix 的角色分工 #

cron(每 5 分鐘):偵測到異常時立即通知 Slack。簡單且可靠。
Zabbix(每 5 分鐘):儲存歷史紀錄、回應時間圖表、升級(escalation)、故障歷史。

同時運作兩者可確保冗餘性。即使 cron 停止,Zabbix 仍會捕捉到;即使 Zabbix 停止運作,cron 依然會發送通知。

營運成果 #

自開始營運以來,我們偵測到 2 起 GPUStack 模型停止回應的案例。這兩起案例都是 CHECK1 通過但 CHECK2 偵測到異常(Exit 2)。查看 GPUStack 主控台後,發現模型處於「Error」狀態。GPUStack 的自動重啟功能重新載入了模型,幾分鐘後便收到 RECOVERED 通知,確認已復原。

若沒有健康檢查,下一次的定期分析(最多 15 分鐘後)就會失敗,而且需要更長的時間,人才會察覺 Slack 沒有再發送通知。

總結 #

我們透過 shell 腳本+cron+Zabbix,將本地 LLM 推論伺服器(GPUStack)的健康監控自動化。

有三個設計重點值得一提。2 階段檢查(分別檢查 API 回應與實際推論,提升偵測準確度)、連續告警抑制(僅在狀態改變時才通知,防止倦怠)、cron 與 Zabbix 併用(同時達成即時通知與長期趨勢分析)。

由於整套機制都以單一個 shell 腳本完成,同樣的做法也可套用在其他提供 OpenAI 相容 API 的 LLM 伺服器監控上,例如 vLLM 或 Ollama。

BASTION 是一項在封閉環境中實現 AI 安全監控的服務。

BASTION 服務頁面
聯絡我們

Updated on 2026年6月9日

What are your feelings

  • Happy
  • Normal
  • Sad

©2020 BESTNET.LLC . All Rights Reserved.