1. 程式人生 > >360 基於 Prometheus的在線服務監控實踐

360 基於 Prometheus的在線服務監控實踐

高可用方案 通用 qss stack 語句 監控系統 參考 穩定性 counter類

轉自:https://mp.weixin.qq.com/s/lcjZzjptxrUBN1999k_rXw

主題簡介:

  1. Prometheus基礎介紹

  2. Prometheus打點及查詢技巧

  3. Prometheus高可用和服務發現經驗

初衷

最近參與的幾個項目,無一例外對監控都有極強的要求,需要對項目中各組件進行詳細監控,如服務端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的值組成的。

標準格式為 <metric name>{<label name>=<label value>,...}

例如:

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

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/

服務發現

在使用初期,參與的幾個項目的Prometheus都是各自獨立部署和維護的。其配置也是按照官方文檔中的標準配置來操作。機器數量少的時候維護簡單,增刪機器之後簡單reload一下即可。例如:

技術分享

但隨著服務器量級增長,業務整合到同一組Prometheus時,每次上下線實例都是一個十分痛苦的過程,配置文件龐大,列表過長,修改的過程極其容易眼花導致誤操作。所以我們嘗試使用了Prometheus的服務發現功能。

從配置文檔中不難發現Prometheus對服務發現進行了大量的支持,例如大家喜聞樂見的Consul、etcd和K8S。

<scrape_config>

<tls_config>

<azure_sd_config>

<Consul_sd_config>

<dns_sd_config>

<ec2_sd_config>

<openstack_sd_config>

<file_sd_config>

<gce_sd_config>

<kubernetes_sd_config>

<marathon_sd_config>

<nerve_sd_config>

<serverset_sd_config>

<triton_sd_config>

由於最近參與的幾個項目深度使用公司內部的配置管理服務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節點進行一些配置。

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的報警規則。

Q3:刪除配置文件job,但是通過查詢還有job記錄,怎麽刪除job記錄?

A3:直接通過Prometheus接口處理即可

curl -X "DELETE" "http://prometheus:9090/api/v1/series?match[]={job="your job"}"

和查詢接口的使用方式類似

360 基於 Prometheus的在線服務監控實踐