编写 Exporter
如果你正在仪表化自己的代码,应遵循使用 Prometheus 客户端库对代码进行仪表化的通用规则。当从另一个监控或仪表化系统获取指标时,情况往往不那么非黑即白。
本文档包含在编写 Exporter 或自定义收集器时应考虑的事项。所涵盖的理论对于进行直接仪表化的人员也很有用。
如果你正在编写 Exporter 并且对本文中的任何内容不清楚,请通过 IRC (#prometheus on libera) 或邮件列表与我们联系。
可维护性和纯粹性
编写 Exporter 时需要做出的主要决定是,你愿意付出多少努力才能获得完美的指标。
如果相关系统只有少数指标且很少更改,那么追求完美是很容易的选择,例如HAProxy Exporter就是一个很好的例子。
另一方面,如果系统有数百个指标且随着新版本频繁变化,而你却试图追求完美,那么你就给自己带来了大量的持续工作。MySQL Exporter就属于这种情况。
Node Exporter是两者的混合体,复杂性因模块而异。例如,mdadm
收集器手动解析文件并暴露专门为此收集器创建的指标,因此我们不妨把指标弄对。对于 meminfo
收集器,结果因内核版本而异,因此我们最终只进行足够的转换来创建有效的指标。
配置
在使用应用程序时,你的目标应该是编写一个 Exporter,它除了告诉用户应用程序在哪里之外,不需要用户进行任何自定义配置。你可能还需要提供筛选特定指标的能力,如果它们在大型设置中可能过于细致和昂贵,例如HAProxy Exporter允许筛选每个服务器的统计信息。同样,可能存在默认禁用的昂贵指标。
在使用其他监控系统、框架和协议时,你通常需要提供额外的配置或自定义来生成适合 Prometheus 的指标。在最好的情况下,一个监控系统拥有与 Prometheus 足够相似的数据模型,你可以自动确定如何转换指标。对于Cloudwatch、SNMP 和collectd就是这种情况。最多,我们需要让用户能够选择他们想要提取哪些指标。
在其他情况下,系统的指标完全是非标准的,这取决于系统的使用和底层应用程序。在这种情况下,用户必须告诉我们如何转换指标。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` 格式。将 `camelCase` 转换为 `snake_case` 是可取的,尽管自动转换并不总能为 `myTCPExample` 或 `isNaN` 等产生好的结果,因此有时最好保持原样。
暴露的指标不应包含冒号,冒号保留给用户定义的记录规则在聚合时使用。
指标名称中只允许使用 `[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就是一个很好的例子。
当你有一个成功的请求计数和一个失败的请求计数时,最好的暴露方式是:一个指标用于总请求数,另一个指标用于失败请求数。这样可以轻松计算失败率。不要使用一个带有失败或成功标签的指标。同样,对于缓存的命中或未命中,最好有一个用于总数的指标和另一个用于命中数的指标。
考虑使用监控的人通过代码或网页搜索指标名称的可能性。如果名称非常成熟,并且不太可能在熟悉这些名称的人(例如 SNMP 和网络工程师)的领域之外使用,那么保持原样可能是一个好主意。此逻辑不适用于所有 Exporter,例如 MySQL Exporter 指标可能被各种人员使用,而不仅仅是 DBA。带有原始名称的 `HELP` 字符串可以提供与使用原始名称相同的大部分好处。
标签
阅读有关标签的一般建议。
避免使用 `type` 作为标签名称,它太通用且通常没有意义。你也应该尽可能避免与目标标签(如 `region`、`zone`、`cluster`、`availability_zone`、`az`、`datacenter`、`dc`、`owner`、`customer`、`stage`、`service`、`environment` 和 `env`)冲突的名称。然而,如果应用程序将某个资源称为这些名称,则最好不要通过重命名来造成混淆。
避免仅仅因为共享前缀就将所有内容放入一个指标的诱惑。除非你确定某件事作为一个指标是合理的,否则多个指标更安全。
标签 `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 时需要考虑的因素。因此,避免使用在不影响时间序列唯一性的情况下可以移除的仪表化标签。关于指标的额外信息可以通过信息指标添加,例如,请参阅下面如何处理版本号。
然而,在某些情况下,几乎所有指标用户都期望获得额外信息。如果这样,添加一个非唯一标签,而不是一个信息指标,是正确的解决方案。例如,mysqld_exporter的 `mysqld_perf_schema_events_statements_total` 的 `digest` 标签是完整查询模式的哈希值,足以保证唯一性。但是,如果没有人类可读的 `digest_text` 标签,它的用处很小,因为对于长查询,`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` 等格式。
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
调用中暴露,而不是在抓取特定目标时暴露。
关于抓取本身的指标
有时您希望导出关于抓取本身的指标,例如抓取花费了多长时间或处理了多少条记录。
这些指标应该作为 `gauge` 类型暴露,因为它们是关于一个事件(即抓取)的,并且指标名称应以导出器名称作为前缀,例如 jmx_scrape_duration_seconds
。通常会省略 _exporter
,如果该导出器也可以作为纯粹的收集器使用,那么更应该省略它。
应避免其他抓取“元”指标。例如,抓取次数的计数器,或抓取持续时间的直方图。让导出器跟踪这些指标会重复 Prometheus 本身自动生成的指标。这会增加每个导出器实例的存储成本。
机器和进程指标
许多系统,例如 Elasticsearch,会暴露机器指标,例如 CPU、内存和文件系统信息。由于 node exporter 在 Prometheus 生态系统中提供了这些指标,因此这类指标应该被舍弃。
在 Java 世界中,许多仪表化框架会暴露进程级别和 JVM 级别的统计信息,例如 CPU 和 GC。Java 客户端和 JMX 导出器已经通过 DefaultExports.java 以首选形式包含了这些指标,因此这些指标也应该被舍弃。
其他语言和框架的情况也类似。
部署
每个导出器应该只监控一个应用实例,最好是与应用部署在同一台机器上。这意味着对于您运行的每个 HAProxy,您都要运行一个 haproxy_exporter
进程。对于每台带有 Mesos worker 的机器,您都要在其上运行 Mesos exporter,如果一台机器同时有 worker 和 master,则还要为 master 运行另一个。
这背后的理论是,对于直接仪表化,您会这样做,我们正在尝试在其他布局中尽可能地接近这种模式。这意味着所有的服务发现都是在 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 的文本文件收集器,依赖内存状态(如果您不需要在重启后持久化,这可能是最好的选择),或者实现与文本文件收集器类似的功能。
抓取失败
目前有两种处理抓取失败的模式,即当您要通信的应用程序没有响应或有其他问题时。
第一种是返回 5xx 错误。
第二种是有一个 myexporter_up
变量(例如 haproxy_up
),其值根据抓取是否成功而为 0 或 1。
当即使抓取失败仍能获取一些有用指标时(例如 HAProxy 导出器提供进程统计信息),后者更好。前者对用户来说稍显更容易处理,因为 up
正常工作,尽管您无法区分导出器宕机和应用程序宕机。
着陆页
如果访问 http://yourexporter/
能看到一个简单的 HTML 页面,上面有导出器的名称和指向 /metrics
页面的链接,那么用户体验会更好。
端口号
用户可能在同一台机器上运行许多导出器和 Prometheus 组件,为了方便管理,每个组件都有一个唯一的端口号。
https://github.com/prometheus/prometheus/wiki/Default-port-allocations 是我们跟踪这些端口的地方,这是可以公开编辑的。
在开发您的导出器时,请随意使用下一个可用的端口号,最好在公开宣布之前。如果您尚未准备好发布,注明您的用户名和“WIP”也是可以的。
这是一个旨在方便用户使用的注册表,而非开发特定导出器的承诺。对于内部应用程序的导出器,我们建议使用默认端口分配范围之外的端口。
发布
一旦您准备好向世界宣布您的导出器,请发送电子邮件到邮件列表,并发送拉取请求以将其添加到 可用导出器列表中。