Prometheus Metrics 設計的最佳實踐和應用實例,看這篇夠了!

Prometheus 是一個開源的監控解決方案,部署簡單易使用,難點在於如何設計符合特定需求的 Metrics 去全面高效地反映系統實時狀態,以助力故障問題的發現與定位。本文即基於最佳實踐的 Metrics 設計方法,結合具體的場景實例——TKE 的網絡組件 IPAMD 的內部監控,以我的實踐經驗談一談如何設計和實現適合的、可以更好反映系統實時狀態的監控指標(Metrics)。該篇內容適於 Prometheus 或相關監控系統的初學者(可無任何基礎瞭解),以及近期有 Prometheus 監控方案搭建和維護需求的系統開發管理者。經過這篇文章,能夠加深對 Prometheus Metrics 的理解,並能針對實際的監控場景提出更好的指標(Metrics)設計。node

1 引言

Prometheus 是一個開源的監控解決方案,它可以提供監控指標數據的採集、存儲、查詢以及監控告警等功能。做爲雲原生基金會(CNCF)的畢業項目,Prometheus 已經在雲原生領域獲得了大範圍的應用,並逐漸成爲了業界最流行的監控解決方案之一。git

Prometheus 的部署和使用能夠說是簡單易上手,可是如何針對實際的問題和需求設計適宜的 Metrics 卻並非那麼直接可行,反而須要優先解決暴露出來的諸多不肯定問題,好比什麼時候選用 Vector,如何設計適宜的 buckets,Summary 和 Histogram 指標類型的取捨等。然而,要想有效助力故障及問題的發現與定位,必需要有一個合理有效的 Metrics 去全面高效地反映系統實時狀態。github

本文將介紹基於最佳實踐的 Metrics 設計方法,並結合具體的場景實例——TKE 的網絡組件 IPAMD 的內部監控,以我的實踐經驗談一談如何設計和實現適合的、可以更好反映系統實時狀態的監控指標(Metrics)。golang

本文以後的第 2 節將對 Prometheus 的 Metrics 作簡單的介紹,對此已有了解的讀者可跳過。以後第 3 節將介紹 Metrics 設計的最佳實踐。第 4 節將結合具體的實例應用相關設計方法。第 5 節將介紹 Golang 上指標收集的實現方案。web

2 Prometheus Metrics Type 簡介

Prometheus Metrics 是整個監控系統的核心,全部的監控指標數據都由其記錄。Prometheus 中,全部 Metrics 皆爲時序數據,並以名字做區分,即每一個指標收集到的樣本數據包含至少三個維度的信息:名字、時刻和數值。api

而 Prometheus Metrics 有四種基本的 type:緩存

  • Counter: 只增不減的單變量
  • Gauge:可增可減的單變量
  • Histogram:多桶統計的多變量
  • Summary:聚合統計的多變量

此外,Prometheus Metrics 中有一種將樣本數據以標籤(Label)爲維度做切分的數據類型,稱爲向量(Vector)。四種基本類型也都有其 Vector 類型:性能優化

  • CounterVec
  • GaugeVec
  • HistogramVec
  • SummaryVec

Vector 至關於一組同名同類型的 Metrics,以 Label 作區分。Label 能夠有多個,Prometheus 實際會爲每一個 Label 組合建立一個 Metric。Vector 類型記錄數據時需先打 Label 才能調用 Metrics 的方法記錄數據。服務器

如對於 HTTP 請求延遲這一指標,因爲 HTTP 請求可在多個地域的服務器處理,且具備不一樣的方法,因而,可定義名爲 http_request_latency_seconds 的 SummaryVec,標籤有regionmethod,以此表示不一樣地域服務器的不一樣請求方法的請求延遲。網絡

如下將對每一個類型作詳細的介紹。

2.1 Counter

  • 定義:是單調遞增的計數器,重啓時重置爲0,其他時候只能增長。
  • 方法:

    type Counter interface {  Metric  Collector  // 自增1  Inc()  // 把給定值加入到計數器中. 若值小於 0 會 panic  Add(float64)}
  • 常測量對象:
    • 請求的數量
    • 任務完成的數量
    • 函數調用次數
    • 錯誤發生次數
    • ...

2.2 Gauge

  • 定義:表示一個可增可減的數字變量,初值爲0
  • 方法:

    type Gauge interface {  Metric  Collector  Set(float64)    // 直接設置成給定值  Inc()   // 自增1  Dec()   // 自減1  Add(float64)     // 增長給定值,可爲負  Sub(float64)    // 減小給定值,可爲負  // SetToCurrentTime 將 Gauge 設置成當前的 Unix 時間戳  SetToCurrentTime()}
  • 常測量對象:
    • 溫度
    • 內存用量
    • 併發請求數
    • ...

2.3 Histogram

  • 定義:Histogram 會對觀測數據取樣,而後將觀測數據放入有數值上界的桶中,並記錄各桶中數據的個數,全部數據的個數和數據數值總和。
  • 方法:

    type Histogram interface {  Metric  Collector  // Observe 將一個觀測到的樣本數據加入 Histogram 中,並更新相關信息  Observe(float64)}
  • 常測量對象:
    • 請求時延
    • 回覆長度
    • ...各類有樣本數據
  • 具體實現:Histogram 會根據觀測的樣本生成以下數據:

    inf 表無窮值,a1,a2,...是單調遞增的數值序列。

    • [basename]_count: 數據的個數,類型爲 counter
    • [basename]_sum: 數據的加和,類型爲 counter
    • [basename]_bucket{le=a1}: 處於[-inf,a1]的數值個數
    • [basename]_bucket{le=a2}: 處於[-inf,a2]的數值個數
    • ...
    • [basename]_bucket{le=<+inf>}:處於[-inf,+inf]的數值個數,prometheus默認額外生成,無需用戶定義
  • Histogram 能夠計算樣本數據的百分位數,其計算原理爲:經過找特定的百分位數值在哪一個桶中,而後再經過插值獲得結果。好比目前有兩個桶,分別存儲了[-inf, 1]和[-inf, 2]的數據。而後如今有20%的數據在[-inf, 1]的桶,100%的數據在[-inf, 2]的桶。那麼,50%分位數就應該在[1, 2]的區間中,且處於(50%-20%) / (100%-20%) = 30% / 80% = 37.5% 的位置處。Prometheus計算時假設區間中數據是均勻分佈,所以直接經過線性插值能夠獲得 (2-1)*3/8+1 = 1.375.

2.4 Summary

  • 定義:Summary 與 Histogram 相似,會對觀測數據進行取樣,獲得數據的個數和總和。此外,還會取一個滑動窗口,計算窗口內樣本數據的分位數。
  • 方法:

    type Summary interface {  Metric  Collector  // Observe 將一個觀測到的樣本數據加入 Summary 中,並更新相關信息  Observe(float64)}
  • 常測量對象:
    • 請求時延
    • 回覆長度
    • ...各類有樣本數據
  • 具體實現:Summary 徹底是在client端聚合數據,每次調用 obeserve 會計算出以下數據:
    • [basename]_count: 數據的個數,類型爲 counter
    • [basename]_sum: 數據的加和,類型爲 counter
    • [basename]{quantile=0.5}: 滑動窗口內 50% 分位數值
    • [basename]{quantile=0.9}: 滑動窗口內 90% 分位數值
    • [basename]{quantile=0.99}: 滑動窗口內 99% 分位數值
    • ...

實際分位數值可根據需求制定,且是對每個 Label 組合作聚合。

2.5 Histogram 和 Summary 簡單對比

能夠看出,Histogram 和 Summary 類型測量的對象是比較接近的,但根據其實現方式和其自己的特色,在性能耗費、適用場景等方面具備必定差異,本文總結以下:

3 Metrics 設計的最佳實踐

3.1 如何肯定須要測量的對象

在具體設計 Metrics 以前,首先須要明確須要測量的對象。須要測量的對象應該依據具體的問題背景、需求和需監控的系統自己來肯定。

思路1:從需求出發

Google 針對大量分佈式監控的經驗總結出四個監控的黃金指標,這四個指標對於通常性的監控測量對象都具備較好的參考意義。這四個指標分別爲:

  • 延遲:服務請求的時間。
  • 通信量:監控當前系統的流量,用於衡量服務的容量需求。
  • 錯誤:監控當前系統全部發生的錯誤請求,衡量當前系統錯誤發生的速率。
  • 飽和度:衡量當前服務的飽和度。主要強調最能影響服務狀態的受限制的資源。例如,若是系統主要受內存影響,那就主要關注系統的內存狀態。

而筆者認爲,以上四種指標,實際上是爲了知足四個監控需求:

  • 反映用戶體驗,衡量系統核心性能。如:在線系統的時延,做業計算系統的做業完成時間等。
  • 反映系統的服務量。如:請求數,發出和接收的網絡包大小等。
  • 幫助發現和定位故障和問題。如:錯誤計數、調用失敗率等。
  • 反映系統的飽和度和負載。如:系統佔用的內存、做業隊列的長度等。

除了以上常規需求,還可根據具體的問題場景,爲了排除和發現之前出現過或可能出現的問題,肯定相應的測量對象。好比,系統須要常常調用的一個庫的接口可能耗時較長,或偶有失敗,可制定 Metrics 以測量這個接口的時延和失敗數。

思路2:從需監控的系統出發

另外一方面,爲了知足相應的需求,不一樣系統須要觀測的測量對象也是不一樣的。在 官方文檔 的最佳實踐中,將須要監控的應用分爲了三類:

  • 線上服務系統(Online-serving systems):需對請求作即時的響應,請求發起者會等待響應。如 web 服務器。
  • 線下計算系統(Offline processing):請求發起者不會等待響應,請求的做業一般會耗時較長。如批處理計算框架 Spark 等。
  • 批處理做業(Batch jobs):這類應用一般爲一次性的,不會一直運行,運行完成後便會結束運行。如數據分析的 MapReduce 做業。

對於每一類應用其一般狀況下測量的對象是不太同樣的。其總結以下:

  • 線上服務系統:主要有請求、出錯的數量,請求的時延等。
  • 線下計算系統:最後開始處理做業的時間,目前正在處理做業的數量,發出了多少 items, 做業隊列的長度等。
  • 批處理做業:最後成功執行的時刻,每一個主要 stage 的執行時間,總的耗時,處理的記錄數量等。

除了系統自己,有時還需監控子系統:

  • 使用的庫(Libraries): 調用次數,成功數,出錯數,調用的時延。
  • 日誌(Logging):計數每一條寫入的日誌,從而可找到每條日誌發生的頻率和時間。
  • Failures: 錯誤計數。
  • 線程池:排隊的請求數,正在使用的線程數,總線程數,耗時,正在處理的任務數等。
  • 緩存:請求數,命中數,總時延等。
  • ...

最後的測量對象的肯定應結合以上兩點思路肯定。

3.2 如何選用 Vector

選用 Vec 的原則:

  • 數據類型相似但資源類型、收集地點等不一樣
  • Vec 內數據單位統一

例子:

  • 不一樣資源對象的請求延遲
  • 不一樣地域服務器的請求延遲
  • 不一樣 http 請求錯誤的計數
  • ...

此外,官方文檔 中建議,對於一個資源對象的不一樣操做,如 Read/Write、Send/Receive, 應採用不一樣的 Metric 去記錄,而不要放在一個 Metric 裏。緣由是監控時通常不會對這二者作聚合,而是分別去觀測。

不過對於 request 的測量,一般是以 Label 作區分不一樣的 action。

3.3 如何肯定 Label

根據3.2,常見 Label 的選擇有:

  • resource
  • region
  • type
  • ...

肯定 Label 的一個重要原則是:同一維度 Label 的數據是可平均和可加和的,也即單位要統一。如風扇的風速和電壓就不能放在一個 Label 裏。

此外,不建議下列作法:

my_metric{label=a} 1my_metric{label=b} 6my_metric{label=total} 7

即在 Label 中同時統計了分和總的數據,建議採用 PromQL 在服務器端聚合獲得總和的結果。或者用另外的 Metric 去測量總的數據。

3.4 如何命名 Metrics 和 Label

好的命名可以見名知義,所以命名也是良好設計的一環。

Metric 的命名:

  • 須要符合 pattern: a-zA-Z*:*
  • 應該包含一個單詞做爲前綴,代表這個 Metric 所屬的域。如:
    • prometheus_notifications_total
    • process_cpu_seconds_total
    • ipamd_request_latency
  • 應該包含一個單位的單位做爲後綴,代表這個 Metric 的單位。如:
    • http_request_duration_seconds
    • node_memory_usage_bytes
    • http_requests_total (for a unit-less accumulating count)
  • 邏輯上與被測量的變量含義相同。
  • 儘可能使用基本單位,如 seconds,bytes。而不是 Milliseconds, megabytes。

Label 的命名:

  • 依據選擇的維度命名,如:
    • region: shenzhen/guangzhou/beijing
    • owner: user1/user2/user3
    • stage: extract/transform/load

3.5 如何設計適宜的 Buckets

根據前述 histogram 的統計原理可知,適宜的 buckets 能使 histogram 的百分位數計算更加準確。

理想狀況下,桶會使得數據分佈呈階梯狀,即各桶區間內數據個數大體相同。如圖1所示,是本人在實際場景下配置的buckets 數據直方圖,y 軸爲 buckets 內的數據個數,x 軸是各 buckets,能夠看出其近似成階梯狀。這種狀況下,當前桶個數下對數據的分辨率最大,各百分位數計算的準確率較高。

圖1 較爲理想的桶數據分佈

而根據筆者實踐經驗,爲了達成以上目標,buckets 的設計可聽從以下經驗:

  • 須要知道數據的大體分佈,若事先不知道可先用默認桶 ({.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10})或 2 倍數桶({1,2,4,8...})觀察數據分佈再調整 buckets。
  • 數據分佈較密處桶間隔制定的較窄一些,分佈稀疏處可制定的較寬一些。
  • 對於多數時延數據,通常具備長尾的特性,較適宜用指數形式的桶(ExponentialBuckets)。
  • 初始桶上界通常覆蓋10%左右的數據,若不關注頭部數據也可讓初始上界更大一些。
  • 若爲了更準確計算特定百分位數,如90%,可在90%的數據處加密分佈桶,即減小桶的間隔。

4 實例:TKE-ENI-IPAMD Metrics 設計與規劃

4.1 組件簡介

該組件用於支持騰訊雲 TKE 的策略路由網絡方案。在這一網絡方案中,每一個 pod 的 IP 都是 VPC 子網的一個IP,且綁定到了所在節點的彈性網卡上,經過策略路由連通網絡,而且使得容器能夠支持騰訊雲的 VPC 的全部特性。

其中,在 2.0.0 版本之前,tke-eni-ipamd 組件是一個 IP 分配管理的 GRPC Server,其主要職責爲:

  • cni IP 真正分配/刪除的 GRPC Server,分配/釋放 IP 會調用騰訊雲彈性網卡接口執行相應的 IP 綁定/解綁操做
  • Node 控制器(用於給 Node 綁定/解綁彈性網卡)
  • Stateulfset 控制器(用於給 Statefulset 預留 IP 資源)

其工做原理和流程如圖 2 所示:

圖2 tke-eni-ipamd(v2.0.0-) 工做原理和流程

4.2 IPAMD 的使用場景和咱們的要求

背景:

  • ip 分配/釋放對時延比較敏感,爲了方便肯定 ip 分配/釋放過程當中性能瓶頸是由咱們自身代碼形成的仍是底層模塊形成的(如 ipamd 調用的 vpc 接口等)。同時也方便對咱們的代碼和推動底層模塊的性能優化。
  • ipamd 運行過程當中可能會出現故障等問題,爲了及時發現故障,定位問題,也須要有內部監控。

需求:

  • 須要可以統計 ip 分配和釋放各個階段的時延,以肯定性能瓶頸
  • 須要知道當前的併發請求數,以肯定 IPAMD 負載
  • vpc 接口 ip分配/釋放,彈性網卡建立/綁定/解綁/釋放耗時比較長,而且常常有失敗狀況。須要可以統計這些接口的時延和調用成功率,以定位性能瓶頸。
  • node controller,statefulset controller 進行 sync 階段會有一系列流程,但願能清楚主要流程耗時,方便定位瓶頸
  • 彈性網卡的建立/刪除等過程當中容易產生髒數據,須要可以統計髒數據的個數,以發現髒數據問題。
  • 須要有較強的實時性,可以清楚的看到最近(~分鐘級別)系統的運行狀態

咱們的場景:

  • ipamd 是部署在每一個用戶集羣中的一個組件
  • 每一個用戶集羣內有 prometheus server 作聚合,而後每一個 region 也有 server 去拉取數據

4.3 整體設計

所以,須要如下幾類 Metric:

  • ip alloc/free 各階段時延
  • 基本運行信息:請求併發數、內存用量、goroutine 數,線程數
  • vpc 接口時延
  • vpc 接口調用成功率
  • controller sync 時延
  • 髒數據計數

4.4 Histogram vs. Summary

時延可選擇 Histogram 或 Summary 進行測量,如何選擇?

基於 2.5 節的二者對比,有以下分析:

Summary:

  • 優勢:
    1. 可以很是準確的計算百分位數
    2. 不須要提早知道數據的分佈
  • 缺點:
    1. 靈活性不足,實時性須要經過 maxAge 來保證,寫死了後靈活性就不太夠(好比想知道更長維度的百分位數)
    2. 在 client 端已經作了聚合,即在各個用戶集羣的 ipamd 中已經聚合了,咱們若是須要觀察所有 user 下的百分位數數據是不行的(只能看均值)
    3. 用戶集羣的 ipamd 的調用頻率可能很低(如小集羣或者穩定集羣),這種狀況下 client 端聚合計算百分位數值失去意義(數據太少不穩定),若是把 maxAge 增大則失去實時性

Histogram:

  • 優勢:
    1. 兼具靈活性和實時性
    2. 能夠靈活的聚合數據,觀察各個尺度和維度下的數據
  • 缺點:
    1. 須要提早知道數據的大體分佈,並以此設計出合適而準確的桶序列
    2. 難以經過 Label 串聯多種 Metrics,由於各個 Metrics 的數據分佈可能差別較大,若是都只用一種桶序列的話會致使百分位數計算差別較大

Summary 的缺點過於致命,難以迴避。Histogram 的缺點能夠經過增長工做量(即經過測試環境中的實驗來肯定各 Metrics 的大體分佈)和增長 Metrics(不用 Label 區分)來較好解決。

因此傾向於使用 Histogram。

4.5 Metrics 規劃示例

詳細的 Metrics 規劃內容較多,這裏選取了一些表明性的樣例,列舉以下:

注1:DefBuckets指默認桶 ({.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10})。

注2:以上 buckets 持續微調中。

5 指標收集的 Golang 實現方案

5.1 整體實現思路

  • 利用 prometheus 的 golang client 實現自定義的 exporter(包括自定義的 Metrics ),並嵌入到 ipamd 代碼中,以收集數據
  • 全部的 Metrics 做爲 Metrics 包的外部變量可供其餘包使用,調用測量方法
  • 自定義 exporter 參考 prometheus client golang example
  • 將收集到的數據經過 http server 暴露出來

5.2 Metrics 收集方案

方案1:非侵入式裝飾器模式

樣例: kubelet/kuberuntime/instrumented_services.go

type instrumentedRuntimeService struct {    service internalapi.RuntimeService}func recordOperation(operation string, start time.Time) {    metrics.RuntimeOperations.WithLabelValues(operation).Inc()    metrics.DeprecatedRuntimeOperations.WithLabelValues(operation).Inc()    metrics.RuntimeOperationsDuration.WithLabelValues(operation).Observe(metrics.SinceInSeconds(start))    metrics.DeprecatedRuntimeOperationsLatency.WithLabelValues(operation).Observe(metrics.SinceInMicroseconds(start))}func (in instrumentedRuntimeService) Status() (*runtimeapi.RuntimeStatus, error) {    const operation = "status"    defer recordOperation(operation, time.Now())    out, err := in.service.Status()    recordError(operation, err)    return out, err}

優勢:

  • 上層調用函數處幾乎不用修改,只需修改調用的實例
  • 抽象較好,非侵入式設計,代碼耦合度低

缺點:

  • 需單獨封裝每一個調用函數,複用度低
  • 沒法封裝內部函數,只能適用於測量對外服務函數的數據

方案2:defer 函數收集

樣例:

func test() (retErr error){    defer func(){        metrics.LatencySeconds.Observe(...)    }()    ...    func body    ...}

優勢:

  • 上層調用函數處徹底不用修改
  • 適用於全部函數的測量

缺點:

  • 有點濫用 defer
  • 侵入式設計,具備必定的耦合度

5.3 目前 IPAMD 的指標收集實現方案

  • 時延統計:經過 golang 的 time 模塊計時,在函數中嵌入 time.Now 和並在其後 defer time.Since 來統計。
  • 調用成功率統計:調用次數在接口函數裏直接用 counter 進行統計,失敗次數在defer裏獲取命名返回值統計,最後在 prometheus server 端聚合的時候經過 PromQL 利用這兩個數據計算出調用成功率。
  • 併發請求數的統計:在最外層的 AddPodIP 和 DelPodIP 中,在函數中和 defer func 中分別調用Inc和Dec。

6 總結

本文介紹了 Prometheus Metrics 及最佳實踐的 Metrics 設計和收集實現方法,並在具體的監控場景—— TKE 的網絡組件 IPAMD 的內部監控中應用了相關方法。

具體而言,本文基於最佳實踐,回答了 Prometheus Metrics 設計過程當中的若干問題:

  • 如何肯定須要測量的對象:依據需求(反映用戶體驗、服務量、飽和度和幫助發現問題等)和需監控的具體系統。
  • 什麼時候選用 Vec:數據類型相似但資源類型、收集地點等不一樣,數據單位統一。
  • 如何肯定 Label:可平均和可加和的,單位要統一;總和數據另外計。
  • 如何命名 Metrics 和 Label:見名知義,應包含監控的系統名/模塊名,指標名,單位等信息。
  • 如何設計適宜的 Buckets:依據數據分佈制定,密集部分桶區間較窄,整體桶分佈儘可能接近階梯狀。
  • 如何取捨 Histogram 和 Summary:Histogram 計算偏差大,但靈活性較強,適用客戶端監控、或組件在系統中較多、或不太關心精確的百分位數值的場景;Summary 計算精確,但靈活性較差,適用服務端監控、或組件在系統中惟一或只有個位數、或須要知道較準確的百分位數值(如性能優化場景)的場景。

此外,Metrics 設計並非一蹴而就的,需依據具體的需求的變化進行反覆迭代。好比需新增 Metrics 去發現定位可能出現的新問題和故障,再好比 Buckets 的設計也須要變化來適應測量數據分佈發生的變化,從而得到更精確的百分位數測量值。

參考資料

  1. Prometheus 官方文檔 :https://prometheus.io/docs/in...
  2. Go client library:https://github.com/prometheus...
【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公衆號,及時獲取更多幹貨!!
相關文章
相關標籤/搜索