云計算

波尔多红酒有哪些品牌:開源監控系統Prometheus的前世今生

廣告
廣告

微信掃一掃,分享到朋友圈

開源監控系統Prometheus的前世今生
0 0

Prometheus是SoundCloud公司開源的監控系統,同時也是繼Kubernetes之后,第二個加入CNCF的項目。Prometheus是一個優秀的監控系統,沃趣圍繞著Prometheus先后開發了多個組件,包括基礎告警組件,服務發現組件、各種采集的Exporters等,這些組件結合Prometheus支撐了沃趣大部分的監控業務。本文主要介紹Prometheus,從他的來源,架構以及一個具體的例子等方面來說明,以及沃趣圍繞Prometheus做了哪些工作。 

起源

SoundCloud公司的之前的應用架構是巨石架構,也就是所有的功能放在一個大的??槔?,各個功能之間沒有明顯的界線。巨石架構的應用主要存在兩方面的問題,一方面在于很難對其進行水平擴展,只能垂直擴展,但是單臺機器的能力畢竟是有限的;另外一方面在于各個功能耦合在一塊,新增一個功能需要在已有的技術棧上進行開發,并且要確保不會對已有的功能造成影響。于是他們轉向了微服務架構,將原有的功能拆分成了幾百個獨立的服務,整個系統運行上千個實例。遷移到微服務架構給監控帶來一定的挑戰,現在不僅需要知道某個組件的運行的情況,還要知道服務的整體運行情況。他們當時的監控方案是:StatsD + Graphite + Nagios,StatsD結合Graphite構建監控圖表,各個服務將樣本數據推送給StatsD,StatsD將推送來的樣本數據聚合在一起,定時地推送給Graphite,Graphite將樣本數據保存在時序數據庫中,用戶根據Graphite提供的API,結合自身監控的需求,構建監控圖表,通過圖表分析服務的指標(例如,延遲,每秒的請求數,每秒的錯誤數等)。 

1_SoundCloud巨石應用架構.png
2_SoundCloud微服務架構.png


那么這樣一種方案能滿足微服務架構對監控的要求么?什么要求呢:既能知道服務整體的運行情況,也能夠保持足夠的粒度,知道某個組件的運行情況。答案是很難,為什么呢?例如,我們要統計api-server服務響應POST /tracks請求錯誤的數量,指標的名稱為api-server.tracks.post.500,這個指標可以通過http狀態碼來測量,服務響應的狀態碼為500就是錯誤的。Graphite指標名稱的結構是一種層次結構,api-server指定服務的名稱,tracks指定服務的handler,post指定請求的方法,500指定請求響應的狀態碼,api-server服務實例將該指標推送給StatsD,StatsD聚合各個實例推送來的指標,然后定時推送給Graphite。查詢api-server.tracks.post.500指標,我們能獲得服務錯誤的響應數,但是,如果我們的api-server服務跑了多個實例,想知道某個實例錯誤的響應數,該怎么查詢呢?問題出在使用這樣一種架構,往往會將各個服務實例發送來的指標聚合到一塊,聚合到一起之后,實例維度的信息就丟失掉了,也就無法統計某個具體實例的指標信息。 

3_StatsD_Graphite監控方案.png


StatsD與Graphite的組合用來構建監控圖表,告警是另外一個系統-Nagios-來做的,這個系統運行檢測腳本,判斷主機或服務運行的是否正常,如果不正常,發送告警。Nagios最大的問題在于告警是面向主機的,每個告警的檢查項都是圍繞著主機的,在分布式系統的環境底下,主機down掉是正常的場景,服務本身的設計也是可以容忍節點down掉的,但是,這種場景下Nagios依然會觸發告警。 

4_面向主機的告警系統Nagios.png


如果大家之前看過這篇 https://landing.google.com/sre … arker 介紹Google Borgmon的文章,對比Prometheus,你會發現這兩個系統非常相似。實際上,Prometheus深受Borgmon系統的影響,并且當時參與構建Google監控系統的員工加入了SoundCloud公司。總之,種種因素的結合,促使了Prometheus系統的誕生。 

Prometheus的解決方案

那么,Prometheus是如何解決上面這些問題的?之前的方案中,告警與圖表的構建依賴于兩個不同的系統,Prometheus采取了一種新的模型,將采集時序數據作為整個系統的核心,無論是告警還是構建監控圖表,都是通過操縱時序數據來實現的。Prometheus通過指標的名稱以及label(key/value)的組合來識別時序數據,每個label代表一個維度,可以增加或者減少label來控制所選擇的時序數據,前面提到,微服務架構底下對監控的要求:既能知道服務整體的運行情況,也能夠保持足夠的粒度,知道某個組件的運行情況。借助于這種多維度的數據模型可以很輕松的實現這個目標,還是拿之前那個統計http錯誤響應的例子來說明,我們這里假設api_server服務有三個運行的實例,Prometheus采集到如下格式的樣本數據(其中intance label是Prometheus自動添加上去的): 

