编写导出器

如果您正在检测自己的代码,则应遵循使用 Prometheus 客户端库检测代码的一般规则。当从其他监控或检测系统获取指标时,事情往往没有那么黑白分明。

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

如果您正在编写导出器并且对此处任何内容不清楚,请在 IRC(libera 上的 #prometheus)或邮件列表上与我们联系。

可维护性和纯度

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

如果相关系统只有少数几个很少更改的指标,那么使所有内容都完美是一个简单的选择,一个很好的例子是HAProxy 导出器

另一方面,如果您尝试在系统具有数百个指标且这些指标随新版本频繁更改时使所有内容都完美,那么您就给自己带来了很多持续的工作。 MySQL 导出器位于此频谱的末端。

节点导出器是这两者的混合,其复杂程度因模块而异。例如,mdadm 收集器手动解析文件并公开专门为此收集器创建的指标,因此我们不妨将指标正确获取。对于 meminfo 收集器,结果在不同的内核版本中有所不同,因此我们最终只进行足够的转换以创建有效的指标。

配置

在处理应用程序时,您应该以导出器为目标,该导出器无需用户进行任何自定义配置,只需告诉它应用程序在哪里即可。您可能还需要提供过滤掉某些指标的功能,如果这些指标在大型设置中过于细粒度且成本过高,例如HAProxy 导出器允许过滤每个服务器的统计信息。同样,可能存在默认情况下禁用的成本过高的指标。

在处理其他监控系统、框架和协议时,您通常需要提供其他配置或自定义以生成适合 Prometheus 的指标。在最佳情况下,监控系统具有与 Prometheus 足够相似的數據模型,您可以自动确定如何转换指标。对于CloudwatchSNMPcollectd就是这样。最多,我们需要能够让用户选择他们想要提取的指标。

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

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

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 后缀由摘要、直方图和计数器使用。除非您正在生成其中之一,否则请避免使用这些后缀。

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

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

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

考虑使用监控的人员进行代码或网络搜索指标名称的可能性。如果名称非常成熟且不太可能在习惯于这些名称的人员领域之外使用,例如 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_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用作计数器,则会产生误导。

帮助字符串

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

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

例如,SNMP 导出器使用 OID,JMX 导出器输入示例 mBean 名称。HAProxy 导出器 具有手写的字符串。节点导出器 也提供了各种示例。

删除不太有用的统计信息

一些检测系统会暴露 1m、5m、15m 速率,自应用程序启动以来的平均速率(例如,在 Dropwizard 指标中称为mean),以及最小值、最大值和标准差。

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

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

点分隔字符串

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

GraphiteStatsD 导出器共享一种使用小型配置语言转换这些内容的方法。其他导出器应该实现相同的功能。转换目前仅在 Go 中实现,并且可以从分解成单独的库中获益。

收集器

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

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

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

通过直接检测检测您自己的导出器是可以的,例如导出器跨所有抓取传输的总字节数或执行的调用次数。对于blackbox 导出器SNMP 导出器等不绑定到单个目标的导出器,这些应该只在普通的/metrics调用中公开,而不是在特定目标的抓取中公开。

关于抓取本身的指标

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

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

机器和进程指标

许多系统(例如 Elasticsearch)会公开机器指标,例如 CPU、内存和文件系统信息。由于节点导出器在 Prometheus 生态系统中提供了这些信息,因此应删除此类指标。

在 Java 世界中,许多检测框架会公开进程级和 JVM 级统计信息,例如 CPU 和 GC。Java 客户端和 JMX 导出器已经通过DefaultExports.java以首选形式包含了这些信息,因此这些也应该删除。

其他语言和框架也是如此。

部署

每个导出器都应该监控一个实例应用程序,最好是位于同一台机器上的它旁边。这意味着对于您运行的每个 HAProxy,您都运行一个haproxy_exporter进程。对于具有 Mesos 工作节点的每台机器,您都在其上运行Mesos 导出器,如果一台机器同时具有这两者,则为其主节点运行另一个。

这背后的理论是,对于直接检测,这就是您将要做的,我们正试图在其他布局中尽可能接近它。这意味着所有服务发现都在 Prometheus 中完成,而不是在导出器中完成。这也有助于 Prometheus 拥有它需要的信息,以便允许用户使用blackbox 导出器探测您的服务。

有两个例外

第一个是当在您正在监控的应用程序旁边运行完全没有意义时。SNMP、blackbox 和 IPMI 导出器是此类主要示例。IPMI 和 SNMP 导出器,因为设备通常是黑盒,无法在其上运行代码(尽管如果您可以在其上运行节点导出器,那将更好),以及 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,并在事件发生后退出,而不是自己处理状态。对于实例级批处理指标,还没有明确的模式。选项包括滥用节点导出器的文本文件收集器、依赖内存状态(如果您不需要在重启后持久化,这可能是最好的选择)或实现类似于文本文件收集器的功能。

刮取失败

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

第一种是返回 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 以将其添加到可用导出器列表中。

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