Prometheus 與 nodata 告警
背景
隨著云原生和高動態(tài)服務端的發(fā)展,在運維領域,以 Prometheus 為代表的現(xiàn)代時間序列存儲正在加速替代以 Zabbix 為代表的傳統(tǒng)監(jiān)控系統(tǒng)。運維領域在享受時間序列技術發(fā)展紅利的同時,也面臨時間序列管理思路上的轉變和監(jiān)控系統(tǒng)實際應用的上一些難點 —— nodata 告警便是其中之一。nodata 告警是傳統(tǒng)監(jiān)控系統(tǒng)的必備功能,但卻缺席了幾乎所有現(xiàn)代時間序列存儲實踐,這給運維監(jiān)控帶了諸多缺陷。本文嘗試分析其中原因,并給出一些可能的解決方法。
nodata 告警觸發(fā)器的特殊性與必要性
nodata 告警觸發(fā)器(Trigger)與普通告警觸發(fā)器相比具有原生的特殊性。普通告警觸發(fā)器的作用是對一組監(jiān)控指標(Metric)的過濾,通常是基于數(shù)值大小的過濾。
如果存在如下表示的監(jiān)控數(shù)值集合
告警觸發(fā)器 可對集合
中的元素做『大于 100』的過濾
普通告警觸發(fā)器觸發(fā)的告警集合 為
某個普通告警觸發(fā)器作用于某組監(jiān)控數(shù)值后,產(chǎn)生普通告警集合的過程如上文所述。nodata 告警觸發(fā)器的工作需要引入額外的全集
nodata 告警觸發(fā)器 對集合
求絕對補集
nodata 告警觸發(fā)器觸發(fā)的告警集合 為
一般來說,運維監(jiān)控場景下我們希望得到的完整告警集合 為
從上文看出,nodata 告警觸發(fā)器與普通告警觸發(fā)器最大的區(qū)別是前者需要引入『全集』 ,全集應當從監(jiān)控系統(tǒng)之外獲得,以保證監(jiān)控系統(tǒng)本身的有效性。
運維監(jiān)控場景下,發(fā)生 nodata 告警最大的可能性是監(jiān)控系統(tǒng)本身的失效,比如采集點失效或采集對象失效,在我們的實踐中,服務器意外下線、磁盤故障、服務崩潰等都會導致 nodata 告警;另外還有一類監(jiān)控指標,這類指標以 nodata 為『正常狀態(tài)』,如 5xx code 產(chǎn)生的速率,在沒有 5xx code 產(chǎn)生時,雖然我們希望指標的數(shù)值為 0 (而不是 nodata) ,但在實踐中往往很難保證,對于這類指標有效性的保證,我們會在其他文章中詳細說明。
nodata 告警觸發(fā)器的難點之一在于全集 的獲取。在高動態(tài)的服務端環(huán)境中,往往很難得到『全部服務器集合』、『全部 IP 地址集合』、『全部 Pod 集合』、『某服務全部運行實例集合』這樣的全集。所以,在數(shù)值型的監(jiān)控采集之外,必須建設更加結構化的信息組織方式,并配以自動、半自動與人工相結合的信息維護方法。假如在結構化信息中很難方便準確地獲取『全部某某集合』這樣的信息,就無法制作真正有效地 nodata 告警觸發(fā)器。
nodata 告警觸發(fā)器的另一個難點是計算的開銷大。普通告警觸發(fā)器對指標的數(shù)值過濾,可以通過『帶條件的查詢』做到,這本質上是將告警計算的開銷一次性卸載到時間序列存儲系統(tǒng)中,而現(xiàn)代的時間序列存儲系統(tǒng)一般都支持這樣做。由上文對 nodata 告警觸發(fā)器的定義可以得到,nodata 的計算必須在數(shù)值過濾之前,也就是說 nodata 告警計算的計算對象是全量的監(jiān)控指標,對全量監(jiān)控指標求補集本身是一個開銷巨大的計算。另外全集 并不存在于時間序列存儲中(否則 nodata 告警就失去了客觀性),把全集
帶入 nodata 告警計算可會給時間序列存儲帶來額外的傳輸與計算壓力。
雖然有諸多困難,但 nodata 告警的重要性不言而喻。如果沒有 nodata 告警,監(jiān)控指標的失效是靜默的,監(jiān)控系統(tǒng)本身的有效性無法得到保證。對于云原生的服務端環(huán)境,監(jiān)控對象的動態(tài)化程度更高,雖然可以制作更加宏觀的監(jiān)控指標(如某類 Pod 的總實例數(shù)),但 nodata 告警可以幫助我們獲悉更加微觀的服務端運行工況。
在 OpsMind 的實踐中,我們使用 CMDB 和經(jīng)典的 CMDB 方法來獲得全集 ,并改造 Prometheus ,將 nodata 計算卸載到存儲層。下文結合我們的實踐,并盡可能剝離我們特殊的業(yè)務場景,以 Prometheus 為例,介紹幾個相對通用的 nodata 告警觸發(fā)器的實現(xiàn)思路。
單一維度的 nodata
單一維度的 nodata 是最常見的 nodata 告警觸發(fā)器,Zabbix 等傳統(tǒng)監(jiān)控系統(tǒng)提供的也是這類 nodata 功能。以服務器 Load 監(jiān)控為例
存在服務器集合
存在監(jiān)控點
便可以得到 Load 監(jiān)控指標
當監(jiān)控指標 中存在失效的監(jiān)控點時,
變?yōu)椴煌暾闹笜?
如果集合 在時間序列存儲之外(例如,存儲于 CMDB 中),就可以將
認定為 nodata 的全集
,而 nodata 的告警集合
為
的絕對補集
在我們的實踐中,為了將 nodata 的補集運算卸載到 Prometheus,我們將 CMDB 作為一個監(jiān)控點,由 Prometheus 向 CMDB 拉取全集 ,具體的指標類似于
nodata_hosts{host="h1", nodata="True"} 1
nodata_hosts{host="h2", nodata="True"} 1
nodata_hosts{host="h3", nodata="True"} 1
...
同時,假設 Load 監(jiān)控指標 類似于
host_cpu_load5{host="h1"} 42
host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
...
我們針對 生成如下告警觸發(fā)器
host_cpu_load5{host=~"h.*"} > 42
則卸載 nodata 之后的運算可表示為
host_cpu_load5{host=~"h.*"} or on(host) nodata_hosts{host=~"h.*"} * 1/0 > 42
此告警觸發(fā)器可以生成如下的告警信息
host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
host_cpu_load5{host="hx", nodata="True"} +inf
這里有如下幾個關鍵點
- 將 CMDB 中的結構化信息轉儲到 Prometheus
- 使用
or運算符做補集運算 -
on()的 label 為 nodata 的單一維度 - 普通指標與 nodata 指標在 nodata 維度上的查詢條件一致
-
or之后的表達式通過* 1/0轉為+inf以保證數(shù)值條件成立
我們通過將全集 轉為監(jiān)控指標,并通過
or 運算符做補集運算實現(xiàn)了單一維度的 nodata 告警觸發(fā)器。
多維度正交的 nodata
多維度正交 nodata 也是運維監(jiān)控場景中的常見需求。假設有 臺服務,每臺服務器上都運行相同的一組
個服務實例,那么對于服務的監(jiān)控指標,就需要在服務器和服務兩個維度上做 nodata 計算。問題描述如下
服務器集合
服務實例集合
監(jiān)控點 用來獲取服務實例的每秒訪問次數(shù)
得到 Qps 監(jiān)控指標
當監(jiān)控指標 存在監(jiān)控點失效時,
變?yōu)椴煌暾谋O(jiān)控指標
與單維度類似,我們轉儲服務的全集指標
nodata_services{service="s1", nodata="True"} 1
nodata_services{service="s2", nodata="True"} 1
nodata_services{service="s3", nodata="True"} 1
...
假設監(jiān)控指標 類似于
service_qps{host="h1", service="s1"} 42
service_qps{host="h1", service="s2"} 43
service_qps{host="h2", service="s1"} 44
...
則對于監(jiān)控指標 的一個告警觸發(fā)器類似于
service_qps{host=~"h.*", service=~"s.*"} > 42
卸載 nodata 計算之后的告警觸發(fā)器
service_qps{host=~"h.*", service=~"s.*"} or on(host, service) absent(nodata_hosts{host=~"h.*"}, nodata_services{service=~"s.*"}) * 1/0 > 42
此告警觸發(fā)器可以生成如下告警信息
service_qps{host="h1", service="s2"} 43
service_qps{host="h2", service="s1"} 44
service_qps{host="hx", service="s1", nodata="True"} +inf
service_qps{host="h1", service="sx", nodata="True"} +inf
...
除了與單一維度 nodata 類似的關鍵點之外,這里還有如下幾個關鍵點:
- 重寫 Prometheus 的
absent函數(shù)支持多vector的正交計算 -
on()的 label 為多個 nodata 的計算維度
我們通過與單一維度 nodata 類似的手法實現(xiàn)了支持多維度正交的 nodata 告警觸發(fā)器。
更一般的多維度 nodata
多維度 nodata 更一般的表述是多維度之間無法形成正交關系的情況。這些情況較難處理,需要 CMDB 與時間序列存儲建立較密切的聯(lián)系(而非簡單的數(shù)據(jù)轉儲),但如果可以處理得當,可以大大增強監(jiān)控系統(tǒng)的能力。
假設有服務器集合
服務器 上的服務實例集合
監(jiān)控指標
針對這個監(jiān)控指標的一個告警觸發(fā)器
service_qps{host=-"h1,h2,h3"service=-"s1,s2"} > 42
注意這里我們擴展了 PromQL 的語法,支持『列表匹配』=-,關于這個語法帶來的功能和性能的優(yōu)化本文暫不贅述。
為了對 具有這類復雜多維度關系的指標,我們需要在 CMDB 中建立服務器與服務實例的關系表
| ID | 服務器 | 服務實例 |
|---|---|---|
| 0 | h1 | s1 |
| 1 | h1 | s2 |
| 2 | h2 | s1 |
| 3 | h3 | s1 |
通過 CMDB 中的關系表,將 nodata 卸載后的告警觸發(fā)器為
service_qps{host=-"h1[0,1],h2[2],h3[3]"service=-"s1[0,2,3],s2[1,3]"} > 42
這里我們擴展了列表匹配的語法,支持 "nodata key" [0,1],表示某個列表項在全集中的 ID。Promethues 查詢時會針對每個 label 計錄缺失列表項的 nodata key,并將多個 label 記錄下來的 nodata key 求并集。
對本例來說,假設 h1 上的 s2 和 h2 上的 s1 監(jiān)控失效,則 host label 缺失的列表項為 h2(并不包含 h1,因為 h1 下的 s1 有數(shù)值),對應的 nodata key 為
service label 上缺失的列表項為 s2(并不包含 s1,因為 h1 下的 s1 有數(shù)值),對應的 nodata key 為
并集的結果為
這表示全集中 ID 為 、
的條目在時間序列中沒有數(shù)值,需要做 nodata 告警,與我們的假設相符。
結語
本文分析了現(xiàn)代時間序列管理方案中 nodata 的特殊性與必要性,并以 Prometheus 為例嘗試給出幾個解決方案。可以看到,現(xiàn)代時間序列管理方案中的 nodata 處理是十分復雜的,我們認為這和云原生環(huán)境下其他的監(jiān)控難題一樣具有原生復雜性,這只是云原生給運維帶來的諸多根本性挑戰(zhàn)的外在表現(xiàn)形式,這些挑戰(zhàn)需要系統(tǒng)性地分析和解決。OpsMind 為應對云原生的挑戰(zhàn)做了大量的技術研判和產(chǎn)品包裝,我們將在其他文章中與大家陸續(xù)分享。