Prometheus 與 nodata 告警

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ù)值集合 {M}
{M={ \left\{ {m\mathop{{}}\nolimits_{{1}},m\mathop{{}}\nolimits_{{2}},...,m\mathop{{}}\nolimits_{{n}}} \right\} }}
告警觸發(fā)器 {T\mathop{{}}\nolimits_{{gt100}}} 可對集合 {M} 中的元素做『大于 100』的過濾
{T_{{gt100}}{ \left( {M} \right) }={ \left\{ {x \left| x \in M,x > 100\right. } \right\} }}
普通告警觸發(fā)器觸發(fā)的告警集合 {A}
A=T_{gt100} (M)
某個普通告警觸發(fā)器作用于某組監(jiān)控數(shù)值后,產(chǎn)生普通告警集合的過程如上文所述。nodata 告警觸發(fā)器的工作需要引入額外的全集 {U}
U=\{m_{1},m_{2},...,m_{n^ \prime }\}
nodata 告警觸發(fā)器 T_{nodata} 對集合 {M} 求絕對補集
T_{nodata}(M)=\left\{ x|x\in U,x\notin M \right\}
nodata 告警觸發(fā)器觸發(fā)的告警集合 A_{nodata}
A_{nodata}=T_{nodata}(M)=\bar{M}
一般來說,運維監(jiān)控場景下我們希望得到的完整告警集合 A_{all}
A_{all}=A\cup A_{nodata}
從上文看出,nodata 告警觸發(fā)器與普通告警觸發(fā)器最大的區(qū)別是前者需要引入『全集』U ,全集應當從監(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ā)器的難點之一在于全集 U 的獲取。在高動態(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)控指標求補集本身是一個開銷巨大的計算。另外全集 U 并不存在于時間序列存儲中(否則 nodata 告警就失去了客觀性),把全集 U 帶入 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 方法來獲得全集 U ,并改造 Prometheus ,將 nodata 計算卸載到存儲層。下文結合我們的實踐,并盡可能剝離我們特殊的業(yè)務場景,以 Prometheus 為例,介紹幾個相對通用的 nodata 告警觸發(fā)器的實現(xiàn)思路。

單一維度的 nodata

單一維度的 nodata 是最常見的 nodata 告警觸發(fā)器,Zabbix 等傳統(tǒng)監(jiān)控系統(tǒng)提供的也是這類 nodata 功能。以服務器 Load 監(jiān)控為例

存在服務器集合 H
{H={ \left\{ {h\mathop{{}}\nolimits_{{1}},h\mathop{{}}\nolimits_{{2}},...,h\mathop{{}}\nolimits_{{n}}} \right\} }}
存在監(jiān)控點 load
load(h)=cpu\ load5\ of\ h
便可以得到 Load 監(jiān)控指標 L
{L={ \left\{ l_{1}, l_{2}, ...,l_{i},...l_{n}|l_{i}=load(h_{i}) \right\} }}
當監(jiān)控指標 L 中存在失效的監(jiān)控點時,L 變?yōu)椴煌暾闹笜?L^ \prime
L^ \prime= { \left\{ l_{1}, l_{2}, ...,l_{i},...l_{n^ \prime}|l_{i}=load(h_{i}) \right\} }

L^ \prime \subseteq L

如果集合 H 在時間序列存儲之外(例如,存儲于 CMDB 中),就可以將 H 認定為 nodata 的全集 U,而 nodata 的告警集合 A_{nodata}L^ \prime 的絕對補集
A_{nodata}=\bar{L^ \prime}
在我們的實踐中,為了將 nodata 的補集運算卸載到 Prometheus,我們將 CMDB 作為一個監(jiān)控點,由 Prometheus 向 CMDB 拉取全集 H ,具體的指標類似于

nodata_hosts{host="h1", nodata="True"} 1
nodata_hosts{host="h2", nodata="True"} 1
nodata_hosts{host="h3", nodata="True"} 1
...

同時,假設 Load 監(jiān)控指標 L 類似于

host_cpu_load5{host="h1"} 42
host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
...