api_server_http_requests_total{method="POST",handler="/tracks",status="500",instance="sample1"} -> 34
api_server_http_requests_total{method="POST",handler="/tracks",status="500",instance="sample2"} -> 28
api_server_http_requests_total{method="POST",handler="/tracks",status="500",instance="sample3"} -> 31


如果我們只關心特定實例的錯誤數,只需添加instance label即可,例如我們想要查看實例名稱為sample1的錯誤的請求數,那么我就可以用api_server_http_requests_total{method=”POST”,handler=”/tracks”,status=”500″,instance=”sample1″}這個表達式來選擇時序數據,選擇的數據如下: 

api_server_http_requests_total{method="POST",handler="/tracks",status="500",instance="sample1"} -> 34


如果我們關心整個服務的錯誤數,只需忽略instance label去除,然后將結果聚合到一塊,即可,例如 
sum without(instance) (api_server_http_requests_total{method=”POST”,handler=”/tracks”,status=”500″})計算得到的時序數據為: 

api_server_http_requests_total{method="POST",handler="/tracks",status="500"} -> 93


告警是通過操縱時序數據而不是運行一個自定義的腳本來實現的,因此,只要能夠采集到服務或主機暴露出的指標數據,那么就可以告警。 

架構

我們再來簡單的分析一下Prometheus的架構,看一下各個組件的功能,以及這些組件之間是如何交互的。 

Prometheus Server是整個系統的核心,它定時地從監控目標(Exporters)暴露的API中拉取指標,然后將這些數據保存到時序數據庫中,如果是監控目標是動態的,可以借助服務發現的機制動態地添加這些監控目標,另外它還會暴露執行PromQL(用來操縱時序數據的語言)的API,其他組件,例如Prometheus Web,Grafana可以通過這個API查詢對應的時序數據。Prometheus Server會定時地執行告警規則,告警規則是PromQL表達式,表達式的值是true或false,如果是true,就將產生的告警數據推送給alertmanger。告警通知的聚合、分組、發送、禁用、恢復等功能,并不是Prometheus Server來做的,而是Alertmanager來做的,Prometheus Server只是將觸發的告警數據推送給Alertmanager,然后Alertmanger根據配置將告警聚合到一塊,發送給對應的接收人。 

如果我們想要監控定時任務,想要instrument任務的執行時間,任務執行成功還是失敗,那么如何將這些指標暴露給Prometheus Server?例如每隔一天做一次數據庫備份,我們想要知道每次備份執行了多長時間,備份是否成功,我們備份任務只會執行一段時間,如果備份任務結束了,Prometheus Server該如何拉取備份指標的數據呢?解決這種問題,可以通過Prometheus的pushgateway組件來做,每個備份任務將指標推送pushgateway組件,pushgateway將推送來的指標緩存起來,Prometheus Server從Pushgateway中拉取指標。 

5_Prometheus架構.png

例子

前面都是從比較大的層面——背景、架構——來介紹Prometheus,現在,讓我們從一個具體的例子出發,來看一下如何借助Prometheus來構建監控圖表、分析系統性能以及告警。 

我們有個服務,暴露出四個API,每個API只返回一些簡單的文本數據,現在,我們要對這個服務進行監控,希望借助監控能夠查看、分析服務的請求速率,請求的平均延遲以及請求的延遲分布,并且當應用的延遲過高或者不可訪問時能夠觸發告警,代碼示例如下: 

package main
import (
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
Latency = promauto.NewHistogramVec(prometheus.HistogramOpts{
    Help: "latency of sample app",
    Name: "sample_app_latency_milliseconds",
    Buckets: prometheus.ExponentialBuckets(10, 2, 9),
}, []string{"handler", "method"})
)
func instrumentationFilter(f http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
    now := time.Now()
    f(writer, request)
    duration := time.Now().Sub(now)
    Latency.With(prometheus.Labels{"handler": request.URL.Path, "method": request.Method}).
        Observe(float64(duration.Nanoseconds()) / 1e6)
}
}
// jitterLatencyFilter make request latency between d and d*maxFactor
func jitterLatencyFilter(d time.Duration, maxFactor float64, f http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
    time.Sleep(d + time.Duration(rand.Float64()*maxFactor*float64(d)))
    f(writer, request)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
http.Handle("/metrics", promhttp.Handler())
http.Handle("/a", instrumentationFilter(jitterLatencyFilter(10*time.Millisecond, 256, func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("success"))
})))
http.Handle("/b", instrumentationFilter(jitterLatencyFilter(10*time.Millisecond, 128, func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("success"))
})))
http.Handle("/c", instrumentationFilter(jitterLatencyFilter(10*time.Millisecond, 64, func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("success"))
})))
http.Handle("/d", instrumentationFilter(jitterLatencyFilter(10*time.Millisecond, 32, func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("success"))
})))
http.ListenAndServe(":5001", nil)
}


