如果您正在对自己的代码进行仪表化,则应遵循关于如何使用 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
后缀由摘要、直方图和计数器使用。 除非您要生成其中之一,否则请避免使用这些后缀。
_total
是计数器的约定,如果您使用 COUNTER 类型,则应使用它。
保留 process_
和 scrape_
前缀。 如果它们遵循匹配的语义,则可以在这些前缀上添加您自己的前缀。 例如,Prometheus 有 scrape_duration_seconds
用于表示抓取花费的时间,最好也有一个以 Exporter 为中心的指标,例如 jmx_scrape_duration_seconds
,说明特定的 Exporter 花了多长时间来完成其工作。 对于您可以访问 PID 的进程统计信息,Go 和 Python 都提供了可以为您处理此问题的收集器。 一个很好的例子是HAProxy exporter。
当您有成功的请求计数和失败的请求计数时,最好的方式是将它们分别作为总请求数的一个指标和失败请求数的另一个指标来展示。这样可以方便地计算失败率。不要使用带失败或成功标签的单个指标。同样,对于缓存的命中或未命中,最好有一个指标表示总数,另一个指标表示命中数。
考虑一下使用监控的人员可能会对指标名称进行代码或 Web 搜索的可能性。如果名称非常成熟且不太可能在习惯这些名称的人(例如 SNMP 和网络工程师)之外使用,那么保持原样可能是一个好主意。此逻辑不适用于所有导出器,例如 MySQL 导出器的指标可能会被各种人使用,而不仅仅是 DBA。带有原始名称的 HELP
字符串可以提供与使用原始名称相同的许多好处。
请阅读关于标签的通用建议。
避免使用 type
作为标签名称,因为它太通用且通常没有意义。您还应尽可能避免使用可能与目标标签冲突的名称,例如 region
、zone
、cluster
、availability_zone
、az
、datacenter
、dc
、owner
、customer
、stage
、service
、environment
和 env
。但是,如果应用程序将某些资源称为这些名称,最好不要通过重命名来造成混淆。
避免仅仅因为它们共享一个前缀就将所有内容放入一个指标的诱惑。除非您确定某件事作为一个指标有意义,否则使用多个指标更安全。
标签 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_total
的 digest
标签是完整查询模式的哈希值,足以保证唯一性。但是,如果没有人类可读的 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 导出器具有手写字符串。节点导出器也有各种各样的示例。
一些检测系统除了最小值、最大值和标准偏差之外,还会公开 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
调用中公开,而不应在特定目标的抓取中公开。
有时您希望导出关于抓取的指标,例如抓取花费的时间或处理的记录数。
这些内容应作为仪表公开,因为它们是关于事件(抓取)的,指标名称以导出器名称为前缀,例如 jmx_scrape_duration_seconds
。通常会排除 _exporter
,并且如果导出器也可以仅用作收集器,则绝对应排除它。
应避免其他抓取“元”指标。例如,抓取次数的计数器或抓取持续时间的直方图。让导出器跟踪这些指标会复制 Prometheus 本身的自动生成的指标。这会增加每个导出器实例的存储成本。
许多系统(例如 Elasticsearch)会公开机器指标,例如 CPU、内存和文件系统信息。由于 节点导出器在 Prometheus 生态系统中提供了这些指标,因此应删除这些指标。
在 Java 世界中,许多检测框架会公开进程级和 JVM 级统计信息,例如 CPU 和 GC。Java 客户端和 JMX 导出器已通过 DefaultExports.java 以首选形式包含了这些内容,因此也应删除这些内容。
与其他语言和框架类似。
每个导出器都应该监控一个应用程序实例,最好与该实例位于同一台机器上。这意味着对于您运行的每个 HAProxy,您都需要运行一个 haproxy_exporter
进程。对于每个运行 Mesos worker 的机器,您需要在其上运行 Mesos 导出器,如果一台机器既是 worker 又是 master,则 master 也需要运行一个。
其背后的理论是,对于直接的检测,您也会这样做,我们正尝试在其他布局中尽可能接近这种方式。这意味着所有服务发现都在 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,并在事件发生后退出,而不是自己处理状态。对于实例级批处理指标,目前还没有明确的模式。选项包括滥用节点导出器的 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 将其添加到 可用导出器列表中。
本文档是 开源的。请通过提交问题或拉取请求来帮助改进它。