我們針對 L 生成如下告警觸發(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

這里有如下幾個關鍵點

  1. 將 CMDB 中的結構化信息轉儲到 Prometheus
  2. 使用 or 運算符做補集運算
  3. on() 的 label 為 nodata 的單一維度
  4. 普通指標與 nodata 指標在 nodata 維度上的查詢條件一致
  5. or 之后的表達式通過 * 1/0 轉為 +inf 以保證數(shù)值條件成立

我們通過將全集 H 轉為監(jiān)控指標,并通過 or 運算符做補集運算實現(xiàn)了單一維度的 nodata 告警觸發(fā)器。

多維度正交的 nodata

多維度正交 nodata 也是運維監(jiān)控場景中的常見需求。假設有 n 臺服務,每臺服務器上都運行相同的一組 m 個服務實例,那么對于服務的監(jiān)控指標,就需要在服務器和服務兩個維度上做 nodata 計算。問題描述如下

服務器集合
H={ \left\{ h_1,h_2,...h_n \right\} }
服務實例集合
S={\left\{s_1,s_2,...s_m \right\}}
監(jiān)控點 qps 用來獲取服務實例的每秒訪問次數(shù)
qps(h_i, s_j) = qps\ of\ service\ s_j\ on\ server\ h_i
得到 Qps 監(jiān)控指標 Q
Q={\left\{q_{1,1},q_{1,2},q_{2,1},... ,q_{i,j}, ..., q_{n,m}|q_{i,j}=qps(h_i, s_j) \right\}}
當監(jiān)控指標 Q 存在監(jiān)控點失效時,Q 變?yōu)椴煌暾谋O(jiān)控指標 Q^ \prime
Q={\left\{q_{1,1},q_{1,2},q_{2,1},... ,q_{i,j}, ..., q_{n^ \prime,m^ \prime}|q_{i,j}=qps(h_i, s_j) \right\}}

Q \subseteq Q^ \prime

與單維度類似,我們轉儲服務的全集指標

nodata_services{service="s1", nodata="True"} 1
nodata_services{service="s2", nodata="True"} 1
nodata_services{service="s3", nodata="True"} 1
...

假設監(jiān)控指標 Q 類似于

service_qps{host="h1", service="s1"} 42
service_qps{host="h1", service="s2"} 43
service_qps{host="h2", service="s1"} 44
...

則對于監(jiān)控指標 Q 的一個告警觸發(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 類似的關鍵點之外,這里還有如下幾個關鍵點:

  1. 重寫 Prometheus 的 absent 函數(shù)支持多 vector 的正交計算
  2. on() 的 label 為多個 nodata 的計算維度

我們通過與單一維度 nodata 類似的手法實現(xiàn)了支持多維度正交的 nodata 告警觸發(fā)器。

更一般的多維度 nodata

多維度 nodata 更一般的表述是多維度之間無法形成正交關系的情況。這些情況較難處理,需要 CMDB 與時間序列存儲建立較密切的聯(lián)系(而非簡單的數(shù)據(jù)轉儲),但如果可以處理得當,可以大大增強監(jiān)控系統(tǒng)的能力。

假設有服務器集合
H={ \left\{ h_1,h_2,...h_n \right\} }
服務器 h_i 上的服務實例集合
S_i={\left\{ s_{i,1},s_{i,2}, ..., s_{i,j}, ..., s_{i,m}|i \in H \right\}}
監(jiān)控指標 Q
Q={\left\{ q_{i,j}|i \in H, j \in S_i \right\}}
針對這個監(jiān)控指標的一個告警觸發(fā)器

service_qps{host=-"h1,h2,h3"service=-"s1,s2"} > 42

注意這里我們擴展了 PromQL 的語法,支持『列表匹配』=-,關于這個語法帶來的功能和性能的優(yōu)化本文暫不贅述。

為了對 Q 具有這類復雜多維度關系的指標,我們需要在 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 為
{\left\{ 2 \right\}}
service label 上缺失的列表項為 s2(并不包含 s1,因為 h1 下的 s1 有數(shù)值),對應的 nodata key 為
{\left\{ 1 \right\}}
并集的結果為
A_{nodata} = {\left\{ 2 \right\}} \cup {\left\{ 1\right\}} = {\left\{ 1,2\right\}}
這表示全集中 ID 為 1、2 的條目在時間序列中沒有數(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ù)分享。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容