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