编写 Exporter

如果您正在为自己的代码添加指标,应该遵循使用 Prometheus 客户端库为代码添加指标的一般规则。当从另一个监控或指标系统中获取指标时,情况通常不像这么黑白分明。

本文档包含您在编写 Exporter 或自定义收集器时应考虑的事项。理论部分也对直接添加指标的人员有帮助。

如果您正在编写 Exporter,并且对本文档中的任何内容不清楚,请在 IRC(#prometheus on libera)或邮件列表上联系我们。

可维护性和纯洁性

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

如果被查询的系统只有少量很少改变的指标,那么追求完美是一个简单的选择,HAProxy exporter  是一个很好的例子。

另一方面,如果您试图在系统拥有数百个指标,并且这些指标随着新版本的发布而频繁更改时追求完美,那么您将承担大量持续的工作。MySQL exporter  属于这一类。

The node exporter  is a mix of these, with complexity varying by module. For example, the mdadm collector hand-parses a file and exposes metrics created specifically for that collector, so we may as well get the metrics right. For the meminfo collector the results vary across kernel versions so we end up doing just enough of a transform to create valid metrics.

配置

与应用程序一起工作时,您应该以不需要用户进行自定义配置的 Exporter 为目标,除了告诉它应用程序的位置。您可能还需要提供过滤掉某些指标的功能,因为在大型设置中,这些指标可能过于详细和昂贵,例如HAProxy exporter  允许过滤每个服务器的统计信息。类似地,可能有一些昂贵的指标默认禁用。

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

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

建议确保 Exporter 在开箱即用时无需配置,并提供转换所需的示例配置选择。

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

指标

命名

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

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

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

指标名称永远不应通过程序生成,除非在编写自定义收集器或 Exporter 时。

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

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

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

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

Prometheus 指标和标签名称以 snake_case 编写。转换为 snake_case 是可取的,尽管自动转换并不总是能产生好的结果,例如 myTCPExampleisNaN,所以有时最好保持原样。

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

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

_sum_count_bucket_total 后缀由 Summaries、Histograms 和 Counters 使用。除非您正在生成其中一种,否则请避免使用这些后缀。

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

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

当您有成功的请求计数和失败的请求计数时,最好的公开方式是为总请求数公开一个指标,为失败请求公开另一个指标。这样可以轻松计算失败率。不要使用一个带有 failed 或 success 标签的指标。类似地,对于缓存的命中或未命中,最好有一个总数指标和一个命中数指标。

考虑到人们使用监控时可能会通过代码或网络搜索指标名称的可能性。如果名称非常成熟且不太可能被除熟悉这些名称的人之外的任何人使用,例如 SNMP 和网络工程师,那么将它们保留原样可能是一个好主意。这个逻辑并不适用于所有 Exporter,例如 MySQL Exporter 的指标可能会被各种人使用,而不仅仅是 DBA。带有原始名称的 HELP 字符串可以提供与使用原始名称相同的好处。

标签

阅读关于标签的常规建议

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

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

le 标签对 Histograms 有特殊含义,quantile 对 Summaries 有特殊含义。通常应避免使用这些标签。

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

经验法则是,一个指标在求和或平均时应该是有意义的。还有另一种情况会出现在 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 的聚合。

如果您的监控以这种方式公开了一个总计,请删除该总计。如果您出于某种原因必须保留它,例如总计包含未单独计算的项目,则使用不同的指标名称。

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

但是,有时期望几乎所有指标的用户都会想要额外的信息。如果是这样,添加一个非唯一的标签而不是 info 指标是正确的解决方案。例如,mysqld_exporter mysqld_perf_schema_events_statements_totaldigest 标签是完整查询模式的哈希,足以保证唯一性。但是,如果没有人类可读的 digest_text 标签,它就没有多大用处,而对于长查询,它只包含查询模式的开头,因此不是唯一的。因此,我们同时拥有用于人类的 digest_text 标签和用于唯一性的 digest 标签。

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

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

这种情况通常有两种。

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

第二种情况是当一个标签实际上是一个目标标签。这些标签如 region、cluster 名称等,来自您的基础设施设置,而不是应用程序本身。应用程序不应决定其在您的标签分类法中的位置,而应由运行 Prometheus 服务器的人进行配置,并且不同的人监控同一个应用程序可能会给它不同的名称。

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

类型

您应该尝试将指标的类型与 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

分位数也存在相关问题,您可以选择丢弃它们或将它们放入 Summary 中。

带有点的字符串

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

The Graphite StatsD  Exporter 共享一个方法,使用一个小型配置语言来转换这些。Graphite StatsD  Exporter 共享一种使用小型配置语言转换这些的方法。其他 Exporter 也应该实现相同的方法。该转换目前仅在 Go 中实现,并受益于将其提取到一个单独的库中。

收集器

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

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

原因有两方面。首先,两次抓取可能同时发生,而直接插装使用的是文件级别的全局变量,因此会发生竞态条件。其次,如果标签值消失,它仍会被导出。

通过直接插装来插装您自己的导出器是可以的,例如,传输的总字节数或导出器在所有抓取中执行的调用数。对于 blackbox exporter SNMP exporter  等导出器,它们不绑定到单个目标,这些导出器应仅在普通的 /metrics 调用时暴露,而不是在抓取特定目标时暴露。

关于抓取的指标

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

这些应作为gauges(仪表盘)暴露,因为它们是关于一个事件(抓取)的,并且指标名称前缀为导出器名称,例如 jmx_scrape_duration_seconds。通常会省略 _exporter,如果导出器也可以作为普通收集器使用,则肯定会省略它。

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

机器和进程指标

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

在 Java 世界中,许多插装框架会暴露进程级别和 JVM 级别的统计信息,如 CPU 和 GC。Java 客户端和 JMX 导出器已经通过 DefaultExports.java  以首选形式提供了这些信息,因此也应弃用此类指标。

其他语言和框架也类似。

部署

每个导出器应只监控一个应用程序实例,最好是紧挨着它运行在同一台机器上。这意味着您运行的每个 HAProxy 都会运行一个 haproxy_exporter 进程。每台带有 Mesos 工作节点的机器上,都会运行一个 Mesos exporter ,如果一台机器同时拥有两者,则会再运行一个。

这是因为直接插装时您也会这样做,而我们正试图在其他布局中尽可能地接近这一点。这意味着所有的服务发现都由 Prometheus 完成,而不是由导出器完成。这还有一个好处,即 Prometheus 拥有用户所需的有关目标的信息,以便使用 blackbox exporter  探测您的服务。

有两个例外

第一个是,当与您要监控的应用程序并行运行时完全没有意义。SNMP、blackbox 和 IPMI 导出器是主要的例子。IPMI 和 SNMP 导出器,因为设备通常是黑盒,无法运行代码(尽管如果可以在它们上面运行 node exporter 会更好),而 blackbox 导出器用于监控类似 DNS 名称的内容,同样也无法运行代码。在这种情况下,Prometheus 仍应执行服务发现,并将目标传递给抓取。请参阅 blackbox 和 SNMP 导出器示例。

请注意,目前只有使用 Go、Python 和 Java 客户端库才能编写此类导出器。

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

当您监控具有主节点选举的系统时,这不适用,在这种情况下,您应该单独监控每个实例,并在 Prometheus 中处理“主节点”状态。这是因为不一定总只有一个主节点,而更改 Prometheus 底层目标的行为会引起异常。

调度

指标应仅在 Prometheus 抓取它们时从应用程序拉取,导出器不应基于自己的计时器执行抓取。也就是说,所有抓取都应该是同步的。

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

如果某个指标检索成本很高,即需要花费超过一分钟的时间,那么缓存它是可以接受的。这应在 HELP 字符串中注明。

Prometheus 的默认抓取超时时间为 10 秒。如果您的导出器可能会超过此时间,则应在您的用户文档中明确说明。

推送

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

这里有两个考虑因素。

首先,您何时会使指标过期?Collectd 和与 Graphite 通信的系统都会定期导出,当它们停止时,我们希望停止暴露这些指标。Collectd 包含一个过期时间,所以我们使用它,而 Graphite 没有,所以这是导出器上的一个标志。

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

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

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

失败的抓取

目前对于应用程序无响应或出现其他问题的失败抓取有两种模式。

第一种是返回 5xx 错误。

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

后者更好,因为即使抓取失败,仍然可以获得一些有用的指标,例如 HAProxy 导出器提供进程统计信息。前者对用户来说稍微容易处理一些,因为 up 的工作方式与往常一样,尽管您无法区分是导出器宕机还是应用程序宕机。

登录页

访问 http://yourexporter/ 时显示一个简单的 HTML 页面,其中包含导出器名称和指向 /metrics 页面的链接,对用户来说更友好。

端口号

用户可能在同一台机器上运行许多导出器和 Prometheus 组件,因此为了方便起见,每个组件都有一个唯一的端口号。

https://github.com/prometheus/prometheus/wiki/Default-port-allocations  是我们跟踪这些信息的地点,该页面可公开编辑。

在开发导出器时,请随时使用下一个可用的端口号,最好是在公开宣布之前。如果您还没有准备好发布,可以写上您的用户名和“WIP”(进行中)。

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

宣布

一旦您准备好向世界宣布您的导出器,请发送电子邮件到邮件列表,并发送一个 PR 将其添加到可用导出器列表中,方法是编辑此 GitHub 仓库文件 

本页内容