新鮮開源:基於Prometheus的企業監控平臺設計與實現

本文由 dbaplus 社羣授權轉載。

大家好,今天跟大家分享的是我們360基於Prometheus開發的監控平臺,名字叫“哆啦A夢”,目前已經在GitHub上開源了,這次我主要會分享以下這些內容:

  1. Prometheus在360搜索雲平臺的應用及Alertmanager的痛點
  2. 哆啦A夢的設計思路
  3. 哆啦A夢的功能介紹
  4. 哆啦A夢的快速部署
  5. 報警恢復聚合方案
  6. 標籤匹配篩選

一、Prometheus的應用及Alertmanager的痛點

我們360搜索事業部雲平臺的Prometheus全部使用容器部署,採用聯邦架構,容器監控指標和物理機監控指標全部使用Prometheus進行採集。在使用過程中,我們發現Prometheus自帶的報警模塊Alertmanager有些地方不是很完善,使用起來不夠靈活,具體如下:

  1. 無法動態加載監控規則,需要修改配置文件;
  2. 使用Alertmanager時,需要修改Prometheus的配置文件將Alertmanager與Prometheus相關聯;
  3. 無法實現報警升級,不支持獲取動態值班組,標籤匹配不能動態配置(需要修改配置文件)。

總而言之就是,配置不夠方便靈活,需要頻繁修改配置文件,學習成本高。

二、哆啦A夢的設計思路

