普羅米修斯聯邦機制


https://dbaplus.cn/news-72-1462-1.html

 

最近參與的幾個項目,無一例外對監控都有極強的要求,需要對項目中各組件進行詳細監控,如服務端API的請求次數、響應時間、到達率、接口錯誤率、分布式存儲中的集群IOPS、節點在線情況、偏移量等。

 

比較常見的方式是寫日志,將日志采集到遠端進行分析和繪圖,或寫好本地監控腳本進行數據采集后,通過監控系統客戶端push到監控系統中進行打點。基本上我們需要的都能覆蓋,但仍然有一些問題在使用上不太舒服,如在大規模請求下日志采集和分析的效率比較難控制,或push打點的粒度和緯度以及查詢不夠靈活等。

 

后來在同事對《Google SRE》這本書中的一些運維思想進行一番安利后,抱着試一試的態度,開始嘗試使用Prometheus做為幾個項目的監控解決方案。

 

Prometheus的特點   

 

  • 多維數據模型(時序數據由 metric 名和一組K/V標簽構成)。

  • 靈活強大的查詢語句(PromQL)。

  • 不依賴存儲,支持local和remote(OpenTSDB、InfluxDB等)不同模型。

  • 采用 HTTP協議,使用Pull模式采集數據。

  • 監控目標,可以采用服務發現或靜態配置的方式。

  • 支持多種統計數據模型,圖形化友好(Grafana)。

 

數據類型   

 

 Counter

 

Counter表示收集的數據是按照某個趨勢(增加/減少)一直變化的。

 

 Gauge

 

Gauge表示搜集的數據是瞬時的,可以任意變高變低。

 

 Histogram

 

Histogram可以理解為直方圖,主要用於表示一段時間范圍內對數據進行采樣,(通常是請求持續時間或響應大小),並能夠對其指定區間以及總數進行統計。

 

 Summary

 

Summary和Histogram十分相似,主要用於表示一段時間范圍內對數據進行采樣,(通常是請求持續時間或響應大小),它直接存儲了 quantile 數據,而不是根據統計區間計算出來的。

 

在我們的使用場景中,大部分監控使用Counter來記錄,例如接口請求次數、消息隊列數量、重試操作次數等。比較推薦多使用Counter類型采集,因為Counter類型不會在兩次采集間隔中間丟失信息。

 

一小部分使用Gauge,如在線人數、協議流量、包大小等。Gauge模式比較適合記錄無規律變化的數據,而且兩次采集之間可能會丟失某些數值變化的情況。隨着時間周期的粒度變大,丟失關鍵變化的情況也會增多。

 

還有一小部分使用Histogram和Summary,用於統計平均延遲、請求延遲占比和分布率。另外針對Historgram,不論是打點還是查詢對服務器的CPU消耗比較高,通過查詢時查詢結果的返回耗時會有十分直觀的感受。

 

時序數據-打點-查詢   

 

我們知道每條時序數據都是由 metric(指標名稱),一個或一組label(標簽),以及float64的值組成的。

 

