如果您正在为自己的代码进行指标监测,应遵循使用Prometheus客户端库进行代码监测的一般规则。在从其他监控或监测系统中获取度量标准时,情况就不那么绝对了。
本文档包含了编写导出器或自定义采集器时需要考虑的事项。其中介绍的理论对于直接进行监测的人也会有帮助。
如果您正在编写导出器(exporter)并对此处的任何内容不清楚,请通过 IRC(libera上的#prometheus)或邮件列表与我们联系。
可维护性和纯净性(Maintainability and purity)
当编写一个导出器时,你需要做的主要决策是愿意为了获得完美的指标而投入多少工作。
如果所涉及的系统只有少数几个指标,而且很少发生变化,那么追求完美是一个容易的选择,HAProxy exporter 就是一个很好的例子。
然而,如果你试图在系统拥有数百个指标,并且这些指标会随着新版本的发布而频繁变化时追求完美,那么你将会面对大量的持续工作。MySQL exporter 就属于这一类别。
Node exporter 则是上述两种情况的混合,其复杂性因模块而异。例如,mdadm 收集器手动解析文件并公开专为该收集器创建的指标,因此我们可以将指标做到完美无缺。而对于 meminfo 收集器来说,结果在不同内核版本上可能有所差异,因此我们只需要进行必要的转换以生成有效的指标。
配置(Configuration)
在处理应用程序时,你应该尽量做到一个导出器除了配置应用程序位置之外,用户无需进行自定义配置。你可能还需要提供过滤某些指标的能力,特别是在大规模环境下,某些指标可能粒度太过细致且计算代价高昂,例如 HAProxy exporter 允许过滤每个服务器的统计信息。类似地,一些昂贵的指标默认情况下是禁用的。
在与其他监控系统、框架和协议一起使用时,通常需要提供额外的配置或定制化支持来生成适合 Prometheus 的指标。在最理想的情况下,监控系统有与 Prometheus 非常相似的数据模型,可以自动确定如何转换指标。这适用于 Cloudwatch、SNMP 和 collectd 等情况。大多数情况下,我们需要允许用户选择他们想要的指标。
在其他情况下,系统的指标完全不符合标准,这取决于系统的使用方式和底层应用程序。在这种情况下,用户必须告诉我们如何转换指标。JMX exporter 是最糟糕的情况,Graphite exporter 和 StatsD exporter 也需要额外配置来提取标签。
建议确保导出器能够开箱即用,无需配置,并提供一系列示例配置以进行必要的转换。
YAML 是 Prometheus 的标准配置格式,所有配置默认应使用 YAML。
指标(metrics)
命名(Naming)
遵循 metric 命名的最佳实践:
- 指标名称应该让了解 Prometheus 但对监控的应用系统不太了解的人也能猜测出指标的含义。例如,一个名为
http_requests_total
的指标并不是非常有用,这些指标是在请求进入时进行测量,还是在某个过滤器中,或者是当它们到达用户代码时?而名称为requests_total
的 metric 更糟糕,你甚至不知道它是什么类型的请求。 - 对于直接的监控,每个指标应该存在且仅存在于一个文件中。同样,在导出器(exporter)和收集器(collector)中,一个指标应该仅使用于一个子系统,并相应地命名。
- 指标名称不应该动态生成,除非你在编写自定义收集器(collector)或导出器(exporter)。
- 应用程序的指标名称通常应以导出器名称作为前缀,例如 haproxy_up。
- 指标必须使用基本单位(例如秒、字节),而将其转换为更易读的形式则交给绘图工具完成。无论你最终使用哪种单位, 指标名称中的单位必须与其使用的单位一致。推荐使用比率(ratio)而不是百分比。比较好的做法是,为比率的两个组成部分都指定一个计数器(Counter)。
- 指标名称不应包含它们导出时带有的标签,例如 by_type,因为当标签被聚合时它就没有意义了。唯一的例外是当你通过多个指标使用不同的标签导出相同的数据时,这通常是区分它们的最好方法。对于直接的监控指标而言,只在导出标签值过多的单个指标时才会遇到这种情况。
- Prometheus 的指标名称和标签名称使用 snake_case(蛇形命名法)。推荐将驼峰命名转换为蛇形命名法,自动转换不一定都适用,例如 myTCPExample 或 isNaN,所以偶尔也可以保持原样。
- 指标名称不应包含冒号,这些冒号被用户定义的记录规则保留使用以进行聚合。
- 只有[a-zA-Z0-9:_]是指标名称中有效的字符。
- _sum、_count、_bucket 和 _total 后缀用于 Summary、Histogram 和 Counter。使用这些后缀时要确定指标的类型是否正确。
- _total 约定用于计数器,当使用 Counter 时应该用它。
- process_ 和 scrape_ 前缀是保留的。如果符合要匹配的语义,可以在这些前缀上添加自己的前缀。例如,Prometheus 在指标名称中使用 scrape_duration_seconds 表示抓取花费的时间,最好还为特定导出器提供指标 ,例如 jmx_scrape_duration_seconds,表示特定导出器执行任务所需的时间。对于可以访问 PID 的进程统计信息,Go 和 Python 提供了处理此问题的收集器。HAProxy 导出器是一个很好的例子。
- 当你有成功请求计数和失败请求计数时,最好的方法是将它们分别暴露为一个总请求数指标和失败请求指标。这样可以轻松计算失败比率。不要使用带有失败或成功标签的单个指标 。类似地,对于缓存的命中与未命中,最好使用一个指标表示总数,另一个指标表示命中数。
- 需要考虑使用监控的人对指标名称进行代码或网络搜索的情况。如果只给专业领域的人使用,例如 SNMP 和网络工程师,那么可以使用原始的专业名称。但是有些导出器不是只给专业人员使用,例如 MySQL 导出器的指标可能会被各种各样的人使用,而不仅仅是 DBA。可以将原始名称放在 HELP 信息中方便给不同的人使用。
标签(Labels)
有关标签的一般建议。
- 避免使用 type 作为标签名称,它过于通用且意义不明。还应尽量避免使用与标签可能会发生冲突的名称,例如 region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、environment 和 env。但是,如果应用程序使用了这些名称中的一个来代表某种特定资源,重命名可能导致混淆,此时可以酌情使用。
- 不要仅仅因为它们有相同的前缀就将它们放入同一个指标中。除非你确定某些内容作为一个指标是有意义的,否则应该使用多个指标。
- 对于直方图(Histogram)来说,标签 le 具有特殊含义,而对于摘要(Summary)来说,标签 quantile 具有特殊含义。应避免使用这些标签。
- “读/写”和“发送/接收”最好作为单独的指标,而不是作为标签。这是因为通常您一次只关心其中一个指标,并且这种方式更容易使用。
一般经验,一个指标在求和或平均时应该是有意义的。还有另一种情况涉及到导出器,如果数据基本上是表格状的,应该避免让用户对名称使用正则表达式的情况。以主板上的电压传感器为例,尽管在它们之间进行数学运算没有意义,但将它们放在一个指标中而不是分散为多个指标要更好。一个度量标准中的所有值应该具有相同的单位,例如如果将风扇转速与电压混合在一起,还得费时间将他们分开。
不要这样做:
my_metric{label="a"} 1 my_metric{label="b"} 6 my_metric{label="total"} 7 # total 应该单独做指标
也不要这样做:
my_metric{label="a"} 1 my_metric{label="b"} 6 my_metric{} 7 # 含义不明的指标
前者在对指标进行求和 (sum) 时会重复计算,而后者则求和 (sum) 时意义不明。例如,某些客户端库(例如 Go)将会在自定义采集器中阻止你的错误使用后者,并且所有客户端库都应该阻止直接使用不带标签的指标名称。永远不要使用这些错误示范,可以使用 Prometheus 的聚合功能代替他们。
- 如果您的监控系统导出了带有 label="total" 的指标,请删除它。如果由于某种原因必须保留它,例如总数包括未单独计数的项目,请换一个指标名。
指标的标签应尽量简洁,每个额外的标签都会增加用户在编写 PromQL 的心智负担。因此,避免使用那些被移除而不影响时间序列唯一性的标签。可以通过信息指标(info metric)添加有关指标的额外信息,例如,参考以下处理版本号的方法。
但是,有一些情况下,几乎所有使用该指标的用户都希望获得附加信息。如果是这种情况,可以添加一个非唯一的标签,而不使用 info 指标,就是正确的解决方案。例如,mysqld_exporter 的 mysqld_perf_schema_events_statements_total 的 digest 标签是完整查询语句的哈希,并且足以确保唯一性。然而,如果没有人类可读的 digest_text 标签,这个 digest 标签就没有什么用处,因为对于长查询语句,digest_text 标签只会包含查询语句的开始部分,因此不是唯一的。因此,我们最终会得到用于人类的 digest_text 标签和用于唯一性的 digest 标签。
动态标签而非静态标签(Target labels, not static scraped labels)
如果你想要对所有指标使用相同的标签,请停止这样做。通常有两种情况会出现这种情况。
- 对指标上添加一些标签会很有用,比如软件的版本号。但是,应该使用 https://www.robustperception.io/how-to-have-labels-for-machin... 中描述的方法。
标签实际上是一个目标标签(target label 即在配置抓取目标时动态设置的标签)。这些标签来自你的基础架构设置,比如区域、集群名称等,而不是应用本身。一个应用本身无法确定它在你的标签分类中属于哪一种,这是运行 Prometheus 服务器的人配置的,监控同一个应用的不同人可能会给它不同的标签值。
因此,这些标签应该通过你正在使用的任何服务发现机制放在 Prometheus 的抓取配置中。在这里应用机器角色的概念也是可以的,因为至少对于一些正在抓取它的人来说,这可能是有用的信息。
指标类型(Types)
你应该尽量使你的指标类型与 Prometheus 的类型相匹配。这通常针对计数器(Counter)和仪表盘(Gauge)来说。带有 _count 和 _sum 的 Summary 也比较常见,偶尔也会看到带有 quantile 的。Histogram 比较少见,如果你用到了 Histogram,请注意他的数据格式中带有累积值。
通常情况下,指标的类型可能不明显,特别是如果你正在自动处理一组指标。总体而言,UNTYPED 是一个安全的默认值。
Counter 类型的值是只增不减的,如果你有一个来自其他仪表系统的可以递减的计数器,例如 Dropwizard 指标,那么它实际上不是 Counter,而是一个 Guage。在这种情况下,UNTYPED 可能是最好的类型,有时候我们可能会错误的将 Gauge 当成 Counter 使用
帮助信息(Help strings)
当你转换指标时,让用户能够追溯到原始指标和转换的规则及转换原因是很有用的。在帮助字符串中加入收集器或导出器的名称、应用的规则的 ID 以及原始指标的名称和详细信息将极大地帮助用户理解指标。
Prometheus 不支持对一个一个指标指定不同的帮助信息。如果你从多个指标中生成一个指标,请选择其中一个放入帮助字符串中。
例如,SNMP 导出器使用 OID,JMX 导出器放入了示例 MBean 名称。HAProxy 导出器有手工编写的字符串。节点导出器也有很多的类似案例。
精简无用的统计数据(Drop less useful statistics)
一些仪表系统额外暴露了 1 分钟、5 分钟、15 分钟的速率,以及自应用启动以来的平均速率(在 Dropwizard 指标中称为平均值),最小值、最大值和标准偏差。
这些都应该被丢弃,因为它们并不是非常有用,而且会增加复杂度。Prometheus 可以自己计算速率,通常更准确,因为暴露的平均值通常是指数衰减的。你不知道最小值或最大值是在什么时间计算的,而标准偏差在统计上是无用的,如果需要的话,你可以用平方和、_sum 和 _count 来计算它。
分位数也有相似的问题,你可以选择丢弃它们或将使用 Summary。
点字符串(Dotted strings)
许多监控系统没有标签,而是采用类似于 my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3 这样的方式。
Graphite 和 StatsD 导出器共同实现了一种使用小型配置语言进行转换的方法。其他导出器应该实现相同的方式。
这种转换目前只在 Go 中实现,可以将它抽成一个单独的库方便使用。
收集器(Collectors)
在实现导出器的收集器时,建议不要使用全局变量存储指标并在每次抓取时更新指标。而应该每次都创建新的指标。在 Go 中,您可以在 Collect()
方法中使用 MustNewConstMetric
来实现这一点。对于 Python,请参阅 https://github.com/prometheus/client_python#custom-collectors,而对于 Java,请在 collect
方法中生成 List<MetricFamilySamples>
,例如请参考 StandardExports.java
。
采取这种方法的原因有两个。首先,两次抓取可能同时发生,使用文件级全局变量可能导致并发问题。其次,使用全局变量会导出某些已经不再使用的标签值的指标。
使用全局变量指标对导出器本身进行指标监测是可以的,例如跟踪所有抓取中导出器传输的总字节数或调用次数。但是,对于 Blackbox exporter 和 SNMP exporter 等不与单个目标绑定的导出器,这些指标应仅在普通的 /metrics
调用中暴露,而不是在特定目标的抓取过程中。
抓取动作本身的指标(Metrics about the scrape itself)
有时,你可能希望导出一些关于抓取操作的指标,比如它耗时多久或者处理了多少条记录。这些指标应该作为仪表盘(Gauge)暴露,因为它们是关于一个事件抓取。指标名称应该以前端导出器的名称为前缀,例如jmx_scrape_duration_seconds。通常,_exporter这个后缀会被省略,如果导出器本身也可以用作一个收集器(collector),那么应该省略它。
机器和进程监测(Machine and process metrics)
许多系统,例如 Elasticsearch,会暴露机器指标,比如 CPU、内存和文件系统信息。由于节点导出器(node exporter)已经在 Prometheus 生态系统中提供了这些指标,因此这些指标应该舍弃。
在 Java 中,许多监控框架会暴露进程级别的和 JVM 级别的统计信息,如 CPU 使用率和垃圾回收(GC)指标。Java 客户端和 JMX 导出器已经通过 DefaultExports.java 以首选形式包含了这些指标,因此这些指标也应该被舍弃。
其他语言和框架的情况也类似。
部署(Deployment)
每个导出器应该只监控一个确切的应用程序实例,最好就运行于同一个机器上。这意味着对于你运行的每个 HAProxy,你都应该运行一个 haproxy_exporter 进程。对于每个装有 Mesos 工作节点的机器,你都应该运行Mesos 导出器,如果一台机器同时拥有工作节点和主节点,那么还应该单独运行一个主节点导出器。
这背后的理论是,你正在做的事情就是直接监控,我们尽量在其他场景中尽可能接近这种方式。这意味着所有的服务发现都应该在 Prometheus 中完成,而不是在导出器中。这也有一个好处,即 Prometheus 拥有它所需的目标(target)信息,允许用户使用 blackbox exporter 探测你的服务。
这里有两个例外:
- 当 exporter 与你要监控的应用程序一起运行完全没有意义时。SNMP、blackbox 和 IPMI exporter 就是典型的情况。IPMI 和 SNMP exporter 因为设备通常是黑盒,在上面运行代码是不可能的(尽管如果你能在它们上面运行节点导出器会更好),使用 blackbox exporter 监控一个 DNS 名称这样的东西,上面也不需要任何应用程序运行。在这种情况下,Prometheus 仍然应该进行服务发现,并将目标传递给需要被抓取的实例。可以查看 blackbox 和 SNMP 导出器的例子。请注意,目前只有使用 Go、Python 和 Java 客户端库才能编写这种类型的导出器。
当你从一个系统的随机实例中拉取一些统计数据,而不在乎数据来源于哪个实例时。例如一组 MySQL 副本,你可能想要运行一些业务查询并导出数据。使用你常用的负载均衡方式抓取一个副本的导出器就是最合适的方式。
这并不适用于监控具有主节点选举的系统,在这种情况下,你应该分别监控每个实例,并在 Prometheus 中处理“主节点性”。因为主节点是不确定的,而且在 Prometheus 中改变监控目标将会导致一些异常情况。
调度(Scheduling)
指标应该仅在 Prometheus 抓取它们时从应用程序中拉取,导出器不应该根据自己的计时器执行抓取。也就是说,所有的抓取操作应该是实时的。
相应地,你不应该为你所暴露的指标设置时间戳,让 Prometheus 来处理这件事。如果你认为你需要时间戳,那么你可能需要的是 Pushgateway。
如果某个指标的获取特别昂贵,即耗时超过一分钟,那么对其进行缓存是可以接受的。这应该在指标的 HELP 信息中注明。
Prometheus 的默认抓取超时时间是 10 秒。如果你的导出器有可能会超过这个时间,你应该在用户文档中明确指出。
推送(Pushes)
一些应用程序和监控系统只可以推送指标,例如 StatsD、Graphite 和 collectd。
这里有两点考虑。
首先,指标什么时候过期?collectd 和与 Graphite 通信的系统都会定期导出指标,当它们停止运行时,我们希望同时停止暴露这些指标。collectd 包含了一个过期时间可供我们使用;Graphite 没有,所以它在导出器上设置了一个标志。
StatsD 有点不同,因为它处理的是事件而不是指标。最好的模型是在每个应用程序运行时同时运行一个导出器,并在应用程序重启时重启它们,以重置状态。
其次,这类系统倾向于允许用户发送增量值或原始指标的绝对值。我们应尽可能使用指标的绝对值,因为这是 Prometheus 的通用模型。
对于服务级别的指标,例如服务级别的批量任务,你应该让你的导出器将指标推送到 Pushgateway,并在事件结束后将 exporter 终止,而不是自己处理状态。对于实例级别的批量指标,目前还没有明确的模式。选择可以有两个:
- 酌情复用节点导出器的文本文件收集器(textfile collector),它依赖内存中的状态(如果你不需要在重启后持久化,这可能是最佳选择),
- 实现类似于文本文件收集器的功能。
抓取失败(Failed scrapes)
目前有两种处理抓取失败的模式,当你与之通信的应用程序不能正确响应或有其他问题时:
- 返回一个5xx错误。
- 用一个像 myexporter_up(例如 haproxy_up)的变量,其值根据抓取是否成功而为 0 或 1。
如果你希望即使在抓取失败的情况下,你也可以获得一些可能有用的指标,那么后者是更好的做法,例如 HAProxy 导出器提供进程统计信息。
返回 5xx 对于用户来说稍微容易一些,因为健康检查的工作方式没有变,但是你无法区分是导出器出错了还是应用程序宕机了。
入口页(Landing page)
如果用户访问 http://yourexporter/ 时,能够看到一个简单的HTML页面,其中包含导出器的名称,以及一个指向 /metrics 页面的链接,这将会更加方便用户使用。
端口号(Port numbers)
用户可能在同一台机器上有许多导出器和 Prometheus 组件,因此为了方便起见,每个组件都应该有一个唯一的端口号。
https://github.com/prometheus/prometheus/wiki/Default-port-allocations 是我们跟踪这些端口号的地方,这是公开可编辑的。
在开发你的导出器时,你可以自由地获取下一个空闲的端口号,最好是在公开宣布之前。如果你还没有准备好发布,写上你的用户名和“WIP”(工作中)也是可以的。
这是一个使我们用户的生活更轻松的注册表,并不是对开发特定导出器的承诺。对于内部应用程序的导出器,我们建议使用默认端口分配范围之外的端口。
公开(Announcing)
一旦你准备向大家公开宣布你的导出器时,你可以发送邮件到邮件列表并且发起一个 PR 将它添加到可以使用的导出器列表中
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。