針對上述這些問題,我們開發並開源了哆啦A夢報警平臺(https://github.com/Qihoo360/doraemon),具體架構如下圖所示:

  • Rule Engine:從Alert Gateway動態拉取報警規則,並下發到多個Prometheus Server進行計算,與此同時接收從Prometheus Server發來的報警並轉發給Alert Gateway。
  • Alert Gateway:接收從Rule Engine發送的報警,並根據報警策略聚合發送報警信息。
  • Web UI:用於運維人員添加報警規則,創建報警策略、維護組,進行報警確認或者查看歷史報警。

上圖左半邊是原生的Prometheus部署在很多個機器上面,從exporters採集了監控數據存儲到TSDB裏面,這整個部分就是原生的Prometheus。

而右半邊就是我們開發的哆啦A夢,它將我們在Web-UI配置的監控規則存入數據庫,然後通過Rule Engine從Web後端的Http接口週期性地拉取Prometheus的報警規則,並通過Prometheus的Query API下發到多臺Prometheus Server進行計算。那些觸發報警的結果會由Rule Engine再轉發給Web後端,Web後端會將這些報警存在MySQL數據庫中。之後我們的報警網關會用一個專門的協程來每分鐘掃描一次數據庫,根據規則將觸發的報警以相應的形式發送給用戶。對於360內部來說,是支持短信、電話,以及內部的藍信報警方式。對於開源給外部用戶的話,是通過Web HOOK將報警以HTTP POST請求的方式放送給指定的Web Server,這個Web Server需要外部用戶自己來設計實現將報警發送到指定終端。

三、哆啦A夢的功能

接下來因爲會講到哆啦A夢的功能,所以需要先介紹一些相關術語:

  • 報警規則:與Prometheus中的報警規則概念相同。
  • 數據源:Prometheus Server的URL,由Rule Engine將報警規則下發至該URL進行計算。
  • 報警接收組:由多個報警接收人組成的組。
  • 值班組:和報警接收組類似,但是它是動態的從接口中獲取組成員的列表。
  • 報警延遲:報警觸發一段時間後纔將報警發送給接收人。
  • 報警週期:報警發送的週期。
  • 報警計劃:由多條報警策略組成的集合。
  • 報警方式:對於內部用戶,可以通過藍信、短信和電話的方式進行報警。非內部用戶可以採用HOOK的方式將報警轉發到自定義的Web Server進行處理。
  • 報警策略:一條報警策略包含報警延遲、報警週期、報警時間段、報警接收組、值班組以及報警方式等配置信息。
  • 報警確認:如果需要短時間的暫停報警,可以通過勾選相應報警並填寫暫停時長來確認報警。
  • 維護組:如果希望屏蔽一些固定時間段內某些特定機器的報警,可以通過配置報警維護組策略來實現。

1、創建報警計劃

我們先創建一個計劃,這個計劃只需要填寫一個名稱和描述,然後在這個計劃下添加具體的報警策略。

一個報警計劃下面可以添加多個報警策略,通過這種方式既可以實現報警的定向發送也可以實現報警的升級。例如下圖的報警計劃實現了將含有標籤idc=beijing的報警發給op1,將含有標籤idc!=beijing的報警發送給op2,並且將所有超過60分鐘還沒有被確認的報警以電話形式通知leader。

以下是對上圖術語的具體解釋:

  • 報警時間段:每天發送報警的時段。
  • 報警延遲:報警觸發多長時間後纔開始發送報警,可用於實現報警升級。
  • 報警週期:每隔多少分鐘發送一次報警。
  • 報警用戶:報警接收人,可以輸入多個報警接收人。
  • 值班組:從接口動態獲取報警值班人。
  • 報警用戶組:可以創建報警用戶組,一個報警用戶組包含多個報警接收人。
  • Filter表達式:通過label來過濾要發送的報警。支持“=”,“!=”,“&”,“|”,“(”,“)”這幾種運算符號,並且在保存Filter表達式之前會自動校驗表達式的合法性,對於不合法的表達式會給出提示。例如只發送來自北京或上海機房並且名稱是搜索的報警,對應的Filter表達式就是“name=sousuo&(idc=beijing|idc=shanghai)”。
  • 報警方式:支持HOOK方式將報警以HTTP POST請求的形式發送至指定報警網關。

在創建完報警計劃後,需要創建Prometheus的數據源,這個比較簡單,只需要填寫一個Prometheus的名稱,還有把Prometheus暴露的Query API的url填寫上就可以了。有了報警計劃和數據源之後,就可以創建一個報警規則,然後將報警規則關聯到數據源,這樣Rule Engine就會把這個規則下發到指定的Prometheus上進行計算。計算的結果會由Web後端定時掃描,根據我們剛纔創建的報警計劃來發送給指定的人。

2、添加報警規則

以下是添加報警規則需要填寫的一些參數的解釋,其實跟Prometheus官方文檔上提到的是一致的 :

  • 監控指標:對應Prometheus的alerting rules中的“expr”。
  • 報警閾值:expr的閾值。
  • 持續時間:對應Prometheus的alerting rules中的“for”。
  • 標題:對應Prometheus的alerting rules中的“summary”。
  • 描述:對應Prometheus的alerting rules中的“description”。
  • 數據源:關聯到前面創建的Prometheus Server的信息,即將這條監控規則下發到該Prometheus進行計算。
  • 策略:關聯到前面創建的報警計劃。

3、報警聚合

哆啦A夢所發送出來的報警都是從報警規則的維度進行聚合的,例如某個報警規則所關聯的報警計劃下面有兩個報警策略,一條策略的報警週期是5分鐘,則該條規則下的所有報警會每5分鐘聚合一次併發送,另一條策略的報警週期是10分鐘,則相應的報警會每10分鐘聚合一次併發送。對於報警恢復的恢復信息會每分鐘聚合一次併發送。對於同一條報警,根據報警策略的不同會以不同的報警延時,不同的報警週期聚合後發送給不同的接收者。

例如這樣的報警策略:報警持續5分鐘後開始發出第一條報警,每5分鐘發一次。假設在第0、1、2、3、4分鐘的每一分鐘都有2臺不同的機器觸發了rule A的報警,如果不聚合則在5-9分鐘每分鐘都會發出一個報警,每條報警包含兩臺機器的信息。這樣當出現多條rule下的多臺機器一起報警的時候,會出現在短時間內多條rule的報警交替出現,這樣對運維人員不友好,不好辨別這一分鐘裏到底是哪幾條rule觸發的報警。

而聚合後,將在第5分鐘將第0分鐘觸發的報警發出,第10分鐘將第0、1、2、3、4分鐘的報警聚合發出,即每五分鐘聚合一次rule A的報警。如果某個報警在第1分鐘到第9分鐘內觸發,並且在這期間又恢復了,則用戶不會收到報警信息以及報警恢復信息。對於同一條報警,由於報警策略不同,會導致有的用戶可能收到過該條報警,而有的用戶沒有,系統會保證該條報警的恢復信息只會發送給已經收到過該報警的用戶。對於沒有發出的報警可以在報警歷史記錄中查看。

4、報警確認

有兩種方式進行報警確認,一種是點擊報警信息中的確認鏈接,這種方式是從報警規則的維度進行報警確認的(因爲報警是以報警規則的維度聚合發送的)。例如,在報警策略中配置了報警延時爲5分鐘,只接收idc=zzzc和idc=shbt的報警,則點開報警確認鏈接只能看到以及確認滿足該報警策略的報警。也就是說,不同的報警策略所展示的報警確認頁面的內容也是不同的:

另一種報警確認方式是從label的維度進行報警確認,比如IP爲10.0.0.1的機器宕機了,觸發了多條報警規則的報警,此時從報警規則的維度進行報警確認效率很低,但如果從label的維度進行報警確認就會非常方便,因爲含有“instance=10.0.0.1:9090”的報警都是該機器產生的報警我們可以一次性的確認。下圖是從label的維度進行報警確認:

5、維護組

有些時候可能會有大批量的機器處於維護狀態,或者某些機器在一段時間內出現故障,需要屏蔽來自這些機器的報警,此時就可以將這些機器的instance標籤的值(不含端口號)作爲機器的唯一識別信息加入維護組。

這裏我們看一下維護組都需要填寫哪些信息:

  • 維護時間段:屏蔽報警的時間段。
  • 維護月:屏蔽報警的月份。
  • 維護日期:在維護月份的哪些天對報警進行屏蔽。
  • 有效期:維護規則的有效期(過了該時間維護規則自動失效)。
  • 機器列表:instance標籤的值(去掉端口號),通常爲機器IP。

6、歷史報警

在“歷史報警查看”頁面可以看到所有的歷史報警信息,包括那些沒有被髮送只是抖動了一下(沒有到報警聚合點就已經自動恢復的報警)的報警,這裏也會把它展示出來。

四、哆啦A夢的快速部署

哆啦A夢提供了兩種部署方式,一種是基於docker-compose本地部署,由於沒有提供額外的高可用機制,因此這種方式僅用於本地測試;另一種方式是基於Kubernetes集羣部署,用於生產環境。兩種部署方式都非常簡單。

1、docker-compose部署

1)從GitHub下載代碼。

2)將deployments/docker-compose/conf/config.js中的“localhost”替換爲本機物理網卡IP或域名,端口號保持不變。

3)進入deployments/docker-compose/目錄下執行docker-compose up -d即可,然後就可以通過http://本機ip:32000訪問Doraemon。

2、基於Kubernetes部署

1)從GitHub下載代碼。

2)修改deployments/kubernetes/doraemon.yml,配置好MySQL數據庫信息,並將“doraemon-ui”這個configmap中的“baseURL”的“nodeip”替換爲Kubernetes集羣中任意結點的結點IP。

3)執行kubectl apply -f deployments/kubernetes/doraemon.yml,之後通過http://nodeip:32000來訪問Doraemon。

五、報警恢復聚合方案

1、報警聚合

我們的報警聚合是希望能儘量利用數據庫中已有的信息來實現目標,其中需要注意的地方有:

  • 一條報警可能有多個接收者,而每個接收者的報警延遲以及報警週期都是不同的。
  • 每分鐘發送報警的線程和接收來自計算引擎的報警恢復信息是異步的。

2、報警發送聚合方案

由於目標是按照rule來聚合報警,因此我們需要一個rule級別的計數器,又因爲一條rule可以有多個報警規則,並且它們的報警延遲和報警週期各不相同,因此針對每一個rule和每一個報警規則創建一個計數器rulecount,並將它保存在一個map中,即map[[2]int{ruleid,start}]=rulecount,其中start表示報警規則的報警延遲。於是報警聚合發送流程如下:

3、rulecount具體實現與優化

爲了更快的獲取到rulecount的值,選擇map來存儲。然而隨着rule的數量增加以及報警規則的增加,map佔用的內存將會越來越多(golang中即便通過delete刪除map中的元素也不會真正釋放內存,僅僅是將原來的位置設爲empty),可以採用如下方式來優化:

4、報警恢復聚合方案

報警聚合還觸及到報警恢復的問題,相當於說當我有一條報警恢復了,這個報警恢復到底該發送給哪些人。針對這個點,我們藉助了之前說到的兩個計數器,一個是count,一個是rulecount。通過這兩個計數器的關係,來計算出某個報警恢復是否需要發送給某個指定的用戶。

主要分成三種情況:

  • 當count-start>=period時,報警信息肯定已經發出,因此需要發送這條報警恢復信息。
  • 當0<=count-start<period時,由上圖可知,當n∙period∈[rulecount-(count-start),rulecount]時報警信息已經發出,因此需要發送該報警恢復信息,即(rulecount-(count-start))%period=0或者rulecount-((rulecount-(count-start))//period) ∙period>=period。
  • 當count-start<0時,沒有發出報警。

整體結合在一起,我們綜合得到了這麼一個報警聚合的發送方案,以及一個報警聚合恢復信息的發送方案。

5、特殊情況下的最壞影響

如果在報警過程中動態的修改報警規則的報警延遲和報警週期會有什麼樣的影響?

  • 報警發生:影響下一次報警的報警時間,之後恢復正常。
  • 報警恢復:對已經處於報警狀態的報警可能會丟失報警恢復,對於修改之後觸發的新的報警沒有影響。

六、標籤匹配篩選

新監控平臺最初有一個計劃是通過標籤匹配表達式來實時篩選符合條件的報警,將指定標籤的報警發給指定的人。例如篩選滿足如下條件的報警:

host!=H1 & (idc=SHYC | idc=ZZZC) | port=80

目標:

  • 能夠校驗表達式的正確性,如果表達式不正確,能夠返回錯誤原因。
  • 表達式的計算時間複雜度儘可能低。

方案:當用戶在前端錄入表達式後,由後端進行有效性驗證,如果驗證通過就存入數據庫,否則將錯誤原因返回前端。

我們借用了逆波蘭表達式的概念,又叫做後綴表達式。逆波蘭表示法是波蘭邏輯學家J・盧卡西維茲(J・Lukasewicz)在1929年首先提出的一種表達式的表示方法,它是一種沒有括號,並嚴格遵循“從左到右”運算的後綴式表達方法。

舉例:

我們的標籤匹配算法具體流程如下:

1)當用戶在前端輸入表達式後,由後端將表達式轉換爲逆波蘭表達式,在轉換表達式的過程中同時驗證表達式的正確性,如果能成功將表達式轉換說明原表達式是有效的表達式,並將轉換後的逆波蘭表達式存在數據庫中,否則返回轉換錯誤原因。

2)監控平臺從數據庫中取出表達式並計算。

以下舉一個更爲具體的例子:

令“&”表示“與”,“|”表示“或”,“=”表示“等於”,“!=”表示“不等於”,以host!=H1 & (idc=SHYC | idc=ZZZC) | port=80爲例,由於“與”“或”運算都是二元運算符,可以考慮將其解析爲一棵二叉樹,然後對其進行後序遍歷可以得到逆波蘭表達式:

host!=H1 idc=SHYC idc=ZZZC | & port=80 |

該算法的時間複雜度爲O(n ∙ log(n)),在增加空間複雜度的情況下時間複雜度可以優化至O(n)。

以上就是我們標籤表達式的一個實現方式。

Q&A

Q1:怎麼設置不分組、不聚合?

A: 當前默認會聚合,沒有提供不聚合的設置。

Q2:這樣是否會導致報警不及時、影響處理時間?

A: 假設報警週期是t,最多延遲t-1分鐘報出來。

Q3:關於告警聚合,支不支持這種情況:比如,機房整個網絡故障了,就不再發該機房下每個服務器失聯的告警,只發送機房網絡故障這一條報警。

A: 當前不支持報警收斂。

Q4:好像不能批量配置告警?比如我一臺主機可能有100個os指標需要配置告警,那我有n臺機器,我需要配置n*100次?

A: 不用,一個指標配一次即可。

Q5:告警抑制怎麼配置呢?

A: 當前不支持告警抑制。

Q6:alertmanager自帶的rule級別的沉默現在支持嗎?

A: 當前支持主機維度的靜默,可以在“維護組”裏設置。

Q7:比如某個交換機掛了,下面的機器報警比較多。這種怎麼快速靜默?

A: 直接在“維護組”中把該交換機下所有機器的ip(或主機名)加入維護組即可。

Q8:對於Prometheus的配置文件有前臺頁面嗎?如何同步前臺頁面配置到後臺配置文件?

A: 沒有提供修改Prometheus配置文件的頁面。

Q9:哆啦A夢動態加載配置怎麼實現的?

A: 通過修改Prometheus的rule manager模塊實現的。

Q10:容器支持不同檔位內存的報警嗎?A容器4G報警,B容器6G報警,C容器8G內存報警。

A: 可以,通過使用不同的label對應不同的rule的閾值。

Q11:出現告警後,有沒有自動化處理?有的話是如何實現的?比如說日誌文件較多,造成的磁盤不足,自動壓縮下文件,這種自動化處理。

A: 當前沒有實現故障自愈的功能。

Q12:Prometheus是部署在K8S集羣外好,還是集羣內好呢?如何解決Prometheus內存佔用過大問題?

A: 需要具體情況具體分析。

Q13:故障自愈那個是否可以說得詳細些?

A: 可以通過執行特定腳本邏輯來實現故障自愈。

作者介紹

劉恆滔,奇虎360搜索事業部,資深開發工程師。

本文轉載自公衆號(ID:)。

原文鏈接

https://mp.weixin.qq.com/s?__biz=MzI4NTA1MDEwNg==&mid=2650791880&idx=1&sn=4c14c21f58f16e3e22f29ee181cfe77b&chksm=f3f96a5dc48ee34bcd5589b6b456131c1046b0b8d559dc0d5af47d9cfb1a18719615df24f209&scene=27#wechat_redirect

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章