编写导出器

如果您正在为自己的代码进行插桩,应遵循使用 Prometheus 客户端库进行代码插桩的一般规则。当从其他监控或插桩系统获取指标时,情况往往不是那么黑白分明。

本文档包含在编写导出器或自定义收集器时应考虑的事项。所涵盖的理论对于直接进行插桩的人员也会感兴趣。

如果您在编写导出器时对本文档的任何内容不清楚,请通过 IRC(libera 上的 #prometheus)或邮件列表与我们联系。

可维护性与纯粹性

在编写导出器时,您需要做出的主要决定是,您愿意投入多少工作来从中获得完美的指标。

如果所讨论的系统只有少数几个很少变化的指标,那么让一切都变得完美是一个简单的选择,一个很好的例子是 HAProxy 导出器 

另一方面,如果系统有数百个指标,并且随着新版本频繁变化,而您试图让一切都变得完美,那么您就为自己承担了大量的持续工作。MySQL 导出器  就属于这一类。

node 导出器  是两者的混合体,其复杂性因模块而异。例如,mdadm 收集器手动解析一个文件并暴露专门为该收集器创建的指标,所以我们不妨把指标做得正确。对于 meminfo 收集器,结果因内核版本而异,因此我们最终只做足够的转换来创建有效的指标。

配置

在处理应用程序时,您的目标应该是导出一个除了告诉它应用程序在哪里之外,不需要用户进行任何自定义配置的导出器。您可能还需要提供过滤某些指标的能力,因为在大型设置中它们可能粒度过细且代价高昂,例如 HAProxy 导出器  允许过滤每个服务器的统计信息。同样,可能有一些代价高昂的指标默认是禁用的。

在与其他监控系统、框架和协议一起工作时,您通常需要提供额外的配置或自定义来生成适合 Prometheus 的指标。在最好的情况下,监控系统的数据模型与 Prometheus 足够相似,您可以自动确定如何转换指标。这适用于 Cloudwatch SNMP collectd 。最多,我们需要让用户能够选择他们想要提取哪些指标。

在其他情况下,来自系统的指标完全不标准,取决于系统的使用和底层应用程序。在这种情况下,用户必须告诉我们如何转换指标。JMX 导出器  在这方面是最大的“罪魁祸首”,而 Graphite StatsD  导出器也需要配置来提取标签。

建议确保导出器开箱即用,无需配置,并在需要时提供一系列用于转换的示例配置。

YAML 是 Prometheus 的标准配置格式,所有配置默认应使用 YAML。

指标

命名

遵循指标命名的最佳实践

通常,指标名称应允许熟悉 Prometheus 但不熟悉特定系统的人能够很好地猜测指标的含义。一个名为 http_requests_total 的指标不是非常有用——这些请求是在它们进入时、在某个过滤器中还是到达用户代码时测量的?而 requests_total 更糟糕,是什么类型的请求?

对于直接插桩,一个给定的指标应该只存在于一个文件中。因此,在导出器和收集器中,一个指标应该只适用于一个子系统,并相应地命名。

除非在编写自定义收集器或导出器时,否则不应以程序方式生成指标名称。

应用程序的指标名称通常应以导出器名称为前缀,例如 haproxy_up

指标必须使用基本单位(例如,秒、字节),并将它们转换为更易读的格式留给绘图工具。无论您最终使用什么单位,指标名称中的单位必须与使用的单位相匹配。同样,暴露比率,而不是百分比。更好的是,为比率的两个组成部分分别指定一个计数器。

指标名称不应包含它们导出的标签,例如 by_type,因为如果该标签被聚合掉,这将没有意义。

唯一的例外是,当您通过多个指标导出具有不同标签的相同数据时,在这种情况下,这通常是区分它们的最理智的方法。对于直接插桩,这只应在导出带有所有标签的单个指标会产生过高基数时出现。

Prometheus 的指标和标签名称使用 snake_case(蛇形命名法)。将 camelCase(驼峰命名法)转换为 snake_case 是可取的,但自动转换对于像 myTCPExampleisNaN 这样的东西并不总能产生好的结果,所以有时最好保持原样。

暴露的指标不应包含冒号,这些是为用户定义的记录规则在聚合时使用的保留字符。

指标名称中只允许使用 [a-zA-Z0-9:_]

_sum_count_bucket_total 后缀用于摘要(Summaries)、直方图(Histograms)和计数器(Counters)。除非您正在生成其中之一,否则请避免使用这些后缀。

_total 是计数器的约定,如果您使用的是 COUNTER 类型,则应使用它。

process_scrape_ 前缀是保留的。如果它们遵循匹配的语义,可以在这些前缀上添加您自己的前缀。例如,Prometheus 有 scrape_duration_seconds 表示一次抓取花费了多长时间,一个好的做法是也有一个以导出器为中心的指标,例如 jmx_scrape_duration_seconds,表示特定导出器完成其工作所需的时间。对于可以访问 PID 的进程统计信息,Go 和 Python 都提供了可以为您处理此问题的收集器。一个很好的例子是 HAProxy 导出器 

当您有成功请求计数和失败请求计数时,最好的暴露方式是使用一个指标表示总请求数,另一个指标表示失败请求数。这使得计算失败率变得容易。不要使用一个带有 failed 或 success 标签的指标。同样,对于缓存的命中或未命中,最好有一个表示总数的指标和另一个表示命中的指标。

考虑使用监控的人进行代码或网络搜索指标名称的可能性。如果名称非常成熟且不太可能在习惯这些名称的人之外的领域使用,例如 SNMP 和网络工程师,那么保持原样可能是一个好主意。这种逻辑并不适用于所有导出器,例如 MySQL 导出器指标可能被各种人使用,而不仅仅是 DBA。带有原始名称的 HELP 字符串可以提供与使用原始名称大部分相同的好处。

标签

阅读关于标签的一般建议

避免使用 type 作为标签名,它太通用了,而且通常没有意义。您还应尽可能避免使用可能与目标标签冲突的名称,例如 regionzoneclusteravailability_zoneazdatacenterdcownercustomerstageserviceenvironmentenv。但是,如果应用程序就是这样称呼某个资源的,最好不要通过重命名它来引起混淆。

避免仅仅因为它们共享一个前缀就把东西放进一个指标里的诱惑。除非您确定某些东西作为一个指标是有意义的,否则多个指标更安全。

标签 le 对直方图有特殊含义,quantile 对摘要有特殊含义。通常避免使用这些标签。

读/写和发送/接收最好作为单独的指标,而不是作为标签。这通常是因为您一次只关心其中一个,并且这样使用它们更容易。

经验法则是,一个指标在求和或求平均时应该有意义。在导出器中还有另一种情况,即数据基本上是表格形式的,否则将需要用户对指标名称进行正则表达式才能使用。考虑您主板上的电压传感器,虽然对它们进行数学运算没有意义,但将它们放在一个指标中比为每个传感器创建一个指标更有意义。一个指标内的所有值应(几乎)总是具有相同的单位,例如考虑如果风扇速度与电压混合在一起,而您没有办法自动将它们分开。

不要这样做

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

或者这样做

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

前者会破坏对您的指标执行 `sum()` 的人的操作,后者会破坏求和并且很难处理。一些客户端库,例如 Go,会主动阻止您在自定义收集器中做后者,并且所有客户端库都应该阻止您在直接插桩中做后者。永远不要做这两种事,而是依赖 Prometheus 的聚合功能。

如果您的监控系统暴露了像这样的总计值,请丢弃总计值。如果由于某种原因您必须保留它,例如总计值包含未单独计数的内容,请使用不同的指标名称。

插桩标签应尽可能少,每个额外的标签都意味着用户在编写 PromQL 时需要多考虑一个。因此,避免使用那些在不影响时间序列唯一性的情况下可以移除的插桩标签。关于指标的附加信息可以通过信息指标添加,例如下面如何处理版本号的例子。

然而,在某些情况下,预计几乎所有指标的用户都会需要这些附加信息。如果是这样,添加一个非唯一的标签,而不是一个信息指标,是正确的解决方案。例如,mysqld_exporter mysqld_perf_schema_events_statements_totaldigest 标签是完整查询模式的哈希值,足以保证唯一性。然而,如果没有人类可读的 digest_text 标签,它就没什么用处,对于长查询,该标签将只包含查询模式的开头,因此不是唯一的。因此,我们最终既有供人类使用的 digest_text 标签,又有保证唯一性的 digest 标签。

目标标签,而非静态抓取标签

如果您发现自己想对所有指标应用相同的标签,请停下来。

通常有两种情况会出现这种情况。

第一种情况是,某个标签对指标很有用,比如软件的版本号。相反,请使用 https://www.robustperception.io/how-to-have-labels-for-machine-roles/  中描述的方法。

第二种情况是,一个标签实际上是一个目标标签。这些是像区域、集群名称等,它们来自您的基础设施设置而不是应用程序本身。一个应用程序不应该说明它在您的标签分类中的位置,这应该由运行 Prometheus 服务器的人来配置,并且监控同一应用程序的不同人可能会给它不同的名称。

因此,这些标签应该放在 Prometheus 的抓取配置中,通过您使用的任何服务发现机制。在这里应用机器角色的概念也是可以的,因为对于至少一些抓取它的人来说,这可能是非常有用的信息。

类型

您应该尝试将您的指标类型与 Prometheus 类型匹配。这通常意味着计数器和仪表。摘要的 `_count` 和 `_sum` 也相对常见,偶尔您会看到分位数。直方图很少见,如果您遇到一个,请记住暴露格式暴露的是累积值。

通常情况下,指标的类型并不明显,特别是当您自动处理一组指标时。一般来说,`UNTYPED` 是一个安全的选择。

计数器不能下降,所以如果您有一个来自其他插桩系统的计数器类型,它可以被递减,例如 Dropwizard 指标,那么它就不是一个计数器,而是一个仪表。`UNTYPED` 可能是那里使用的最佳类型,因为如果它被用作计数器,`GAUGE` 将会产生误导。

帮助字符串

当您转换指标时,用户能够追溯到原始指标是什么,以及导致该转换的规则是什么,这将非常有用。将收集器或导出器的名称、任何应用的规则的ID以及原始指标的名称和详细信息放入帮助字符串中,将极大地帮助用户。

Prometheus 不喜欢一个指标有不同的帮助字符串。如果您从许多其他指标创建一个指标,请选择其中一个放入帮助字符串。

例如,SNMP 导出器使用 OID,JMX 导出器放入一个示例 mBean 名称。HAProxy 导出器  有手写的字符串。node 导出器  也有各种各样的例子。

丢弃不太有用的统计数据

一些插桩系统除了最小值、最大值和标准差外,还暴露 1 分钟、5 分钟、15 分钟的速率,以及自应用程序启动以来的平均速率(例如在 Dropwizard 指标中称为 `mean`)。

这些都应该被丢弃,因为它们不是很有用,并且会增加混乱。Prometheus 可以自己计算速率,而且通常更准确,因为暴露的平均值通常是指数衰减的。您不知道最小值或最大值是在什么时间计算的,标准差在统计上是无用的,如果您需要计算它,您可以随时暴露平方和、`_sum` 和 `_count`。

分位数有相关的问题,您可以选择丢弃它们或将它们放入摘要中。

点分隔字符串

许多监控系统没有标签,而是使用类似 `my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3` 的方式。

Graphite StatsD  导出器共享一种使用小型配置语言转换这些字符串的方法。其他导出器应该实现相同的功能。该转换目前仅在 Go 中实现,如果能将其分解为一个独立的库,将会受益匪浅。

收集器

在实现导出器的收集器时,您永远不应该使用通常的直接插桩方法,然后在每次抓取时更新指标。

相反,应该每次都创建新的指标。在 Go 中,这是通过在您的 `Collect()` 方法中使用 MustNewConstMetric  完成的。对于 Python,请参阅 https://github.com/prometheus/client_python#custom-collectors ,对于 Java,在您的 collect 方法中生成一个 `List<MetricFamilySamples>`,请参阅 StandardExports.java  中的示例。

这样做的原因有两方面。首先,两次抓取可能会同时发生,而直接插桩使用的是实际上是文件级别的全局变量,所以您会遇到竞争条件。其次,如果一个标签值消失了,它仍然会被导出。

通过直接插桩来检测您的导出器本身是可以的,例如,导出器在所有抓取中传输的总字节数或执行的调用次数。对于像 blackbox 导出器 SNMP 导出器  这样不与单个目标绑定的导出器,这些指标只应在普通的 `/metrics` 调用中暴露,而不应在抓取特定目标时暴露。

关于抓取本身的指标

有时,您可能希望导出关于抓取本身的指标,例如抓取耗时或处理的记录数。

这些指标应作为 gauges 暴露,因为它们是关于一个事件(即抓取)的,并且指标名称应以 exporter 名称为前缀,例如 jmx_scrape_duration_seconds。通常,_exporter 后缀会被省略,如果该 exporter 也可以作为一个普通的 collector 使用,那么就绝对应该省略它。

应避免使用其他与抓取相关的“元”指标。例如,用于记录抓取次数的计数器,或抓取持续时间的直方图。让 exporter 跟踪这些指标会与 Prometheus 自身自动生成的指标重复。这会增加每个 exporter 实例的存储成本。

机器和进程指标

许多系统(例如 Elasticsearch)会暴露机器指标,如 CPU、内存和文件系统信息。由于 node exporter  在 Prometheus 生态系统中提供了这些指标,因此应舍弃这类指标。

在 Java 世界中,许多检测框架会暴露进程级和 JVM 级的统计信息,如 CPU 和 GC。Java 客户端和 JMX exporter 已经通过 DefaultExports.java  以首选形式包含了这些信息,因此也应舍弃它们。

其他语言和框架也类似。

部署

每个 exporter 都应只监控一个应用程序实例,最好与它并排部署在同一台机器上。这意味着对于运行的每个 HAProxy,您都应运行一个 haproxy_exporter 进程。对于每台运行 Mesos worker 的机器,您都应在其上运行 Mesos exporter ,如果一台机器上同时有 master,则还要为 master 再运行一个。

这背后的理论是,对于直接检测,您本就会这样做,而我们正试图在其他部署结构中尽可能地接近这种模式。这意味着所有的服务发现都在 Prometheus 中完成,而不是在 exporter 中。这样做的好处是 Prometheus 拥有所需的目标信息,允许用户使用 blackbox exporter  来探测您的服务。

有两个例外情况

第一种情况是,在您监控的应用程序旁边运行 exporter 完全没有意义。SNMP、blackbox 和 IPMI exporter 是这方面的主要例子。对于 IPMI 和 SNMP exporter 而言,设备通常是无法运行代码的黑盒(尽管如果您可以在它们上面运行 node exporter 会更好);而对于 blackbox exporter 来说,您监控的是像 DNS 名称这样的东西,也没有地方可以运行 exporter。在这种情况下,Prometheus 仍然应该进行服务发现,并将要抓取的目标传递给 exporter。请参阅 blackbox 和 SNMP exporter 的示例。

请注意,目前只有 Go、Python 和 Java 客户端库可以编写这种类型的 exporter。

第二个例外是,当您从系统的某个随机实例中提取一些统计数据,并且不关心具体与哪个实例通信时。考虑一组 MySQL 副本,您希望对数据运行一些业务查询,然后导出结果。使用您常用的负载均衡方法与其中一个副本通信的 exporter 是最明智的做法。

这不适用于监控具有主节点选举的系统,在这种情况下,您应该单独监控每个实例,并在 Prometheus 中处理“主节点身份”的问题。这是因为主节点并不总是只有一个,并且在 Prometheus 不知情的情况下更改目标会引发异常。

调度

只有在 Prometheus 抓取指标时,才应从应用程序中拉取指标,exporter 不应根据自己的计时器执行抓取。也就是说,所有抓取都应是同步的。

因此,您不应该为您暴露的指标设置时间戳,让 Prometheus 来处理。如果您认为需要时间戳,那么您可能需要的是 Pushgateway

如果某个指标的检索成本特别高,即需要超过一分钟,可以接受对其进行缓存。这应在 HELP 字符串中注明。

Prometheus 的默认抓取超时时间是 10 秒。如果您的 exporter 预计会超过这个时间,您应该在用户文档中明确指出。

推送

一些应用程序和监控系统只推送指标,例如 StatsD、Graphite 和 collectd。

这里有两个考虑因素。

首先,何时让指标过期?Collectd 和与 Graphite 通信的系统都会定期导出数据,当它们停止时,我们希望停止暴露这些指标。Collectd 包含一个过期时间,所以我们使用它;Graphite 则没有,因此需要在 exporter 上设置一个标志。

StatsD 有点不同,因为它处理的是事件而不是指标。最佳模型是在每个应用程序旁边运行一个 exporter,并在应用程序重启时重启它们,以便清除状态。

其次,这类系统通常允许用户发送增量(delta)或原始计数器。您应尽可能依赖原始计数器,因为这是 Prometheus 的通用模型。

对于服务级指标,例如服务级的批处理作业,您应该让 exporter 推送到 Pushgateway,并在事件结束后退出,而不是自己处理状态。对于实例级的批处理指标,目前还没有明确的模式。选项包括滥用 node exporter 的 textfile collector,依赖内存中的状态(如果不需要在重启后持久化,这可能是最好的选择),或者实现与 textfile collector 类似的功能。

抓取失败

当您通信的应用程序没有响应或出现其他问题时,目前有两种处理抓取失败的模式。

第一种是返回一个 5xx 错误。

第二种是设置一个 myexporter_up 变量,例如 haproxy_up,其值为 0 或 1,取决于抓取是否成功。

在即使抓取失败仍能获取一些有用指标的情况下(例如 HAProxy exporter 提供进程统计信息),后一种方法更好。前一种方法对用户来说处理起来稍简单一些,因为 up 指标可以按常规方式工作,尽管您无法区分是 exporter 宕机还是应用程序宕机。

着陆页

如果访问 http://yourexporter/ 时能看到一个简单的 HTML 页面,上面有 exporter 的名称并链接到 /metrics 页面,这对用户来说会更友好。

端口号

用户可能在同一台机器上运行多个 exporter 和 Prometheus 组件,为了简化操作,每个组件都有一个唯一的端口号。

我们在 https://github.com/prometheus/prometheus/wiki/Default-port-allocations  跟踪这些端口号,这个页面是公开可编辑的。

在开发您的 exporter 时,请随时获取下一个可用的端口号,最好在公开发布之前就确定下来。如果您还没准备好发布,可以先用您的用户名和“WIP”(进行中)来占位。

这是一个旨在方便我们用户生活的注册表,而不是开发特定 exporter 的承诺。对于内部应用程序的 exporter,我们建议使用默认端口分配范围之外的端口。

发布

当您准备好向全世界发布您的 exporter 时,请发送邮件到邮件列表,并提交一个 PR,通过编辑此 GitHub 仓库文件 将其添加到可用 exporter 列表中。

本页内容