我們按照instrumentation、exposition、collection、query這樣的流程構建監控系統,instrumentation關注的是如何測量應用的指標,有哪些指標需要測量;exposition關注的是如何通過http協議將指標暴露出來;collection關注的是如何采集指標;query關注的是如何構建查詢時序數據的PromQL表達式。我們首先從instrumentation這里,有四個指標是我們關心的: 

  • 請求速率
  • 請求的平均延遲
  • 請求的延遲分布
  • 訪問狀態
var (
Latency = promauto.NewHistogramVec(prometheus.HistogramOpts{
    Help: "latency of sample app",
    Name: "sample_app_latency_milliseconds",
    Buckets: prometheus.ExponentialBuckets(10, 2, 9),
}, []string{"handler", "method"})
)


首先將指標注冊進來,然后追蹤、記錄指標的值。用Prometheus提供的golang客戶端庫可以方便的追蹤、記錄指標的值,我們將instrumentation code放到應用的代碼里,每次請求,對應的指標狀態的值就會被記錄下來。 

client golang提供了四種指標類型,分別為Counter, Gauge, Histogram, Summary,Counter類型的指標用來測量只會增加的值,例如服務的請求數;Gauge類型的指標用來測量狀態值,即可以變大,也可以變小的值,例如請求的延遲時間;Histogram與Summary指標類似,這兩個指標取樣觀察的值,記錄值的分布,統計觀察值的數量,累計觀察到的值,可以用它來統計樣本數據的分布。為了采集請求速率、平均延遲以及延遲分布指標,方便起見用Histogram類型的指標追蹤、記錄每次請求的情況,Histogram類型的指標與普通類型(Counter、Gauge)不同的地方在于會生成多條樣本數據,一個是觀察樣本的總數,一個是觀察樣本值的累加值,另外是一系列的記錄樣本百分位數的樣本數據。訪問狀態可以使用up指標來表示,每次采集時,Prometheus會將采集的健康狀態記錄到up指標中。 

http.Handle("/metrics", promhttp.Handler())


instrumentation完成之后,下一步要做的就是exposition,只需將Prometheus http handler添加進來,指標就可以暴露出來。訪問這個Handler返回的樣本數據如下(省略了一些無關的樣本數據): 

sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="10"} 0
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="20"} 0
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="40"} 0
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="80"} 0
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="160"} 0
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="320"} 0
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="640"} 1
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="1280"} 1
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="2560"} 1
sample_app_latency_milliseconds_bucket{handler="/d",method="GET",le="+Inf"} 1
sample_app_latency_milliseconds_sum{handler="/d",method="GET"} 326.308075
sample_app_latency_milliseconds_count{handler="/d",method="GET"} 1


僅僅將指標暴露出來,并不能讓prometheus server來采集指標,我們需要進行第三步collection,配置prometheus server發現我們的服務,從而采集服務暴露出的樣本數據。我們簡單地看下prometheus server的配置,其中,global指定采集時全局配置, scrape_interval 指定采集的間隔, evaluation_interval 指定 alerting rule (alerting rule是PromQL表達式,值為布爾類型,如果為true就將相關的告警通知推送給Alertmanager)也就是告警規則的求值時間間隔,scrape_timeout指定采集時的超時時間;alerting指定Alertmanager服務的地址;scrape_configs指定如何發現監控對象,其中job_name指定發現的服務屬于哪一類,static_configs指定服務靜態的地址,前面我們也提到,Prometheus支持動態服務發現,例如文件、kubernetes服務發現機制,這里我們使用最簡單的靜態服務發現機制。 

# my global config
global:
  scrape_interval:     2s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).
rule_files:
- rule.yaml
# Alertmanager configuration
alerting:
  alertmanagers:
  - static_configs:
    - targets:
      - localhost:9093
scrape_configs:
- job_name: sample-app
  scrape_interval: 3s
  static_configs:
  - targets:
    - sample:5001


采集完指標,就可以利用Prometheus提供的PromQL語言來操縱采集的時序數據,例如,我們想統計請求的平均速率,可以用這個表達式 
irate(sample_app_latency_milliseconds_sum[1m]) / irate(sample_app_latency_milliseconds_count[1m])來計算。 

有了時序數據之后,就可以借助Grafana來構建監控圖表,具體怎么配置Grafana圖表在這里就不展開了,核心點是利用PromQL表達式選擇、計算時序數據。 

6_Grafana圖表.png


Prometheus的告警是通過對Alerting Rule求值來實現的,alerting rule是一系列的PromQL表達式,alerting rule保存在配置文件中。我們想要對應用的延遲以及可用狀態進行告警,當應用過高或者不可訪問時就觸發告警,規則可以如下這樣定義: 

