Prometheus 與 nodata 告警

背景

隨著雲原生和高動態服務端的發展,在運維領域,以 Prometheus 為代表的現代時間序列存儲正在加速替代以 Zabbix 為代表的傳統監控系統。運維領域在享受時間序列技術發展紅利的同時,也面臨時間序列管理思路上的轉變和監控系統實際應用的上一些難點 —— nodata 告警便是其中之一。nodata 告警是傳統監控系統的必備功能,但卻缺席了幾乎所有現代時間序列存儲實踐,這給運維監控帶了諸多缺陷。本文嘗試分析其中原因,並給出一些可能的解決方法。

nodata 告警觸發器的特殊性與必要性

nodata 告警觸發器(Trigger)與普通告警觸發器相比具有原生的特殊性。普通告警觸發器的作用是對一組監控指標(Metric)的過濾,通常是基於數值大小的過濾。

如果存在如下表示的監控數值集合 MM

M={m1,m2,...,mn}M={m1,m2,...,mn}

告警觸發器 Tgt100Tgt100 可對集合 MM 中的元素做『大於 100』的過濾

Tgt100(M)={x|x∈M,x>100}Tgt100(M)={x|x∈M,x>100}

普通告警觸發器觸發的告警集合 AA 為

A=Tgt100(M)A=Tgt100(M)

某個普通告警觸發器作用於某組監控數值後,產生普通告警集合的過程如上文所述。nodata 告警觸發器的工作需要引入額外的全集 UU

U={m1,m2,...,mn′}U={m1,m2,...,mn′}

nodata 告警觸發器 TnodataTnodata 對集合 MM 求絕對補集

Tnodata(M)={x|x∈U,x∉M}Tnodata(M)={x|x∈U,x∉M}

nodata 告警觸發器觸發的告警集合 AnodataAnodata 為

Anodata=Tnodata(M)=¯MAnodata=Tnodata(M)=M¯

一般來說,運維監控場景下我們希望得到的完整告警集合 AallAall 為

Aall=A∪AnodataAall=A∪Anodata

從上文看出,nodata 告警觸發器與普通告警觸發器最大的區別是前者需要引入『全集』UU ,全集應當從監控系統之外獲得,以保證監控系統本身的有效性。

運維監控場景下,發生 nodata 告警最大的可能性是監控系統本身的失效,比如採集點失效或採集對象失效,在我們的實踐中,服務器意外下線、磁盤故障、服務崩潰等都會導致 nodata 告警;另外還有一類監控指標,這類指標以 nodata 為『正常狀態』,如 5xx code 產生的速率,在沒有 5xx code 產生時,雖然我們希望指標的數值為 0 (而不是 nodata) ,但在實踐中往往很難保證,對於這類指標有效性的保證,我們會在其他文章中詳細說明。

nodata 告警觸發器的難點之一在於全集 UU 的獲取。在高動態的服務端環境中,往往很難得到『全部服務器集合』、『全部 IP 地址集合』、『全部 Pod 集合』、『某服務全部運行實例集合』這樣的全集。所以,在數值型的監控採集之外,必須建設更加結構化的信息組織方式,並配以自動、半自動與人工相結合的信息維護方法。假如在結構化信息中很難方便準確地獲取『全部某某集合』這樣的信息,就無法制作真正有效地 nodata 告警觸發器。

nodata 告警觸發器的另一個難點是計算的開銷大。普通告警觸發器對指標的數值過濾,可以通過『帶條件的查詢』做到,這本質上是將告警計算的開銷一次性卸載到時間序列存儲系統中,而現代的時間序列存儲系統一般都支持這樣做。由上文對 nodata 告警觸發器的定義可以得到,nodata 的計算必須在數值過濾之前,也就是說 nodata 告警計算的計算對象是全量的監控指標,對全量監控指標求補集本身是一個開銷巨大的計算。另外全集 UU 並不存在於時間序列存儲中(否則 nodata 告警就失去了客觀性),把全集 UU 帶入 nodata 告警計算可會給時間序列存儲帶來額外的傳輸與計算壓力。

雖然有諸多困難,但 nodata 告警的重要性不言而喻。如果沒有 nodata 告警,監控指標的失效是靜默的,監控系統本身的有效性無法得到保證。對於雲原生的服務端環境,監控對象的動態化程度更高,雖然可以製作更加宏觀的監控指標(如某類 Pod 的總實例數),但 nodata 告警可以幫助我們獲悉更加微觀的服務端運行工況。

在 OpsMind 的實踐中,我們使用 CMDB 和經典的 CMDB 方法來獲得全集 UU ,並改造 Prometheus ,將 nodata 計算卸載到存儲層。下文結合我們的實踐,並儘可能剝離我們特殊的業務場景,以 Prometheus 為例,介紹幾個相對通用的 nodata 告警觸發器的實現思路。

單一維度的 nodata

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

存在服務器集合 HH

H={h1,h2,...,hn}H={h1,h2,...,hn}

存在監控點 loadload

load(h)=cpu load5 of hload(h)=cpu load5 of h

便可以得到 Load 監控指標 LL

L={l1,l2,...,li,...ln|li=load(hi)}L={l1,l2,...,li,...ln|li=load(hi)}

當監控指標 LL 中存在失效的監控點時,LL 變為不完整的指標 L′L′

L′={l1,l2,...,li,...ln′|li=load(hi)}L′={l1,l2,...,li,...ln′|li=load(hi)}

L′⊆LL′⊆L

