Prometheus 是一個開源的監控解決方案,部署簡單易使用,難點在於如何設計符合特定需求的 Metrics 去全面高效地反映系統實時狀態,以助力故障問題的發現與定位。本文即基於最佳實踐的 Metrics 設計方法,結合具體的場景實例——TKE 的網絡組件 IPAMD 的內部監控,以個人實踐經驗談一談如何設計和實現適合的、能夠更好反映系統實時狀態的監控指標(Metrics)。該篇內容適於 Prometheus 或相關監控系統的初學者(可無任何基礎了解),以及近期有 Prometheus 監控方案搭建和維護需求的系統開發管理者。通過這篇文章,可以加深對 Prometheus Metrics 的理解,並能針對實際的監控場景提出更好的指標(Metrics)設計。
1 引言
Prometheus 是一個開源的監控解決方案,它能夠提供監控指標數據的采集、存儲、查詢以及監控告警等功能。作為雲原生基金會(CNCF)的畢業項目,Prometheus 已經在雲原生領域得到了大范圍的應用,並逐漸成為了業界最流行的監控解決方案之一。
Prometheus 的部署和使用可以說是簡單易上手,但是如何針對實際的問題和需求設計適宜的 Metrics 卻並不是那么直接可行,反而需要優先解決暴露出來的諸多不確定問題,比如何時選用 Vector,如何設計適宜的 buckets,Summary 和 Histogram 指標類型的取舍等。然而,要想有效助力故障及問題的發現與定位,必須要有一個合理有效的 Metrics 去全面高效地反映系統實時狀態。
本文將介紹基於最佳實踐的 Metrics 設計方法,並結合具體的場景實例——TKE 的網絡組件 IPAMD 的內部監控,以個人實踐經驗談一談如何設計和實現適合的、能夠更好反映系統實時狀態的監控指標(Metrics)。
本文之后的第 2 節將對 Prometheus 的 Metrics 做簡單的介紹,對此已有了解的讀者可跳過。之后第 3 節將介紹 Metrics 設計的最佳實踐。第 4 節將結合具體的實例應用相關設計方法。第 5 節將介紹 Golang 上指標收集的實現方案。
2 Prometheus Metrics Type 簡介
Prometheus Metrics 是整個監控系統的核心,所有的監控指標數據都由其記錄。Prometheus 中,所有 Metrics 皆為時序數據,並以名字作區分,即每個指標收集到的樣本數據包含至少三個維度的信息:名字、時刻和數值。
而 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,標簽有region和method,以此表示不同地域服務器的不同請求方法的請求延遲。
以下將對每個類型做詳細的介紹。
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:][a-zA-Z0-9:]*
-
應該包含一個單詞作為前綴,表明這個 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:
-
優點:
-
- 能夠非常准確的計算百分位數
- 不需要提前知道數據的分布
-
缺點:
-
- 靈活性不足,實時性需要通過 maxAge 來保證,寫死了后靈活性就不太夠(比如想知道更長維度的百分位數)
- 在 client 端已經做了聚合,即在各個用戶集群的 ipamd 中已經聚合了,我們如果需要觀察全部 user 下的百分位數數據是不行的(只能看均值)
- 用戶集群的 ipamd 的調用頻率可能很低(如小集群或者穩定集群),這種情況下 client 端聚合計算百分位數值失去意義(數據太少不穩定),如果把 maxAge 增大則失去實時性
Histogram:
-
優點:
-
- 兼具靈活性和實時性
- 可以靈活的聚合數據,觀察各個尺度和維度下的數據
-
缺點:
-
- 需要提前知道數據的大致分布,並以此設計出合適而准確的桶序列
- 難以通過 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 的設計也需要變化來適應測量數據分布發生的變化,從而獲得更精確的百分位數測量值。
參考資料
- Prometheus 官方文檔 :https://prometheus.io/docs/introduction/overview/Prometheus
- Go client library:https://github.com/prometheus/client_golang
【騰訊雲原生】雲說新品、雲研新術、雲游新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多干貨!!