- name: sample-up
  rules:
  - alert: UP
    expr: up{instance="sample:5001"} == 0
    for: 1m
    labels:
      severity: page
    annotations:
      summary: Service health
  - alert: 95th-latency
    expr: histogram_quantile(0.95, rate(sample_app_latency_milliseconds_bucket[1m])) > 1000
    for: 1m
    labels:
      severity: page
    annotations:
      summary: 95th service latency


其中UP指定服務的可用狀態,95th-latency指定95%的請求大于1000毫秒就觸發告警。Prometheus定時的對這些規則進行求值,如果條件滿足,就將告警通知發送給Alertmanger,Alertmanger會根據自身路由配置,對告警進行聚合,分發到指定的接收人,我們想通過郵箱接收到告警,可以如下進行配置: 

global:
  smtp_smarthost: <your_smtp_server>
  smtp_auth_username: <your_username>
  smtp_from: <from>
  smtp_auth_password: <secret>
  smtp_require_tls: false
  resolve_timeout: 5m
route:
  receiver: me
receivers:
- name: me
  email_configs:
  - to: [email protected]
templates:
- '*.tmpl'


這樣,我們就可以通過郵箱收到告警郵件了。 

相關的工作

無論是監控圖表相關的業務,還是告警相關的業務,都離不開相關指標的采集工作,沃趣是一家做數據庫產品的公司,我們花費了很多的精力去采集數據庫相關的指標,從Oracle到MySQL,再到SQL Server,主流的關系型數據庫的指標都有采集。對于一些通用的指標,例如操作系統相關的指標,我們主要是借助開源的Exporters來采集的。沃趣的產品是軟、硬一體交付的,其中有大量硬件相關的指標需要采集,因此,我們也有專門采集硬件指標的Expoters。 

沃趣大部分場景中,要監控的服務都是動態的。比如,用戶從平臺上申請了一個數據庫,需要增加相關的監控服務,用戶刪除數據庫資源,需要移除相關的監控服務,要監控的數據庫服務處于動態的變化之中。沃趣每個產品線的基礎架構都不相同,數據庫服務有跑在Oracle RAC上的,有跑在ZStack的,有跑在Kubernetes上的。對于跑在Kubernetes上的應用來說,并需要擔心Prometheus怎么發現要監控的服務,只需要配置相關的服務發現的機制就可以了。對于其他類型的,我們主要借助Prometheus的file_sd服務發現機制來實現,基于文件的服務發現機制是一種最通用的機制,我們將要監控的對象寫到一個文件中,Prometheus監聽這個文件的變動,動態的維護要監控的對象,我們在file_sd基礎上構建了專門的組件去負責服務的動態更新,其他應用調用這個組件暴露的API來維護自身想要監控的對象。 

Prometheus本身的機制的并不能滿足我們業務上對告警的要求,一方面我們需要對告警通知進行統計,但是Alertmanager本身并沒有對告警通知做持久化,服務重啟之后告警通知就丟失掉了;另外一方面用戶通過Web頁面來配置相關的告警,告警規則以及告警通知的路由需要根據用戶的配置動態的生成。為了解決這兩方面的問題,我們將相關的業務功能做成基礎的告警組件,供各個產品線去使用。針對Alertmanager不能持久化告警通知的問題,基礎告警組件利用Alertmanager webhook的機制來接收告警通知,然后將通知保存到數據庫中;另外用戶的告警配置需要動態的生成,我們定義了一種新的模型來描述我們業務上的告警模型。 

總結

Promtheus將采集時序數據作為整個系統的核心,無論是構建監控圖表還是告警,都是通過操縱時序數據來完成的。Prometheus借助多維度的數據模型,以及強大的查詢語言滿足了微服務架構底下對監控的要求:既能知道服務整體的運行情況,也能夠保持足夠的粒度,知道某個組件的運行情況。沃趣站在巨人的肩旁上,圍繞Prometheus構建了自己的監控系統,從滿足不同采集要求的Exporters到服務發現,最后到基礎告警組件,這些組件結合Prometheus,構成了沃趣監控系統的核心。 

作者:郭振,沃趣科技開發工程師,多年的Python、Golang等語言的開發經驗,熟悉Kubernetes、Prometheus等云原生應用,負責QFusion RDS平臺以及基礎告警平臺的研發工作。

我還沒有學會寫個人說明!

企業需要知道的 6個AI/ML關鍵點

上一篇

TPC-C解析系列01_TPC-C benchmark測試介紹

下一篇

你也可能喜歡

開源監控系統Prometheus的前世今生

長按儲存圖像,分享給朋友

ITPUB 每周精要將以郵件的形式發放至您的郵箱


微信掃一掃

微信掃一掃