如果集合 HH 在時間序列存儲之外(例如,存儲於 CMDB 中),就可以將 HH 認定為 nodata 的全集 UU,而 nodata 的告警集合 AnodataAnodata 為 L′L′ 的絕對補集

Anodata=¯L′Anodata=L′¯

在我們的實踐中,為了將 nodata 的補集運算卸載到 Prometheus,我們將 CMDB 作為一個監控點,由 Prometheus 向 CMDB 拉取全集 HH ,具體的指標類似於

<code>nodata_hosts{host="h1", nodata="True"} 1
nodata_hosts{host="h2", nodata="True"} 1
nodata_hosts{host="h3", nodata="True"} 1
.../<code>

同時,假設 Load 監控指標 LL 類似於

<code>host_cpu_load5{host="h1"} 42
host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
.../<code>

我們針對 LL 生成如下告警觸發器

<code>host_cpu_load5{host=~"h.*"} > 42/<code>

則卸載 nodata 之後的運算可表示為

<code>host_cpu_load5{host=~"h.*"} or on(host) nodata_hosts{host=~"h.*"} * 1/0 > 42/<code>

此告警觸發器可以生成如下的告警信息

<code>host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
host_cpu_load5{host="hx", nodata="True"} +inf/<code>

這裡有如下幾個關鍵點

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

我們通過將全集 HH 轉為監控指標,並通過 or 運算符做補集運算實現了單一維度的 nodata 告警觸發器。

多維度正交的 nodata

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

服務器集合

H={h1,h2,...hn}H={h1,h2,...hn}

服務實例集合

S={s1,s2,...sm}S={s1,s2,...sm}

監控點 qpsqps 用來獲取服務實例的每秒訪問次數

qps(hi,sj)=qps of service sj on server hiqps(hi,sj)=qps of service sj on server hi

得到 Qps 監控指標 QQ

Q={q1,1,q1,2,q2,1,...,qi,j,...,qn,m|qi,j=qps(hi,sj)}Q={q1,1,q1,2,q2,1,...,qi,j,...,qn,m|qi,j=qps(hi,sj)}

當監控指標 QQ 存在監控點失效時,QQ 變為不完整的監控指標 Q′Q′

Q={q1,1,q1,2,q2,1,...,qi,j,...,qn′,m′|qi,j=qps(hi,sj)}Q={q1,1,q1,2,q2,1,...,qi,j,...,qn′,m′|qi,j=qps(hi,sj)}

Q⊆Q′Q⊆Q′

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

<code>nodata_services{service="s1", nodata="True"} 1
nodata_services{service="s2", nodata="True"} 1
nodata_services{service="s3", nodata="True"} 1
.../<code>

假設監控指標 QQ 類似於

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

則對於監控指標 QQ 的一個告警觸發器類似於

<code>service_qps{host=~"h.*", service=~"s.*"} > 42/<code>

卸載 nodata 計算之後的告警觸發器

<code>service_qps{host=~"h.*", service=~"s.*"} or on(host, service) absent(nodata_hosts{host=~"h.*"}, nodata_services{service=~"s.*"}) * 1/0 > 42/<code>

此告警觸發器可以生成如下告警信息

<code>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
.../<code>

除了與單一維度 nodata 類似的關鍵點之外,這裡還有如下幾個關鍵點:

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

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

更一般的多維度 nodata

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

假設有服務器集合

H={h1,h2,...hn}H={h1,h2,...hn}

服務器 hihi 上的服務實例集合

Si={si,1,si,2,...,si,j,...,si,m|i∈H}Si={si,1,si,2,...,si,j,...,si,m|i∈H}

監控指標 QQ

Q={qi,j|i∈H,j∈Si}Q={qi,j|i∈H,j∈Si}

針對這個監控指標的一個告警觸發器

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

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

為了對 QQ 具有這類複雜多維度關係的指標,我們需要在 CMDB 中建立服務器與服務實例的關係表

ID服務器服務實例0h1s11h1s22h2s13h3s1

通過 CMDB 中的關係表,將 nodata 卸載後的告警觸發器為

<code>service_qps{host=-"h1[0,1],h2[2],h3[3]"service=-"s1[0,2,3],s2[1,3]"} > 42/<code>

這裡我們擴展了列表匹配的語法,支持 "nodata key" [0,1],表示某個列表項在全集中的 ID。Promethues 查詢時會針對每個 label 計錄缺失列表項的 nodata key,並將多個 label 記錄下來的 nodata key 求並集。

對本例來說,假設 h1 上的 s2 和 h2 上的 s1 監控失效,則 host label 缺失的列表項為 h2(並不包含 h1,因為 h1 下的 s1 有數值),對應的 nodata key 為

{2}{2}

service label 上缺失的列表項為 s2(並不包含 s1,因為 h1 下的 s1 有數值),對應的 nodata key 為

{1}{1}

並集的結果為

Anodata={2}∪{1}={1,2}Anodata={2}∪{1}={1,2}

這表示全集中 ID 為 11、22 的條目在時間序列中沒有數值,需要做 nodata 告警,與我們的假設相符。

結語

本文分析了現代時間序列管理方案中 nodata 的特殊性與必要性,並以 Prometheus 為例嘗試給出幾個解決方案。可以看到,現代時間序列管理方案中的 nodata 處理是十分複雜的,我們認為這和雲原生環境下其他的監控難題一樣具有原生複雜性,這只是雲原生給運維帶來的諸多根本性挑戰的外在表現形式,這些挑戰需要系統性地分析和解決。OpsMind 為應對雲原生的挑戰做了大量的技術研判和產品包裝,我們將在其他文章中與大家陸續分享。


分享到:


相關文章: