4.28 單台主機的監控盲區與失效域以外的觀察者:主機停機、使用者路徑中斷與告警鏈中斷,心跳監看與外部探測各自回答的問題與位置,以及與內部探測並排的判讀
這一篇整理服務與監控放在同一台主機(或同一個失效域)時,監控結構上判斷不了的故障,以及從失效域以外送出訊號的方式。觀測與被觀測系統一起退化的一般機制在 4.25 觀測共命運失效:工具退化時的訊號與人層設計,這一篇處理其中最小的情形:只有一台主機。第三方服務的挑選與接法見 第三方心跳監看與外部探測服務,多台主機怎麼分配服務與監控見 小型服務的兩到三台主機配置。在 4.0 服務端監控的分層 的分法裡,主機停機與告警鏈停掉是位置盲區(觀察者跟著被觀察的對象一起停),使用者路徑中斷是服務網路內探測的檢查範圍盲區(公開入口不在它的檢查範圍裡),通知送不出去是讀取缺口,由送失敗的訊號偵測,各層的接手關係見那一篇;本篇的外部探測就是那一篇的使用者路徑合成監控(Synthetic Monitoring),主機內部探測是服務網路內的探測。
監控與服務共用失效域時看不到的故障:主機停機、使用者路徑中斷與告警鏈中斷
監控要判斷一件事,有兩個條件:監控自己的程序在運作,以及它所在的位置看得到那件事。同一台主機上的 Prometheus 讀得到主機與容器的資源、每個服務的指標,也能從主機內部對各服務發探測請求,這些都在它的位置看得到。它與服務共用同一個失效域(failure domain):同一台主機、同一個網路出口、同一個雲端帳號。下面三種故障,監控在自己的位置上看不到,或看到了也送不出告警:
主機整台停掉。 當機、斷電、雲端業者的維護重開、帳號因為付款失敗被停權,Prometheus 與 Alertmanager 跟著服務一起停。通知端的結果是什麼都沒收到,而「什麼都沒收到」與「一切正常」在收件匣裡看起來相同。
使用者到主機的路徑中斷。 網域過期、DNS 設定改錯、TLS 憑證過期、雲端業者的網路故障、防火牆規則擋掉外部連線,主機內部的服務都在運作,而使用者連不進來。主機內部的探測走的是容器網路或 127.0.0.1,不經過 DNS、不經過公開的網路入口與 TLS,所以照樣回報成功。以 4.27 主機過載時的監控告警 那個短網址服務為例,它的探測程式 Gatus 跑在同一個 Compose 專案裡,連的是服務名稱,量到的是「容器之間通不通」,不是「使用者連不連得到」。
告警鏈中斷。 規則照常成立,而告警送不到人手上,分成兩種。告警鏈停掉:Alertmanager 停了、規則檔沒有載入、Prometheus 連不到 Alertmanager。通知送不出去:Alertmanager 在運作,而某一條通知管道的網址打錯、外部的推播服務拒收。通知送不出去時,Alertmanager 會留下失敗的計數,Prometheus 可以把它寫成告警(見 Alertmanager 的通知管道 的〈通知送不出去的偵測〉);而這條告警本身也要經過某一條通知管道,唯一的管道壞掉時它同樣送不出去。
主機停機與告警鏈中斷,是監控與它要判斷的對象一起停掉,或監控的告警出不去;使用者路徑中斷,是監控所在的位置不在使用者的路徑上。這兩類成因都不是在同一台主機上把監控做得更可靠(多跑一份 Prometheus、加大資源)解決得了的,因為多出來的那一份同樣在這台主機上、同樣不在使用者的路徑上;要的是一個位置在主機失效域以外、而且能照使用者路徑發請求的觀察者,下文稱為失效域以外的觀察者。它送出的訊號稱為頻外訊號(out-of-band signal,簡體中文資料常寫作「帶外」)。
主機過載是另一種情形:Prometheus 與 Gatus 都還在運作,而它們自己也在等 CPU,量到的延遲混進了監控本身的等待,判讀見 4.27 主機過載時的監控告警 的〈探測程式與 Prometheus 在過載主機上的 CPU 等待〉。
頻外訊號:心跳監看與外部探測
失效域以外的觀察者送出的頻外訊號有心跳監看與外部探測,兩者經過的路徑不同,所以各回答一個問題、彼此不能取代:心跳走的是監控本身送出告警的那條路,外部探測走的是使用者到服務的那條路。送出的方向也不同:心跳由被觀察的一方送出,觀察者在它缺席時通知;外部探測由觀察者主動發出請求,看回應。
心跳監看:監控與送出心跳的那條路還在不在運作
心跳監看(dead man’s switch)把判斷反過來:監控持續對外送出「我還在」,觀察者在約定的時限內沒收到就通知。時限由兩個值組成:週期(period,預期的心跳間隔)與寬限(grace time,容許的延遲),距離最後一次心跳超過兩者的總和就判定中斷。Prometheus 上的做法是一條永遠成立的告警規則(Watchdog),Alertmanager 定時把它經由一個專用的 receiver 送到觀察者的網址。主機整台停掉、主機對外的連線斷掉,以及告警鏈停掉的各種情形(Alertmanager 停了、規則檔沒有載入、Prometheus 連不到 Alertmanager、Watchdog 的路由設錯),心跳都會中斷。中斷的時間點大多在週期加寬限之後;停掉的是 Prometheus 時再晚一段:Prometheus 送給 Alertmanager 的告警帶著結束時間,Prometheus 停掉之後,Alertmanager 在這個結束時間之前照常重送心跳,依 Prometheus 的原始碼這段重送期約是 4 個評估週期(評估週期短於 1 分鐘時以 1 分鐘計),這裡沒有實測。
心跳只走送到觀察者的那一個 receiver,所以它不涵蓋通知送不出去的那一種:ntfy 的網址打錯、推播服務拒收時,心跳照常送達。這一種由通知送不出去的偵測發現,送到心跳監看服務上另一個專用檢查項目的回報失敗端點,設定見 第三方心跳監看與外部探測服務 的〈Alertmanager 的 receiver 與路由:心跳與送失敗的通知〉。心跳監看也不回答使用者連不連得到服務:主機照常運作、監控照常送心跳時,DNS 改錯讓使用者全部連不進來,心跳監看看不出任何異常。心跳也分不出故障與入侵:監控主機被入侵時,攻擊者可以用它存著的心跳網址自己送心跳,判讀見 7.43 兩個網路之間的連線安全 的〈監控主機在信任關係裡的位置〉。
成立的條件有三個。觀察者在送出心跳那台主機(跑 Alertmanager 的那一台)的失效域以外:跑在同一台主機上的觀察者,主機停掉時跟著停。觀察者的通知管道不依賴那台主機,也不寄到被監看服務網域底下的信箱:觀察者也透過那台主機上的推播服務通知時,心跳中斷的通知會走那條已經停掉的路;被監看服務的網域過期時,寄到那個網域的所有通知會一起收不到,觀察者的通知不能也在其中。Alertmanager 實際送出心跳的最長間隔(repeat_interval 加一個 group_interval,因為 Alertmanager 只在 group_interval 的時間點檢查要不要重送)要小於觀察者設定的週期加寬限,參數的寫法見 Alertmanager 的通知管道 的〈Watchdog:告警鏈本身的心跳〉。
沒有 Prometheus 的主機也能送心跳:以 systemd 的計時器定時對心跳監看服務送請求,做法見 服務掛了怎麼自動知道 的〈第三層:整台機器死掉怎麼辦(監控自己的盲點)〉。排程工作(備份、匯出)同樣可以在跑完時自己送一次心跳,那時心跳回答的是「工作有沒有跑完」。
外部探測:使用者看到的結果
這一篇的外部探測指從服務主機的失效域以外,在使用者連線出發的那一側網路照使用者的路徑對服務發請求:公開服務的使用者從公開的網際網路來,探測就解析公開的網域、經過 HTTPS 與公開的入口,檢查回應的狀態碼與內容(也叫 synthetic monitoring 或 blackbox 探測,多地區探測的做法見 4.10 Client-side / Synthetic / RUM 的〈Synthetic Monitoring〉);只在公司內網或 VPN 裡提供的服務,探測端要放在那個網路裡。站內 多容器服務的存活監看 的外部探測是從同一台主機上另一個容器發出的,「外部」是相對於服務的程序;它在服務程序卡住時有用,而主機停掉時跟著停,不是這裡要的觀察者。
外部探測回答的是使用者連不連得到、回應對不對,涵蓋主機整台停掉與使用者路徑中斷。它不回答監控還在不在運作:服務照常回應、Alertmanager 已經停了一整天,外部探測全部成功。
成立的條件是探測的位置與路徑:探測端在使用者所在的那一側網路、服務主機的失效域以外,請求打的是使用者用的網址,而不是主機的 IP 或內部的服務名稱;失敗通知同樣不寄到被監看服務網域底下的信箱。判斷成功的方式也要選對,只看狀態碼時入口回一個錯誤頁也可能是 200,比對回應內容的做法見 第三方心跳監看與外部探測服務 的〈外部探測服務的挑選條件〉。
觀察者的位置:第三方服務、另一個失效域的主機(含互相監看)與另一個業者的排程平台
心跳監看與外部探測要避開的失效域不同:心跳監看的觀察者要避開送出心跳那台主機(跑 Alertmanager 的那一台)的失效域;外部探測量使用者路徑時要避開服務主機的失效域,兩台主機時用來分辨連線中斷的那一個另外要避開監控主機(見本節最後的清單)。只有一台主機時,跑 Alertmanager 的主機就是服務主機,兩種觀察者要避開的是同一台;監控搬到另一台主機之後,兩者分開。這個位置有幾種來源。
第三方服務。 心跳監看與外部探測都有現成的託管服務,它們跑在別人的機房與網路上。業者維運的意思是業者自己知道服務停了,購買服務的一方並不會因此收到通知:檢查項目被刪除、業者自己停擺,心跳都只是不再被看著。購買服務的一方能做的是定期演練(見〈心跳監看、外部探測與內部探測並排時的判讀〉的演練那一段),以及訂閱業者的狀態頁。代價是告警的內容(標籤、主機名)與被監看服務的公開網址會送到第三方,以及多了一個要管理的帳號與私密網址。
另一個失效域的主機。 心跳監看的邏輯不複雜(記下最後一次心跳的時間,距離它超過週期與寬限的總和就通知),開源的心跳監看服務也可以自架。自架的觀察者成立的前提有兩個。第一,觀察者所在的主機與它要避開的那台主機不共用失效域:換一個雲端業者,或一台在雲端、一台在地端,是分開的起點;分開之後還要逐項比對兩台仍共用的依賴(付款方式與帳號、通知用的郵件服務、DNS 業者),比對方式見 Failure Domain 的〈判讀方式〉。第二,觀察者自己停掉時有人知道,這又分成兩種情形。主機停掉:只多租一台主機專門跑觀察者、監控仍留在服務主機上時,由服務主機上的 Prometheus 探測那一台;監控搬到第二台時,由互相監看處理,服務主機上跑心跳監看程式,接收監控主機送出的心跳,監控主機讀取服務主機的指標(讀不到時,監控主機上那個讀取對象的 up 變成 0,這也是一種由觀察者主動發出的訊號),服務主機停掉時由監控主機上的 Alertmanager 通知值班的人,任一台主機停掉,另一台都會發現。監看程式這個程序停掉、主機照常運作:由監控主機對它發探測。兩邊的通知管道也要彼此獨立:服務主機上的監看程式直接寄 email 或走另一個推播服務,不經過監控主機上的 Alertmanager。心跳的接收端點不對外開放、監看程式怎麼被探測、兩台同時停掉的風險,見 小型服務的兩到三台主機配置 的〈兩台主機互相監看〉。
另一個業者的排程平台。 自己寫的觀察者不一定要跑在一台主機上,也可以是另一個業者的排程或無伺服器平台上、以組織自己的帳號執行的程式。它與主機的故障分屬不同失效域,帳號、付款方式與通知管道仍要逐項比對;資料留在自己的帳號裡,而那個業者也是要多管理的一個帳號。這種觀察者要能接收心跳並保存最後一次心跳的時間,它自己停掉時,可以由主機上的 Prometheus 定時探測它來發現;本篇沒有實作這一種。
兩種頻外訊號的位置分開選:
- 心跳監看:手上有一台與跑 Alertmanager 那台主機不共用失效域、它停掉時也會被發現的主機,就放在那台,告警內容留在自己手上,也不必多管理一個第三方的帳號;沒有,就用第三方服務,或另一個業者的排程平台。告警內容不能離開組織時,第三方的心跳服務也還有一種用法:Alertmanager 的 webhook 一律送整份告警 JSON,改由主機上的排程確認
Watchdog還在之後對 ping 網址送一個不帶本文的請求,送到第三方的就只剩「還在」這個訊號。這條路繞過了 Alertmanager 送出告警的那一段,所以心跳不再涵蓋 Alertmanager 的送出;本篇沒有實作這種送法。 - 外部探測:量使用者路徑時放在服務主機的失效域以外,通常是第三方的外部探測服務;只有一台主機時就滿足了。兩台主機互相監看時,還要有一個探測負責分辨「兩台之間的連線斷了」與「服務主機停了」:監控主機讀不到服務主機時,兩者看起來相同。只是兩台之間的 VPN 斷掉時,監控主機對服務公開網址發的探測走網際網路、不走 VPN,照樣成功,已經分辨得出來(監控主機的流量若被 VPN 改走隧道,這一點就不成立);兩地之間的網路路徑一起斷掉時,放在監控主機上的探測端與讀取一起失敗,值班的人會把它判成服務主機停了。所以這一個探測放在服務主機與監控主機兩者的失效域以外,它也是兩台同時停掉時唯一還看得到的觀察者。
心跳監看、外部探測與內部探測並排時的判讀
心跳監看、外部探測與主機內部對同一功能的探測各自站在不同的位置,故障發生時三者的組合指向不同段的失效。下表以單台主機加第三方服務的配置為準,並假設沒有收到送失敗通知的那個檢查項目寄來的通知。主機內部探測要打外部探測檢查的那一個功能,而且直接打應用程式、不經過反向代理(例如外部探測打一條測試短網址,內部也直接對應用程式要同一條短網址);只看「有沒有任何告警成立」分不出失效在服務還是在路徑,而 4.27 那個範例的 Gatus 探的是健康端點,與外部探測不是同一個功能,要另外加這一條。內部探測若經過反向代理,入口元件的故障會落在「服務本身」那一列。
| 心跳監看 | 外部探測 | 主機內部對同一功能的探測 | 指向的失效 |
|---|---|---|---|
| 正常 | 正常 | 成功 | 沒有失效:服務對外可用,監控在運作;其他內部告警(資源、某個服務)照常判讀 |
| 正常 | 正常 | 失敗 | 內部探測自己的路徑或探測程式(容器網路、服務名稱解析、探測程式過載),或外部探測打到 CDN 的快取而源站已經壞了 |
| 正常 | 失敗 | 失敗 | 服務本身:外部與內部兩個位置都看到同一個功能失敗,失效在應用程式、資料庫這一側 |
| 正常 | 失敗 | 成功 | 主機內部探測沒有經過的那段路徑:主機以外的網域註冊、DNS、業者網路、防火牆或安全群組,以及主機上的入口元件(反向代理、TLS 憑證的自動續期、tunnel 的連接程式);入口元件的紀錄在主機上 |
| 中斷 | 正常 | 不可信 | 監控或心跳送出的那條路:Prometheus、Alertmanager、規則與路由、送心跳的 receiver、主機對心跳監看服務的對外連線;服務仍在對外服務 |
| 中斷 | 失敗 | 不可信 | 主機整台或它的網路 |
心跳中斷的兩列,主機內部探測的結果靠監控送出,監控停了或送不出去時結果到不了人手上,所以這一欄不能拿來判斷,這段時間沒有收到內部告警也不代表服務正常。表上沒有通知送不出去的那一種:三欄都可以是正常,訊號是送失敗通知的那個專用檢查項目寄來的 DOWN(檢查項目的名稱就指出是送失敗通知),它本身指出了失效所在。兩台主機互相監看時,心跳欄看的是服務主機上的監看程式有沒有收到監控主機送來的心跳:服務主機整台停掉時,監看程式跟著停,心跳欄不會有任何通知,主機停機的告警由監控主機發出;兩地之間的連線斷掉時,兩邊各自發出對方停機的通知,而外部探測成功。
只有主機內部的監控時,所有判斷都來自主機內部,排查從登入主機看程序與日誌開始。多了失效域以外的觀察者之後,各觀察者看到的組合先指出失效在哪一段,再決定下一步:指向服務本身時,照內部告警與應用程式的日誌查;指向路徑時,先查主機上入口元件的紀錄(反向代理的日誌、憑證續期的紀錄),沒有異常再往主機外查 DNS、網域與業者的狀態頁;指向主機整台或它的網路時,從主機外一層層確認哪裡斷,見 機器連不到或起不來;指向監控本身時,查 Prometheus 與 Alertmanager 的程序狀態(見 程序、服務與狀態怎麼判),再查規則、路由與主機對心跳監看服務的對外連線。
心跳監看與外部探測的失敗通知平時不會發出,所以接好之後要演練,而且要定期再演練,因為第三方那邊的檢查項目被刪除時同樣沒有任何訊號。兩種演練各自隔離一段路徑:停掉 Alertmanager 讓告警鏈中斷,確認心跳監看在週期加寬限之後通知;在外部探測服務上把探測的目標網址暫時改成不存在的路徑,不必停掉服務就模擬出使用者路徑的失敗,確認外部探測的失敗通知會到。演練完重新啟動 Alertmanager、把探測網址改回原值。心跳演練的實測時間線見 第三方心跳監看與外部探測服務 的〈心跳中斷的演練〉;演練的範圍、安全邊界與成功標準怎麼定,見 Game Day,驗證備援的一般原則見 4.25 觀測共命運失效 的〈驗證備援:不測的備援是薛丁格的備援〉。
延伸閱讀:觀察者改由第三方提供、主機變成兩台、失效範圍擴大到觀測系統與主機過載
- 觀察者改由第三方服務提供時,要挑的條件(服務的位置、通知管道、告警資料留在哪裡)、healthchecks 的 ping 網址與
/fail、Alertmanager 送心跳與送失敗通知的路由,見 第三方心跳監看與外部探測服務。 - 主機從一台變成兩台時,監控搬到另一個失效域、兩台之間的 VPN 與互相監看、雲端業者處理與不處理的故障,見 小型服務的兩到三台主機配置。
- 失效的範圍從一台主機擴大到觀測系統本身時,依賴耦合、反噬與供應商單點三種機制,狀態頁的失效域隔離與人在盲飛時的應對,見 4.25 觀測共命運失效。
- 主機沒有停而是過載時,監控自己也在等 CPU 的訊號與資源隔離,見 4.27 主機過載時的監控告警。
#backend #observability #reliability #monitoring #failure-domain