標准格式為 {

 

例如:

 

rpc_invoke_cnt_c{code="0",method="Session.GenToken",job="Center"} 5

rpc_invoke_cnt_c{code="0",method="Relation.GetUserInfo",job="Center"} 12

rpc_invoke_cnt_c{code="0",method="Message.SendGroupMsg",job="Center"} 12

rpc_invoke_cnt_c{code="4",method="Message.SendGroupMsg",job="Center"} 3

rpc_invoke_cnt_c{code="0",method="Tracker.Tracker.Get",job="Center"} 70

 

這是一組用於統計RPC接口處理次數的監控數據。

 

其中rpc_invoke_cnt_c為指標名稱,每條監控數據包含三個標簽:code 表示錯誤碼,service表示該指標所屬的服務,method表示該指標所屬的方法,最后的數字代表監控值。

 

針對這個例子,我們共有四個維度(一個指標名稱、三個標簽),這樣我們便可以利用Prometheus強大的查詢語言PromQL進行極為復雜的查詢。

 

PromQL(Prometheus Query Language) 是 Prometheus 自己開發的數據查詢 DSL 語言,語言表現力非常豐富,支持條件查詢、操作符,並且內建了大量內置函,供我們針對監控數據的各種維度進行查詢。

 

我們想統計Center組件Router.Logout的頻率,可使用如下Query語句:

 

rate(rpc_invoke_cnt_c{method="Relation.GetUserInfo",job="Center"}[1m])

 

 

或者基於方法和錯誤碼統計Center的整體RPC請求錯誤頻率:

 

sum by (method, code)(rate(rpc_invoke_cnt_c{job="Center",code!="0"}[1m]))

 

 

如果我們想統計Center各方法的接口耗時,使用如下Query語句即可:

 

rate(rpc_invoke_time_h_sum{job="Center"}[1m]) / rate(rpc_invoke_time_h_count{job="Center"}[1m])

 

 

更多的內建函數這里不展開介紹了。函數使用方法和介紹可以詳細參見官方文檔中的介紹:https://Prometheus.io/docs/querying/functions/

 

另外,配合查詢,在打點時metric和labal名稱的定義也有一定技巧。

 

比如在我們的項目中:

  • rpc_invoke_cnt_c 表示rpc調用統計

  • api_req_num_cv 表示httpapi調用統計

  • msg_queue_cnt_c 表示隊列長度統計

 

盡可能使用各服務或者組件通用的名稱定義metric然后通過各種lable進行區分。

 

最開始我們的命名方式是這樣的,比如我們有三個組件center、gateway、message。RPC調用統計的metric相應的命名成了三個:

  • center_rpc_invoke_cnt_c

  • gateway_rpc_invoke_cnt_c

  • message_rpc_invoke_cnt_c

 

這種命名方式,對於各組件的開發同學可能讀起來會比較直觀,但是在實際查詢過程中,這三個metric相當於三個不同的監控項。

 

例如我們查詢基於method統計所有組件RPC請求錯誤頻率,如果我們使用通用名稱定義metric名,查詢語句是這樣的:

 

sum by (method, code) (rate(rpc_invoke_cnt_c{code!="0"}[1m]))

 

但如果我們各個組件各自定義名稱的話,這條查詢需要寫多條。雖然我們可以通過 {__name__=~".*rpc_invoke_cnt_c"} 的方式來規避這個問題,但在實際使用和操作時體驗會差很多。

 

例如在Grafana中,如果合理命名相對通用的metric名稱,同樣一個Dashboard可以套用給多個相同業務,只需簡單修改template匹配一下label選擇即可。不然針對各個業務不同的metric進行針對性的定制繪圖也是一個十分痛苦的過程。

 

同時通過前面的各類查詢例子也會發現,我們在使用label時也針對不同的含義進行了區分如 method=GroupJoin|GetUserInfo|PreSignGet|... 來區分調用的函數方法,code=0|1|4|1004|...來區分接口返回值,使查詢的分類和結果展示更加方便直觀,並且label在Grafana中是可以直接作為變量進行更復雜的模版組合。

 

更多的metric和label相關的技巧可以參考官方文檔-https://Prometheus.io/docs/practices/naming/

 

由於最近參與的幾個項目深度使用公司內部的配置管理服務gokeeper,雖然不是Prometheus原生支持,但是通過簡單適配也是同樣能滿足服務發現的需求。我們最終選擇通過file_sd_config進行服務發現的配置。

 

 

 

file_sd_config 接受json格式的配置文件進行服務發現。每次json文件的內容發生變更,Prometheus會自動刷新target列表,不需要手動觸發reload操作。所以針對我們的gokeeper編寫了一個小工具,定時到gokeeper中采集服務分類及分類中的服務器列表,並按照file_sd_config的要求生成對應的json格式。

 

 

 

下面是一個測試服務生成的json文件樣例。

 

 

 

 

 

[

 

    {

 

        "targets": [

 

            "10.10.10.1:65160",

 

            "10.10.10.2:65160"

 

        ],

 

        "labels": {

 

            "job":"Center",

 

            "service":"qtest"

 

        }

 

    },

 

    {

 

        "targets": [

 

            "10.10.10.3:65110",

 

            "10.10.10.4:65110"

 

        ],

 

        "labels": {

 

            "job":"Gateway",

 

            "service":"qtest"

 

        }

 

    }

 

]

 

 

 

Prometheus配置文件中將file_sd_configs的路徑指向json文件即可。

 

 

 

 

 

-job_name: 'qtest'

 

    scrape_interval: 5s

 

    file_sd_configs:

 

      - files: ['/usr/local/prometheus/qtestgroups/*.json']

 

 

 

如果用etcd作為服務發現組件也可以使用此種方式,結合confd配合模版和file_sd_configs可以極大地減少配置維護的復雜度。只需要關注一下Prometheus后台采集任務的分組和在線情況是否符合期望即可。社區比較推崇Consul作為服務發現組件,也有非常直接的內部配置支持。

 

 

 

感興趣的話可以直接參考官方文檔進行配置和測試-https://Prometheus.io/docs/operating/configuration/#<consul_sd_config>

 

 

高可用目前暫時沒有太好的方案。官方給出的方案可以對數據做Shard,然后通過federation來實現高可用方案,但是邊緣節點和Global節點依然是單點,需要自行決定是否每一層都要使用雙節點重復采集進行保活。

 

使用方法比較簡單,例如我們一個機房有三個Prometheus節點用於Shard,我們希望Global節點采集歸檔數據用於繪圖。首先需要在Shard節點進行一些配置

 

如果用etcd作為服務發現組件也可以使用此種方式,結合confd配合模版和file_sd_configs可以極大地減少配置維護的復雜度。只需要關注一下Prometheus后台采集任務的分組和在線情況是否符合期望即可。社區比較推崇Consul作為服務發現組件,也有非常直接的內部配置支持。

 

感興趣的話可以直接參考官方文檔進行配置和測試-https://Prometheus.io/docs/operating/configuration/#<consul_sd_config>

 

高可用   

 

高可用目前暫時沒有太好的方案。官方給出的方案可以對數據做Shard,然后通過federation來實現高可用方案,但是邊緣節點和Global節點依然是單點,需要自行決定是否每一層都要使用雙節點重復采集進行保活。

 

使用方法比較簡單,例如我們一個機房有三個Prometheus節點用於Shard,我們希望Global節點采集歸檔數據用於繪圖。首先需要在Shard節點進行一些配置。

 

Prometheus.yml:

 

global:

  external_labels:

  slave: 0 #給每一個節點指定一個編號 三台分別標記為0,1,2

 

rule_files:

  - node_rules/zep.test.rules  #指定rulefile的路徑

 

scrape_configs:

  - job_name: myjob

    file_sd_configs:

    - files: ['/usr/local/Prometheus/qtestgroups/*.json']

    relabel_configs:

    - source_labels: [__address__]

      modulus:       3   # 3節點

      target_label:  __tmp_hash

      action:        hashmod

    - source_labels: [__tmp_hash]

      regex:         ^0$ # 表示第一個節點

      action:        keep

 

編輯規則文件:

 

 

node_rules/zep.test.rules:

job:rpc_invoke_cnt:rate:1m=rate(rpc_invoke_cnt_c{code!="0"}[1m])

 

在這里job:rpc_invoke_cnt:rate:1m 將作為metric名,用來存放查詢語句的結果。

 

在Global節點Prometheus.yml也需要進行修改。

 

 -job_name: slaves

    honor_labels: true

    scrape_interval: 5s

    metrics_path: /federate

    params:

      match[]:

         - '{__name__=~"job:.*"}'

    static_configs:

      - targets:

         - 10.10.10.150:9090

         - 10.10.10.151:9090

         - 10.10.10.152:9090

 

在這里我們只采集了執行規則后的數據用於繪圖,不建議將Shard節點的所有數據采集過來存儲再進行查詢和報警的操作。這樣不但會使Shard節點計算和查詢的壓力增大(通過HTTP讀取原始數據會造成大量IO和網絡開銷),同時所有數據寫入Global節點也會使其很快達到單Prometheus節點的承載能力上限。

 

另外部分敏感報警盡量不要通過global節點觸發,畢竟從Shard節點到Global節點傳輸鏈路的穩定性會影響數據到達的效率,進而導致報警實效降低。例如服務updown狀態,API請求異常這類報警我們都放在s hard節點進行報警。

 

此外我們還編寫了一個實驗性質的Prometheus Proxy工具,代替Global節點接收查詢請求,然后將查詢語句拆解,到各shard節點抓取基礎數據,然后再在Proxy這里進行Prometheus內建的函數和聚合操作,最后將計算數據拋給查詢客戶端。這樣便可以直接節約掉Global節點和大量存儲資源,並且Proxy節點由於不需要存儲數據,僅接受請求和計算數據,橫向擴展十分方便。

 

當然問題還是有的,由於每次查詢Proxy到shard節點拉取的都是未經計算的原始數據,當查詢的metric數據量比較大時,網絡和磁盤IO開銷巨大。因此在繪圖時我們對查詢語句限制比較嚴格,基本不允許進行無label限制的模糊查詢。

 

報警   

 

Prometheus的報警功能目前來看相對計較簡單。主要是利用Alertmanager這個組件。已經實現了報警組分類,按標簽內容發送不同報警組、報警合並、報警靜音等基礎功能。配合rules_file中編輯的查詢觸發條件,Prometheus會主動通知Alertmanager然后發出報警。由於我們公司內使用的自研的Qalarm報警系統,接口比較豐富,和Alertmanager的webhook簡單對接即可使用。

 

Alertmanager也內建了一部分報警方式,如Email和第三方的Slack,初期我們的存儲集群報警使用的就是Slack,響應速度還是很不錯的。

 

需要注意的是,如果報警已經觸發,但是由於一些原因,比如刪除業務監控節點,使報警恢復的規則一直不能觸發,那么已出發的報警會按照Alertmanager配置的周期一直重復發送,要么從后台silence掉,要么想辦法使報警恢復。例如前段時間我們縮容Ceph集群,操作前沒有關閉報警,觸發了幾個osddown的報警,報警刷新周期2小時,那么每過兩小時Alertmanager都會發來一組osddown的報警短信。

 

對應編號的osd由於已經刪掉已經不能再寫入up對應的監控值,索性停掉osddown報警項,直接重啟ceph_exporter,再調用Prometheus API刪掉對應osd編號的osdupdown監控項,隨后在啟用osddown報警項才使報警恢復。

如下圖的報警詳情頁面,紅色的是已觸發的報警,綠色的是未觸發報警:

 

 

繪圖展示   

 

對於頁面展示,我們使用的是Grafana,如下面兩張圖,是兩個不同服務的Dashboard,可以做非常多的定制化,同時Grafana的template也可以作為參數傳到查詢語句中,對多維度定制查詢提供了極大的便利。

 

 

 

Q&A  

 

Q1:Promethues Alertmanager,能結合案例來一個么?

A1:直接演示一條報警規則吧。

ALERT SlowRequest

  IF ceph_slow_requests{service="ceph"} > 10

  FOR 1m

  LABELS { qalarm = "true" }

  ANNOTATIONS {

    summary = "Ceph Slow Requests",

    description = "slow requests count: {{ $value }} - Region:{{ $labels.group }}",

  }

這條規則在查詢到ceph slow_request > 10並且超過1分鍾時觸發一條報警。

 

Q2:exporter的編寫及使用方法,以及 promethues 如何結合 grafana使用和promethues 是如何進行報警的。

A2:exporter的編寫可以單獨拿出來寫一篇文章了。我們這邊主要使用的Prometheus Golang SDK,在項目中注冊打點,並通過Http接口暴露出來。報警沒有結合Grafana,不過大多數Grafana中使用的查詢語句,簡單修改即可成為Alertmanager的報警規則。

 

直播鏈接  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM