如果要对自己的代码进行仪表化,则应遵循如何使用 Prometheus 客户端库对代码进行仪表化的一般规则。从其他监控或仪表系统获取指标时,事情往往不是那么黑白分明。
本文档包含在编写导出器或自定义收集器时应考虑的事项。所涉及的理论也会引起直接使用仪器的用户的兴趣。
在编写导出程序(Exporter)时,您需要做出的主要决定是,为了获得完美的指标,您愿意付出多少努力。
如果相关系统只有极少数很少变化的指标,那么让所有指标都完美无缺是一个简单的选择,HAProxy 输出程序就是一个很好的例子。
另一方面,如果系统有数百个指标,而且这些指标会随着新版本的推出而频繁变化,而你却试图将一切都做到完美,那么你就会面临大量的持续性工作。MySQL 输出程序就属于这种情况。
节点导出程序(node exporter)是上述几种程序的混合体,复杂程度因模块而异。例如,mdadm 收集器会手动解析文件,并公开专为该收集器创建的度量指标,因此我们最好能正确处理度量指标。对于 meminfo 收集器,不同内核版本的结果各不相同,因此我们最终只需进行足够的转换即可创建有效的指标。
在处理应用程序时,除了告诉它应用程序的位置外,用户不需要对导出器进行任何自定义配置。如果某些指标在大型设置中过于精细和昂贵,您可能还需要提供过滤掉这些指标的功能,例如HAProxy 输出程序允许过滤每个服务器的统计数据。同样,有些昂贵的指标可能会被默认禁用。
在使用其他监控系统、框架和协议时,您通常需要提供额外的配置或定制,以生成适合 Prometheus 的指标。在最佳情况下,监控系统的数据模型与 Prometheus 足够相似,您可以自动确定如何转换指标。Cloudwatch、SNMP 和 collectd 就是这种情况。在大多数情况下,我们需要让用户选择他们想要提取的指标。
在其他情况下,根据系统和底层应用程序的使用情况,来自系统的指标是完全非标准的。在这种情况下,用户必须告诉我们如何转换指标。在这种情况下,JMX 输出程序是最糟糕的,Graphite 和 StatsD输出程序也需要配置才能提取标签。
建议确保导出器开箱即用,无需配置,并在需要时提供用于转换的配置示例。
YAML 是标准的 Prometheus 配置格式,所有配置默认都应使用 YAML。
一般来说,指标名称应能让熟悉 Prometheus 但不熟悉特定系统的人猜出指标的含义。名为 http_requests_total 的指标并不是非常有用 —— 这指标是在它们进入时、在某个过滤器中还是在它们到达用户代码时进行测量的?requests_total 指标名称就更糟糕了,到底是什么类型的请求?
在直接仪器中,一个给定的度量指标应正好存在于一个文件中。相应地,在导出器和收集器中,一个指标应适用于一个子系统,并相应命名。
除非是在编写自定义收集器或导出器时,否则绝不能按程序生成指标名称。
应用程序的指标名称一般应以出口程序名称为前缀,如 haproxy_up。
指标单位必须使用基本单位(如秒、字节),并将其转换为图形工具更易读取的单位。无论最终使用什么单位,指标名称中的单位必须与使用的单位一致。同样,要使用比率,而不是百分比。更好的做法是,为比率的两个组成部分分别指定一个计数器。
指标名称不应包括导出时使用的标签,如 by_type,因为如果标签被聚合,就没有意义了。
一个例外情况是,当你通过多个指标导出带有不同标签的相同数据时,在这种情况下,这通常是区分它们的最合理方式。对于直接仪器,只有在导出包含所有标签的单一指标值会导致卡入度过高时才会出现这种情况。
Prometheus 的指标和标签名称以蛇形大小写书写。将 camelCase 转换为 snake_case 是可取的,不过自动转换并不总能为 myTCPExample 或 isNaN 等产生好的结果,因此有时最好保持原样。
公开的指标不应包含冒号,这些冒号是为用户定义的记录规则保留的,以便在汇总时使用。
指标名称中只有 [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 字符串可以提供与使用原始名称相同的大多数好处。
避免使用类型作为标签名称,因为它过于通用,通常毫无意义。还应尽可能避免使用可能与目标标签冲突的名称,如 region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、environment 和 env。不过,如果应用程序就是这样称呼某些资源的,最好不要重新命名,以免造成混淆。
避免因为共用一个前缀就将其归入一个指标。除非你确定某样东西作为一个度量标准是合理的,否则使用多个度量标准更为安全。
标签 le 对于直方图有特殊意义,而对于汇总表则有量化意义。一般应避免使用这些标签。
读/写和发送/接收最好作为单独的度量指标,而不是作为一个标签。这通常是因为您每次只关心其中一个指标,这样使用起来更方便。
经验法则,一个指标在求和或求平均值时应该是合理的。在使用导出器时还有一种情况,那就是数据基本上是表格形式的,如果不这样做,用户就必须对度量名称进行重定义才能使用。考虑到主板上的电压传感器,虽然在它们之间进行数学运算毫无意义,但将它们放在一个度量单位中,而不是每个传感器一个度量单位,还是很有意义的。一个指标中的所有值(几乎)都应使用相同的单位,例如,如果风扇速度与电压混在一起,而你又没有办法自动将它们分开,那么就可以考虑使用相同的单位。
不要这样做:
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_text 标签,它就没有什么用处了,因为对于长查询来说,它只包含查询模式的起始部分,因此不是唯一的。因此,我们最终会同时使用摘要文本标签和摘要标签来保证唯一性。
如果你发现自己想给所有指标贴上同一个标签,请停下来。
通常有两种情况会出现这种情况。
第一种情况是在度量指标上使用某些有用的标签,如软件的版本号。请使用https://www.robustperception.io/how-to-have-labels-for-machine-roles/ 上描述的方法。
第二种情况是标签实际上是目标标签。这些标签包括区域、群集名称等,它们来自基础架构设置而非应用程序本身。应用程序在标签分类中的位置不是由应用程序决定的,而是由运行 Prometheus 服务器的人员配置的,而且监控同一应用程序的不同人员可能会给它起不同的名字。
因此,这些标签属于 Prometheus 通过您使用的任何服务发现进行的抓取配置。在这里应用机器角色的概念也是可以的,因为这很可能是至少对某些抓取人员有用的信息。
您应尽量将指标类型与 Prometheus 类型相匹配。这通常是指计数器和仪表。汇总的 _count 和 _sum 也比较常见,有时您还会看到量化值。直方图比较少见,如果您遇到直方图,请记住说明格式显示的是累积值。
指标类型通常并不明显,尤其是在自动处理一组度量时。一般来说,UNTYPED 是一个安全的默认值。
计数器不能递减,所以如果你的计数器类型来自其他可递减的仪表系统,例如 Dropwizard 指标,那么它就不是计数器,而是仪表。UNTYPED 可能是最适合的类型,因为如果将仪表用作计数器,会产生误导。
在转换度量指标时,用户可以追溯到原始指标是什么,以及导致转换的规则是什么。在帮助字符串中输入收集器或导出器的名称、应用的任何规则的 ID 以及原始度量的名称和详细信息,将对用户大有帮助。
Prometheus 不喜欢一个度量值有不同的帮助字符串。如果要从许多其他度量标准中创建一个度量标准,请选择其中一个放入帮助字符串。
举例来说,SNMP 输出程序使用 OID,而 JMX 输出程序则使用 mBean 名称示例。HAProxy输出程序使用手写字符串。节点输出器也有各种各样的示例。
除了最小值、最大值和标准偏差外,一些仪表系统还会显示 1m、5m、15m 速率,以及自应用程序启动以来的平均速率(例如,在 Dropwizard 指标中称为平均值)。
这些指标都应该舍弃,因为它们用处不大,还会增加杂乱。Prometheus 可以自行计算速率,而且通常更准确,因为暴露的平均值通常是指数衰减的。你不知道最小值或最大值是在什么时间计算出来的,标准偏差在统计上也没有用,如果需要计算,你可以随时公开平方和、_sum 和 _count。
量值也有相关问题,您可以选择放弃它们或将它们放在摘要中。
许多监控系统没有标签,而是使用 my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3 这样的标签。
Graphite 和 StatsD 输出程序共享了一种用小型配置语言转换标签的方法。其他输出程序也应采用同样的方法。这种转换目前仅在 Go 语言中实现,如果能将其分解为一个单独的库,将大有裨益。
在为 Exporter 程序实施收集器时,千万不要使用通常的直接仪器方法,然后在每次抓取时更新度量指标。
而是每次都创建新的度量指标。在 Go 中,这可以通过 Collect() 方法中的 MustNewConstMetric 来实现。Python 请参见 https://github.com/prometheus/client_python#custom-collectors,Java 请在您的 collect 方法中生成 List<MetricFamilySamples>,请参见 StandardExports.java 以了解示例。
这样做有两个原因。首先,两次抓取可能同时发生,而直接检测使用的实际上是文件级全局变量,因此会出现竞争条件。其次,如果标签值消失了,它仍然会被导出。
通过直接仪表对导出器本身进行仪表化是没问题的,例如导出器在所有抓取中传输的总字节数或执行的调用。对于黑盒导出器和 SNMP 导出器等不与单个目标绑定的导出器,这些信息只应在普通 /metrics 调用中公开,而不是在对特定目标的抓取中公开。
有时,您希望导出有关抓取的指标,例如抓取用了多长时间或处理了多少条记录。
这些指标应作为计量器导出,因为它们与事件、抓取和以导出器名称为前缀的指标名称有关,例如 jmx_scrape_duration_seconds。通常情况下,_exporter 是不包括在内的,如果 Exporter 也可以只作为收集器使用,那么一定要将其排除在外。
任何系统(例如 Elasticsearch)都会暴露 CPU、内存和文件系统信息等机器指标。由于 Prometheus 生态系统中的 node exporter 程序提供了这些信息,因此应放弃此类指标。
在 Java 世界中,许多仪表框架都会暴露 CPU 和 GC 等进程级和 JVM 级统计数据。Java 客户端和 JMX 输出程序已通过 DefaultExports.java 以首选形式包含了这些数据,因此也应放弃这些数据。
其他语言和框架也是如此。
每个 Exporter 都应监控一个实例应用程序,最好就在同一台机器上的旁边。也就是说,每运行一个 HAProxy,就运行一个 haproxy_exporter 进程。对于每台有 Mesos Worker 的机器,你都要在上面运行Mesos Exporter 程序,如果一台机器同时有两个 Mesos Worker,还要为主应用程序运行另一个。
这样做的理论依据是,如果要直接使用仪器,就必须这样做,而我们正试图在其他布局中尽可能接近这一点。这意味着所有服务发现都在 Prometheus 中完成,而不是在 Exporter 中。这样做的另一个好处是,Prometheus 可以获得所需的目标信息,从而允许用户使用黑盒 Exporter 探查您的服务。
有两种例外情况:
第一种情况是,在监控应用程序旁边运行完全没有意义。SNMP、黑盒和 IPMI Exporter 就是这方面的主要例子。IPMI 和 SNMP Exporter 的设备通常都是黑盒子,不可能在上面运行代码(不过如果能在上面运行节点输出程序就更好了),而黑盒子 Exporter 要监控的是 DNS 名称之类的东西,也没有什么可运行的。在这种情况下,Prometheus 仍应进行服务发现,并传递要抓取的目标。有关示例,请参阅黑盒和 SNMP 输出程序。
请注意,目前只有 Go、Python 和 Java 客户端库可以编写这种类型的导出器。
第二种例外情况是,您要从一个系统的随机实例中提取一些统计数据,但并不关心与哪个实例对话。考虑到您想针对数据运行一些业务查询,然后导出一组 MySQL 复制。使用通常的负载均衡方法与一个副本对话的输出程序是最明智的方法。
这不适用于监控有主选择的系统,在这种情况下,应单独监控每个实例,并在 Prometheus 中处理 "主属性" 问题。这是因为并不总是只有一个主控,改变 Prometheus 脚下的目标会导致奇怪的现象。
只有在 Prometheus 抓取时,才应从应用程序中提取指标,Exporter 不应根据自己的计时器执行抓取。也就是说,所有抓取都应该是同步的。
因此,不应在暴露的指标上设置时间戳,而应让 Prometheus 来处理。如果您认为需要时间戳,那么您可能需要使用 Pushgateway。
如果一个指标的检索成本特别高,即需要一分钟以上,那么缓存它是可以接受的。这一点应在 HELP 字符串中注明。
Prometheus 的默认 scrape 超时时间为 10 秒。如果您的导出器可能会超过这一超时时间,则应在用户文档中明确说明。
有些应用程序和监控系统只推送指标,例如 StatsD、Graphite 和 collectd。
这里有两个注意事项。
首先,指标何时过期?Collectd 和与 Graphite 通信的设备都会定期导出指标,当它们停止导出时,我们希望停止公开指标。Collectd 包含过期时间,所以我们使用它,而 Graphite 没有,所以它是导出器上的一个标记。
StatsD 有点不同,因为它处理的是事件而不是指标。最好的模式是在每个应用旁运行一个输出器,并在应用重启时重新启动它们,以便清除状态。
其次,这类系统往往允许用户发送增量或原始计数器。您应尽可能依赖原始计数器,因为这是 Prometheus 的一般模式。
对于服务级指标,例如服务级批处理作业,您应该让您的导出器推送到 Pushgateway 并在事件发生后退出,而不是自己处理状态。对于实例级批处理指标,目前还没有明确的模式。可供选择的方案有:滥用节点出口程序的文本文件收集器、依赖内存状态(如果不需要在重启后持久化,可能是最好的选择)或实现与文本文件收集器类似的功能。
在与您对话的应用程序没有响应或有其他问题的情况下,目前有两种模式可处理抓取失败。
第一种是返回 5xx 错误。
第二种是设置一个 myexporter_up(例如 haproxy_up)变量,其值为 0 或 1,取决于抓取是否成功。
后者的好处是,即使抓取失败,仍能获得一些有用的指标,例如 HAProxy 输出程序提供的进程统计信息。对于用户来说,前者更容易处理,因为可以按常规方式运行,但无法区分 Exporter 宕机和应用程序宕机。
如果访问 http://yourexporter/ 的用户看到的是一个简单的 HTML 页面,上面有输出程序的名称,以及指向 /metrics 页面的链接,那就更好了。
用户可能会在同一台机器上安装多个 Exporter 和 Prometheus 组件,因此为了方便起见,每个输出程序和组件都有一个唯一的端口号。
https://github.com/prometheus/prometheus/wiki/Default-port-allocations 是我们跟踪它们的地方,可以公开编辑。
在开发你的 Exporter 时,最好在公开发布之前,随意获取下一个免费端口号。如果您还没准备好发布,写上您的用户名和 WIP 也可以。
这是一个让用户生活更轻松的注册表,而不是开发特定导出程序的承诺。对于内部应用程序的导出程序,我们建议使用默认端口分配范围之外的端口。
一旦您准备好向全世界宣布您的 Exporter ,请向邮件列表发送电子邮件并发送 PR,将其添加到可用Exporter 列表中。