编写 exporters

如果您正在检测自己的代码,则应遵循关于如何使用 Prometheus 客户端库检测代码的通用规则。当从另一个监控或检测系统获取指标时,情况往往不是那么黑白分明。

本文档包含编写 exporter 或自定义收集器时应考虑的事项。所涵盖的理论对于那些进行直接检测的人员也很有意义。

如果您正在编写 exporter 并且对这里的任何内容不清楚,请通过 IRC (#prometheus on libera) 或邮件列表与我们联系。

可维护性和纯粹性

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

如果所讨论的系统只有少数几个很少更改的指标,那么使一切都完美是一个简单的选择,HAProxy exporter就是一个很好的例子。

另一方面,如果您尝试在系统具有数百个指标且这些指标会随着新版本频繁更改的情况下使事情变得完美,那么您就为自己签署了大量持续的工作。MySQL exporter就属于这种极端情况。

node exporter是这些的混合体,其复杂性因模块而异。例如,mdadm收集器手动解析文件并公开专门为该收集器创建的指标,因此我们不妨将指标弄对。对于meminfo收集器,结果因内核版本而异,因此我们最终只进行了足够的转换来创建有效的指标。

配置

当与应用程序一起工作时,您的目标应该是 exporter 不需要用户进行自定义配置,只需告诉它应用程序在哪里即可。如果某些指标可能过于细粒度并且在大型设置上成本高昂,您可能还需要提供过滤掉某些指标的能力,例如HAProxy exporter允许过滤每个服务器的统计信息。同样,可能存在默认情况下禁用的昂贵指标。

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

在其他情况下,来自系统的指标是完全非标准的,具体取决于系统的使用情况和底层应用程序。在这种情况下,用户必须告诉我们如何转换指标。JMX exporter是这里最糟糕的情况,GraphiteStatsD exporters 也需要配置才能提取标签。

建议确保 exporter 在没有配置的情况下开箱即用,并为转换提供一系列示例配置(如果需要)。

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

指标

命名

遵循关于指标命名的最佳实践

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

对于直接检测,给定的指标应仅存在于一个文件中。因此,在 exporters 和收集器中,指标应仅适用于一个子系统并相应地命名。

指标名称永远不应以程序方式生成,除非编写自定义收集器或 exporter。

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

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

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

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

Prometheus 指标和标签名称以snake_case编写。将camelCase转换为snake_case是可取的,尽管这样做并不总是为myTCPExampleisNaN等内容产生好的结果,因此有时最好保持原样。

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

只有[a-zA-Z0-9:_]在指标名称中有效。

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

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

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

当您有成功的请求计数和失败的请求计数时,公开此信息的最佳方法是将总请求数作为一个指标,将失败的请求数作为另一个指标。这使得计算失败率变得容易。不要使用一个带有失败或成功标签的指标。同样,对于缓存的命中或未命中,最好有一个指标用于总数,另一个用于命中数。

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

标签

阅读关于标签的通用建议

避免将type用作标签名称,它太通用且通常毫无意义。您还应尽可能尝试避免可能与目标标签冲突的名称,例如regionzoneclusteravailability_zoneazdatacenterdcownercustomerstageserviceenvironmentenv。但是,如果应用程序将某些资源称为这些名称,最好不要通过重命名来造成混淆。

避免仅仅因为它们共享一个前缀就将所有内容放入一个指标的诱惑。除非您确定某些内容作为一个指标有意义,否则多个指标更安全。

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

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

经验法则是,一个指标在求和或平均时应该有意义。exporter 还会出现另一种情况,即数据从根本上是表格形式的,否则将需要用户对指标名称进行正则表达式才能使其可用。考虑主板上的电压传感器,虽然对它们进行数学运算没有意义,但将它们放在一个指标中而不是每个传感器一个指标是有意义的。指标中的所有值都应(几乎)始终具有相同的单位,例如,考虑风扇速度是否与电压混合在一起,并且您无法自动分离它们。

不要这样做

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 时需要考虑的一个标签。因此,避免使用可以删除而不会影响时间序列唯一性的检测标签。可以通过 info 指标添加有关指标的附加信息,有关示例,请参见下文如何处理版本号。

但是,在某些情况下,几乎所有指标用户都希望获得附加信息。如果是这样,则添加非唯一标签而不是 info 指标是正确的解决方案。例如,mysqld_exportermysqld_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用作计数器,则会产生误导。

帮助字符串

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

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

例如,SNMP exporter 使用 OID,而 JMX exporter 放入示例 mBean 名称。HAProxy exporter具有手写字符串。node exporter也提供了各种示例。

删除不太有用的统计信息

某些检测系统除了最小值、最大值和标准差之外,还公开 1m、5m、15m 速率、自应用程序启动以来的平均速率(例如,在 Dropwizard 指标中称为mean)。

这些都应该删除,因为它们不是很有用并且会增加混乱。Prometheus 可以自行计算速率,并且通常更准确,因为公开的平均值通常是指数衰减的。您不知道计算最小值或最大值的时间范围,并且标准差在统计上毫无用处,如果您需要计算它,您可以始终公开平方和_sum_count

分位数存在相关问题,您可以选择删除它们或将它们放入摘要中。

点分隔字符串

许多监控系统没有标签,而是执行诸如my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3之类的操作。

GraphiteStatsD exporters 共享一种使用小型配置语言转换这些内容的方法。其他 exporters 应实现相同的方法。转换目前仅在 Go 中实现,并且将受益于被分解为单独的库。

收集器

在为您的 exporter 实现收集器时,您绝不应使用通常的直接检测方法,然后在每次抓取时更新指标。

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

这样做的原因有两个。首先,两次抓取可能同时发生,而直接检测使用实际上是文件级全局变量的内容,因此您会遇到竞争条件。其次,如果标签值消失,它仍将被导出。

通过直接检测来检测您自己的 exporter 是可以的,例如,exporter 在所有抓取中传输的总字节数或执行的调用次数。对于诸如blackbox exporterSNMP exporter之类的 exporters,它们不与单个目标绑定,这些应仅在普通的/metrics调用中公开,而不是在特定目标的抓取中公开。

关于抓取本身的指标

有时您希望导出有关抓取的指标,例如抓取花费了多长时间或您处理了多少条记录。

这些应作为仪表公开,因为它们是关于事件(抓取)的,并且指标名称以 exporter 名称为前缀,例如jmx_scrape_duration_seconds。通常排除_exporter,如果 exporter 也适合仅用作收集器,则绝对排除它。

应避免其他抓取“元”指标。例如,抓取次数的计数器或抓取持续时间的直方图。让 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 的 exporter。

这背后的理论是,对于直接检测,这将是您要做的,并且我们正在努力在其他布局中尽可能接近它。这意味着所有服务发现都在 Prometheus 中完成,而不是在 exporters 中完成。这还具有以下好处:Prometheus 具有用户使用blackbox exporter探测您的服务所需的目标信息。

有两个例外

第一个例外是,在您正在监控的应用程序旁边运行是完全没有意义的。SNMP、blackbox 和 IPMI exporters 是这方面的主要示例。IPMI 和 SNMP exporters,因为设备通常是黑盒,无法在其上运行代码(尽管如果您可以在它们上运行 node exporter 会更好),而 blackbox exporter,您正在监控诸如 DNS 名称之类的内容,在这种情况下,也没有任何内容可以运行。在这种情况下,Prometheus 仍应进行服务发现,并将要抓取的目标传递下去。有关示例,请参见 blackbox 和 SNMP exporters。

请注意,目前仅可以使用 Go、Python 和 Java 客户端库编写此类 exporter。

第二个例外是,您正在从系统的随机实例中提取一些统计信息,并且不关心您正在与哪个实例对话。考虑一组您想要针对数据运行一些业务查询然后导出的 MySQL 副本。使用您的常用负载均衡方法与一个副本对话的 exporter 是最明智的方法。

当您监控具有 master 选举的系统时,这不适用,在这种情况下,您应单独监控每个实例,并在 Prometheus 中处理“主节点性”。这是因为并非始终只有一个 master,并且在 Prometheus 脚下更改目标将导致异常。

调度

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

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

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

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

推送

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

这里有两个注意事项。

首先,您何时使指标过期?Collectd 和与 Graphite 通信的事物都定期导出,当它们停止时,我们希望停止暴露指标。Collectd 包含过期时间,因此我们使用它,Graphite 不包含,因此它是 exporter 上的一个标志。

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

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

对于服务级指标,例如服务级批处理作业,您应让您的 exporter 推送到 Pushgateway 并在事件发生后退出,而不是自行处理状态。对于实例级批处理指标,目前尚无明确的模式。选项是滥用 node exporter 的 textfile 收集器,依赖内存状态(如果您不需要在重新启动后持久化,这可能是最佳选择)或实现类似于 textfile 收集器的功能。

抓取失败

对于抓取失败,目前有两种模式,即您正在与之通信的应用程序未响应或存在其他问题。

第一种是返回 5xx 错误。

第二种是有一个myexporter_up变量(例如haproxy_up),其值根据抓取是否有效而为 0 或 1。

后者更好,在即使抓取失败,您仍然可以获得一些有用的指标的情况下,例如 HAProxy exporter 提供进程统计信息。前者对于用户来说更容易处理,因为up以通常的方式工作,尽管您无法区分 exporter 宕机和应用程序宕机。

着陆页

如果访问http://yourexporter/具有一个简单的 HTML 页面,其中包含 exporter 的名称以及指向/metrics页面的链接,则对用户来说更友好。

端口号

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

https://github.com/prometheus/prometheus/wiki/Default-port-allocations是我们跟踪它们的地方,这是公开可编辑的。

在开发 exporter 时,请随时获取下一个空闲端口号,最好在公开宣布之前。如果您尚未准备好发布,则可以填写您的用户名和 WIP。

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

发布

一旦您准备好向全世界发布您的 exporter,请发送电子邮件到邮件列表并发送 PR 以将其添加到可用 exporters 列表

本文档是开源的。请通过提交问题或拉取请求来帮助改进它。