最近參與的幾個項目,無一例外對監控都有極強的要求,須要對項目中各組件進行詳細監控,如服務端API的請求次數、響應時間、到達率、接口錯誤率、分佈式存儲中的集羣IOPS、節點在線狀況、偏移量等。node
比較常見的方式是寫日誌,將日誌採集到遠端進行分析和繪圖,或寫好本地監控腳本進行數據採集後,經過監控系統客戶端push到監控系統中進行打點。基本上咱們須要的都能覆蓋,但仍然有一些問題在使用上不太舒服,如在大規模請求下日誌採集和分析的效率比較難控制,或push打點的粒度和緯度以及查詢不夠靈活等。web
後來在同事對《Google SRE》這本書中的一些運維思想進行一番安利後,抱着試一試的態度,開始嘗試使用Prometheus作爲幾個項目的監控解決方案。json
多維數據模型(時序數據由 metric 名和一組K/V標籤構成)。api
靈活強大的查詢語句(PromQL)。服務器
不依賴存儲,支持local和remote(OpenTSDB、InfluxDB等)不一樣模型。網絡
採用 HTTP協議,使用Pull模式採集數據。運維
監控目標,能夠採用服務發現或靜態配置的方式。curl
支持多種統計數據模型,圖形化友好(Grafana)。分佈式
Counter表示收集的數據是按照某個趨勢(增長/減小)一直變化的。函數
Gauge表示蒐集的數據是瞬時的,能夠任意變高變低。
Histogram能夠理解爲直方圖,主要用於表示一段時間範圍內對數據進行採樣,(一般是請求持續時間或響應大小),並可以對其指定區間以及總數進行統計。
Summary和Histogram十分類似,主要用於表示一段時間範圍內對數據進行採樣,(一般是請求持續時間或響應大小),它直接存儲了 quantile 數據,而不是根據統計區間計算出來的。
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(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>
詳細的服務發現配置請參照官網: https://prometheus.io/docs/prometheus/latest/configuration/configuration/
因爲最近參與的幾個項目深度使用公司內部的配置管理服務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做爲服務發現組件,也有很是直接的內部配置支持。
高可用目前暫時沒有太好的方案。官方給出的方案能夠對數據作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限制的模糊查詢。
0x07 報警
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報警項才使報警恢復。
以下圖的報警詳情頁面,紅色的是已觸發的報警,綠色的是未觸發報警:
0x08 繪圖展現
對於頁面展現,咱們使用的是Grafana,以下面兩張圖,是兩個不一樣服務的Dashboard,能夠作很是多的定製化,同時Grafana的template也能夠做爲參數傳到查詢語句中,對多維度定製查詢提供了極大的便利。
0x09 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"}" 和查詢接口的使用方式相似