Prometheus 的監控對象各式各樣,沒有統一標准。為了解決這個問題,Prometheus 制定了一套監控規范,符合這個規范的樣本數據可以被 Prometheus 采集並解析樣本數據。Exporter 在 Prometheus 監控系統中是一個采集監控數據並通過 Prometheus 監控規范對外提供數據的組件,針對不同的監控對象可以實現不同的 Exporter,這樣就解決了監控對象標准不一的問題。從廣義上說,所有可以向 Prometheus 提供監控樣本數據的程序都可以稱為 Exporter,Exporter 的實例也就是我們上期所說的"target"。
Exporter 的運行方式
Exporter 有兩種運行方式
-
集成到應用中
使用 Prometheus 提供的 Client Library,可以很方便地在應用程序中實現監控代碼,這種方式可以將程序內部的運行狀態暴露給 Prometheus,適用於需要較多自定義監控指標的項目。目前一些開源項目就增加了對 Prometheus 監控的原生支持,如 Kubernetes,ETCD 等。
-
獨立運行
在很多情況下,對象沒法直接提供監控接口,可能原因有:
1. 項目發布時間較早,並不支持 Prometheus 監控接口,如 MySQL、Redis;
2. 監控對象不能直接提供 HTTP 接口,如監控 Linux 系統狀態指標。
對於上述情況,用戶可以選擇使用獨立運行的 Exporter。除了用戶自行實現外,Prometheus 社區也提供了許多獨立運行的 Exporter,常見的有 Node Exporter、MySQL Server Exporter。更多詳情可以到官網了解:https://prometheus.io/docs/instrumenting/exporters/
Exporter 接口數據規范
Exporter 通過 HTTP 接口以文本形式向 Prometheus 暴露樣本數據,格式簡單,沒有嵌套,可讀性強。每個監控指標對應的數據文本格式如下:
# HELP <監控指標名稱> <監控指標描述> # TYPE <監控指標名稱> <監控指標類型> <監控指標名稱>{ <標簽名稱>=<標簽值>,<標簽名稱>=<標簽值>...} <樣本值1> <時間戳> <監控指標名稱>{ <標簽名稱>=<標簽值>,<標簽名稱>=<標簽值>...} <樣本值2> <時間戳> ...
- 以 # 開頭的行,如果后面跟着"HELP",Prometheus 將這行解析為監控指標的描述,通常用於描述監控數據的來源;
- 以 # 開頭的行,如果后面跟着"TYPE",Prometheus 將這行解析為監控指標的類型,支持的類型有:Counter、Gauge、Histogram、Summary、Untyped。在往期文章中介紹過 Prometheus 在存儲數據時是不區分數據類型的,所以當你在猶豫一個數據類型應該用 Counter 或 Gauge 時,可以試試 Untype;
- 以 # 開頭的行,如果后面沒有跟着"HELP"或"TYPE",則 Prometheus 將這行視為注釋,解析時忽略;
- 如果一個監控指標有多條樣本數據,那么每條樣本數據的標簽值組合應該是唯一的;
- 每行數據對應一條樣本數據;
- 時間戳應為采集數據的時間,是可選項,如果 Exporter 沒有提供時間戳的話,Prometheus Server 會在拉取到樣本數據時將時間戳設置為當前時間;
- Summary 和 Histogram 類型的監控指標要求提供兩行數據分別表示該監控指標所有樣本的和、樣本數量,命名格式為:<監控指標名稱>_sum、<監控指標名稱>_count;
- Summary 類型的樣本數據格式:1. 根據 Exporter 提供的分位點,樣本會被計算后拆分成多行數據,每行使用標簽"quantile"區分,"quantile"的值包括 Exporter 提供的所有分位點。2. 數據的排列順序必須是按照標簽"quantile"值遞增;3. 舉個栗子:一個名為x的監控指標,提供的分位點為:0.5, 0.9, 0.99,那么它暴露給 Prometheus 的接口數據格式如下:
# HELP x balabala
# TYPE x summary
x{quantile="0.5"} value1
x{quantile="0.9"} value2
x{quantile="0.99"} value3
x_sum sum(values)
x_count count(values)
- Histogram 類型的樣本數據格式:
1. 根據 Exporter 提供的 Bucket 值,樣本會被計算后拆分成多行數據,每行使用標簽"le"區分,"le"為 Exporter 提供的 Buckets;
2. 數據的排列順序必須是按照標簽"le"值遞增;
3. 必須要有一行數據的標簽 le="+Inf",值為該監控指標的樣本總數;
4. 舉個栗子:一個名為 x 的監控指標,提供的 Buckets 為:20, 50, 70,那么它暴露給 Prometheus 的接口數據格式如下:
# HELP x The temperature of cpu
# TYPE x histogram
x_bucket{le="20"} value1
x_bucket{le="50"} value2
x_bucket{le="70"} value3
x_bucket{le="+Inf"} count(values)
x_sum sum(values)
x_count count(values)
這樣的文本格式也有不足之處:
1. 文本內容可能過於冗長;
2. Prometheus 在解析時不能校驗 HELP 和 TYPE 字段是否缺失,如果缺失 HELP 字段,這條樣本數據的來源可能就難以判斷;如果缺失 TYPE 字段,Prometheus 對這條樣本數據的類型就無從得知;
3. 相比於 protobuf,Prometheus 使用的文本格式沒有做任何壓縮處理,解析成本較高。
MySQL Server Exporter
針對被廣泛使用的關系型數據庫 MySQL,Prometheus 官方提供了 MySQL Server Exporter,支持 MySQL 5.6 及以上版本,對於 5.6 以下的版本,部分監控指標可能不支持。
MySQL Server Exporter 監控的信息包括了常用的 global status/variables 信息、schema/table 的統計信息、user 統計信息、innodb 的信息以及主從復制、組復制的信息,監控指標比較全面。但是由於它提供的監控指標中缺少對 MySQL 實例的標識,所以當一台主機上存在多個 MySQL 實例,需要運行多個 MySQL Server Exporter 進行監控時,就會難以區分實例信息。具體使用方式可參考:https://github.com/prometheus/mysqld_exporter
Node Exporter
Prometheus 官方的 Node Exporter 提供對 *NIX 系統、硬件信息的監控,監控指標包括 CPU 使用率/配置、系統平均負載、內存信息、網絡狀況、文件系統信息統計、磁盤使用情況統計等。對於不同的系統,監控指標會有所差異,如 diskstats 支持 Darwin, Linux, OpenBSD 系統;loadavg 支持 Darwin, Dragonfly, FreeBSD, Linux, NetBSD, OpenBSD, Solaris 系統。Node Exporter 的監控指標沒有對主機身份的標識,可以通過 relabel 功能在 Prometheus Server 端增加一些標識標簽。具體使用方式可參考:https://github.com/prometheus/node_exporter
如何實現一個 Exporter
編寫一個簡單的 Exporter
使用 prometheus/client_golang 包,我們來編寫一個簡單的 Exporter,包括 Prometheus 支持的四種監控指標類型
package main import ( "log" "net/http" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" ) var ( //使用GaugeVec類型可以為監控指標設置標簽,這里為監控指標增加一個標簽"device" speed = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "disk_available_bytes", Help: "Disk space available in bytes", }, []string{"device"}) tasksTotal = prometheus.NewCounter(prometheus.CounterOpts{ Name: "test_tasks_total", Help: "Total number of test tasks", }) taskDuration = prometheus.NewSummary(prometheus.SummaryOpts{ Name: "task_duration_seconds", Help: "Duration of task in seconds", //Summary類型的監控指標需要提供分位點 Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }) cpuTemperature = prometheus.NewHistogram(prometheus.HistogramOpts{ Name: "cpu_temperature", Help: "The temperature of cpu", //Histogram類型的監控指標需要提供Bucket Buckets: []float64{20, 50, 70, 80}, }) ) func init() { //注冊監控指標 prometheus.MustRegister(speed) prometheus.MustRegister(tasksTotal) prometheus.MustRegister(taskDuration) prometheus.MustRegister(cpuTemperature) } func main() { //模擬采集監控數據 fakeData() //使用prometheus提供的promhttp.Handler()暴露監控樣本數據 //prometheus默認從"/metrics"接口拉取監控樣本數據 http.Handle("/metrics", promhttp.Handler()) log.Fatal(http.ListenAndServe(":10000", nil)) } func fakeData() { tasksTotal.Inc() //設置該條樣本數據的"device"標簽值為"/dev/sda" speed.With(prometheus.Labels{"device": "/dev/sda"}).Set(82115880) taskDuration.Observe(10) taskDuration.Observe(20) taskDuration.Observe(30) taskDuration.Observe(45) taskDuration.Observe(56) taskDuration.Observe(80) cpuTemperature.Observe(30) cpuTemperature.Observe(43) cpuTemperature.Observe(56) cpuTemperature.Observe(58) cpuTemperature.Observe