编写导出器


当直接检测你的代码质量时,通过Prometheus客户库检测你的代码质量是一个常用手段。当从另一个监控或者检测系统获取度量指标时,事情往往不是一成不变的。

当你准备写度量指标的导出器或者自定义收集器时,这篇文章值得你借鉴和学习。涉及的理论对于做一些检测的事情是非常有帮助的。

如果你正在写一个导出器,且有任何不清楚的,可以随时联系我们, 详见IRC和邮件

可维护性和Purity

当你写一个exporter时,你需要做出的最大决定是你会投入多大的精力,来获得比较完美的度量指标测量

一方面,如果系统的度量指标很少有变化,则写导出器是不需要很长的时间和精力(例如:HAProxy exporter); 另一方面,当系统版本每次更新时,若有大量的度量指标发生变化,则你需要投入大量的时间和精力写一个导出器,mysql导出器就是一个例子

这个node exporter是混合态的,不同的模块的处理方式也不尽相同。对于mdadm,我们必须手动解析文件,并获取我们想要的度量指标,所以我们可以在需要的时候获取度量指标。对于meminfo,不同的内核版本命名可能会有变化,所以我们需要创建有效的度量指标,以适应不同内核版本的内存使用情况

配置

当导出器和应用程序一起工作时,你的导出器目标是尽量不要让用户使用自定义配置,而是尽可能地使用默认配置就ok了。你也可以给你的导出器提供过滤度量指标的支持,因为有些度量指标是非常多且琐碎,也有些需要花费很大的代价才能获取度量指标,这些都可以进行过滤(例如:HAProxy exporter允许过滤每个服务器的统计信息)。类似地,默认配置可以直接禁用获取代价比较的度量指标。

当导出器和监控系统协同工作时,框架和协议相关工作可能会很复杂。

使用监控系统时,框架和协议不是那么简单的。

最好的情况是其他监控或者检测系统与Prometheus有非常相似的数据模型,则系统可以自动导出度量指标到Prometheus监控系统中,如:Cloudwatch, SNMP和Collectd。但是大多数情况下,我们都需要用户自己指定其他监控或者检测系统需要导出的度量指标,并把这些度量指标样本数据导入到Prometheus系统中。

但是更多常见的情况是,来自其他监控或者检测系统的度量指标往往都是非标准的,它们往往依赖于用户使用它的方法和底层应用程序是什么样的。这种情况下,用户必须告诉我们怎么转换这些度量指标。这个JMX导出器是最糟糕的,graphite和statsd导出器也要求在配置中抽取标签集。

提供一些导出器的使用方式是值得建议的,例如:生成这个导出器的输出和实例配置的选择。当为这些导出器写一些配置文档时,这篇文档应该时刻牢记在心。

YAML是Prometheus的标准配置格式

度量指标

命名

遵循度量指标最佳实践

通常度量指标名称应该允许对Prometheus监控系统比较熟悉的人, 对一个度量名称的字面含义比较容易理解它的真正含义。一个命名为http_requests_total的度量指标名称不是很有用,因为它是针对所有进入系统的请求计数。如果改为requests_total,这度量指标名称更差劲,它不能表示是什么样的请求,是tcp,udp还是http?

为了把度量指标推送到检测系统中,一个给定的度量指标应该在一个指定的文件中存在。根据导出器和收集器,一个度量指标名称应该应用在一个子系统中。

度量指标名称永远不要程序化的生成,除非在编写自定义收集器或者导出器时。

应用程序的度量指标名称应该以exporter名称为前缀,如haproxy_up

度量指标名称必须使用基本单位(例如:秒,字节),并将其转换为图形软件更易读的内容。无论你最终使用什么样的计量单位,指标名称中的单位都必须与正在使用的单位相匹配。类似地展示比率,而不是百分比(尽管两个组件的计数器比率更好)。

度量指标名称不应该包括他们导出的labels(如:by_type)如果标签聚在一起,就没有意义。

这里有一个特例,当你通过多个度量指标用不同标签导出相同数据时,通常是区分他们最好的方式。一个例子是,当对单个标签用所有标签导出数据时,这个计算是非常复杂的,这种情况下使用这个特例是非常合适的。

Prometheus度量指标和标签名称写在snake_case中。把camelCase转换成snake_case度量指标是可取的,尽管自动化地做这些事情并不总是产生好的结果,例如:myTCPExampleisNaN, 所以有时最好将他们保留。

暴露的度量指标不应包含冒号,这些用于聚合时可供用户使用。度量指标名称只有符合正则表达式[a-zA-Z0-9:]是有效的,任何其他字符串都需要被改成”“下划线

_sum, _count, _bucket, 和_total用于Summaries,Histogram和Counters的后缀。如果你正好要使用这些统计则可以使用这些后缀,否则避免使用这些后缀。

如果你正在使用COUNTER类型,则应该使用_total作为度量指标的后缀。

这个process_scrape_的前缀被保留。如果它们遵循匹配的语义,可以将这些前缀添加到度量指标上。例如:Prometheus的持续获取时间为scrape_duration_seconds,这是很好的做法。jmx_scrape_duration_seconds表示JMX收集器花费多久时间获取度量指标数据。对于对于进程信息统计,你可以通过PID进程号获取,Go和Python都会为你提供处理此选项的收集器(请参阅HAProxy exporter示例)

当你统计请求的成功数量和失败数量时,最佳方法是暴露两个度量指标,一个是总的请求数,另一个度量指标是失败的请求度量指标。这使得计算失败比率变得很容易。不要使用带有failed/success的标签。类似于缓存中的hit/miss,有一个总的度量指标和另一个hits的度量指标是更好的。

考虑使用监控的人可能会对指标名称执行代码或者网络搜索。如果这些名称是非常完善的,并且不太可能在用于这些名称的人的领域之外(例如:SNMP和网络工程师)使用,那么按原样离开它们可能是一个好主意。该逻辑不适用于(例如:作为非数据库管理员的MySQL),可以预期会围绕这些指标。具有原始名称的帮助文档可以提供与使用以前的名称大致相同的好处。

labels

阅读一个有关labels标签的建议,详见advice

避免把type做为标签名称,它太通用化,而且无意义。你也应该试着尽可能地避免与目标标签冲突,例如:region, zone, cluster, availability, az, datacenter, dc, owner, customer, stage, environmentenv, 尽管这是应用程序调用的东西,但是最好不要通过重命名造成混乱。

不要仅仅因为它们共享了同一个前缀,就把一些样本数据全部集成到一个度量指标下。除非你确定某些指标是有意义的,否则请使用多个度量指标。

标签le对于Histograms有特殊的含义。同样对于Summaries来说,quantile也是如此。避免使用这些标签。

对于Read/write和send/receive, 不要把它们当做一个标签使用,而是应该作为独立的度量指标使用。因为你关心的是某个时刻它们中的一个,度量指标命名并使用它们是容易的。

实践经验是,当求和或者求平均时,一个度量指标应该是有意义的。 还有另一种情况导出器的出现,数据基本是用表格的方式呈现,否则将要求用户对度量标准名称进行正则表达式可用。 考虑您主板上的电压传感器,而对它们进行数学计算是无意义的,将它们置于一个度量标准中而不是每个传感器有一个度量是有意义的。 度量中的所有值(几乎)总是具有相同的单位(如果风扇速度与电压混合,则无法自动分离)。

不要做这些:

  1. my_metric{label=a} 1
  2. my_metric{label=b} 6
  3. **my_metric{label=total} 7**

或者

  1. my_metric{label=a} 1
  2. my_metric{label=b} 6
  3. **my_metric{} 7**

前者打破了用户Prometheus聚合操作sum()函数作用, 后者也一样。某些客户端库(如:Go)会积极地尝试阻止你在自定义收集器中执行后者。依赖Prometheus的sum聚合操作,可以轻易地达到这一点。

如果你的监控使用了一个total,请删除它。如果你必须因为某些原因(例如:这个total并不是统计计数)保留它,请使用其他的度量指标名称。

Target labels, not static scraped labels(目标标签,非静态获取标签)

如果你发现自己想要对所有度量指标使用相同的标签,请立即停止。

通常有两种情况出现。

第一个是有些标签关联到度量指标上市非常有用的,例如:软件的版本号。请使用文档地址中所述的方法。

另一种情况是真正的实例标签。这些标签是区域,集群名称等,它们来自目标实例的硬件信息而不是应用程序本身。这类标签不应该在分类中使用, 这个Prometheus服务加载服务配置,对于相同的应用可以给出不同的度量指标名称,但是可以有相同的标签名称

因此,这些标签通过你使用的任何服务发现,都属于Prometheus的获取配置。还可以在这里应用机器角色的概念,因为至少有一些人获取它,可能是非常有用的信息。

Types类型

你应该尝试将你的度量指标类型与Prometheus类型相匹配。这通常意味着主要类型是Counter和Gauge。summaries的_count_sum也是比较常见的,有时你会看到quantiles。Histogram是罕见的,如果你碰到了,记住那展示格式用来暴露累计值。

通常,衡量度量指标的类型是不明显的(特别是如果你自动处理一组度量指标),那么在这种情况下使用UNTYPED无类型度量指标。一般来说,UNTYPED类型是一个比较安全的默认值。

Counter递增,如果你在其他监控或者检测系统中发现了一个counter类型,但是它能够减少( 例如:Dropwizard指标),这不是一个Counter类型而是一个Gauge类型, UNTYPED无类型可能是那里使用最好的类型,因为如果它被用作counter,则GAUGE将会被误导。

帮助文档

当你转换度量指标时,用户能够跟踪原始内容记忆导致该转换的规则是有用的。以收件人/导出者的身份,应用程序的任何规则ID和原始度量的名称/详细信息记录到帮助文档,会极大地帮助用户。

Prometheus不喜欢一个度量指标名称有不同帮助文档。如果你正在使用来自多个其他系统的一个度量指标,请选择其中一个来放置帮助文档。

例如:SNMP导出器使用OID,JMX导出器放入一个样例mBean名称中。HAProxy exporter有手写字符串。node exporter有大量的例子可以使用。

放弃无用的统计数据

某些检测系统提供了一些度量指标,包含1m/5m/15m速率、程序运行到现在的平均速率(例如:在dropwizard指标中统计为平均值)、最小值、最大值和标准偏差。

这些检测系统的统计度量指标数据都应该被丢弃,因为Prometheus提供了同样的一套统计数据,数据类型为Summary,而且统计的样本数据更加准确,所以不需要再在写导出器时统计这类数据。

Quantiles涉及的相关issue,你可以选择丢弃或者将其放在Summary中。

.字符串(Dotted strings)

许多监控系统没有标签,而是做成像my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3这样。

graphite和statsd的导出器采用了一种使用配置文件来达到带有标签的目的。其他exporters也应该这样做。它目前仅在Go中实现,并将受益于将其分解为单独的库。

Collectors

当在导出器中实现收集器的功能模块时,你永远不要”使用通用且直接的检测方案,然后在每次抓取后更新度量指标样本数据”。

我们宁愿在抓取度量指标数据时,创建新的度量指标。在Go中,在Update()方法中调用MustNewConstMetric完成此操作。对于Python,请参与https://github.com/prometheus/client_python#custom-collectors, 对于Java,在Collector方法中生成列表\, 请参与StardardExports.java作为示例。

我们在抓取数据时采用创建度量指标的方案,主要有两个原因: 1. 如果在同一时刻发生两次抓取,这样在度量指标数据时会发生资源竞争问题;2. 如果一个度量指标的标签值消失后,更新度量指标数据,那么空标签数据还是会被导出。

通过直接的检测来判断你写的导出器是否是ok的。例如:总字节数在所有的数据抓取传输或者调用时由导出器执行,例如:blackbox exporters和snmp exporter,他们关联了多个目标实例,所以这些只能在一个vanilla/metrics调用上,而不是在特定目标上。

关于获取度量指标本身

有时候你想要导出关于一次抓取本身相关的度量指标数据时,例如:这次抓取花费了多长时间,处理了多少记录。

这些数据的数据类型应该为gauges,数据指标名称以导出器名称为前缀,例如:jmx_scrape_duration_seconds。通常_exporter被排除(如果exporter仅仅作为collector使用,绝对排除它)。

Machine & Process metrics(硬件,进程度量指标)

许多系统(如:elasticsearch)提供了一些硬件度量指标,如:cpu,内存和文件系统信息。Prometheus生态中的node导出器已经提供了这些度量指标的检测方案,所以不需要在写导出器时实现这些硬件度量指标的检测或者获取方案。

在Java世界中,许多检测框架提供了进程级别和JVM级别的统计测量方案,例如:CPU占用率,GC次数等。Java客户端和JMX导出器已经通过形如DefaultExports.java的机制,可以获取这些度量指标,所以也不再需要在写导出器时再实现一套这样的方案。

与其他语言类似。

Deployment部署

每个导出器在实例所在的服务器上部署并监控,这意味着如果你想要haproxy,则需要在实例所在的服务器上运行haproxy_exporter导出器进程。对于每个有mesos从节点的服务器上,你需要在这个服务器上运行一个mesos导出器进程(如果在这台服务器上还有mesos主节点,则还需要再启动一个mesos导出器进程服务)

这背后的理论是,对于在所有实例上的导出器进程,我们需要获取其上的度量指标样本数据的话,这意味着在Prometheus监控系统上需要提供服务发现机制,而不是由导出器提供。Prometheus监控系统有目标实例的相关信息的优点,主要在于它允许用户用blackbox导出器探测你的服务。

有两个例外:

第一个是被监控的实例所在的服务器位置是完全不关心的。例如:SNMP、blackbox和IPMI。IPMI和SNMP作为设备是有效地黑盒,它是不可能运行代码的(如果你能够在其上运行node导出器代替,那会更好),blackbox作为你监控像DNS名称这样的,完全不需要运行代码)。但是Prometheus仍然应该需要做服务发现机制,通过传输被抓取的目标实例。

请注意,目前只有使用python和Java客户端库编写这种类型的exporter(在Go中编写的blackbox exporter手工执行文本格式,请不要这样做)。

第二个是,你从一个系统拉取一个随机服务实例的一些统计时,这个服务器的位置是你完全不关心的。考虑一种情况,你想要通过Mysql从节点运行一些商业查询,然后导出数据。这时候使用一个导出器和Mysql从节点通信是最佳方案。

当你监控一个带有master选取的系统时,也不能用Prometheus服务发现机制。这种情况下,我们应该监控每个实例服务,并通过Prometheus和目标系统的主节点通信。这里并不总是准确的,改变目标实例也可能会在Prometheus监控系统下造成数据错乱。

调度

当Prometheus服务抓取度量指标数据时,度量指标才能够从实例中被抓取。导出器不应该设定自己的时间去抓取目标数据,而是由Prometheus监控系统统一管理和下放。即所有的抓取是同步的。如果你需要时间戳,你可以通过pushgateway代替

如果一个度量指标的数据获取需要花费大量时间和代价,那么暂时先缓存这些数据也是可以接受的。它应该在HELP中说明

在Prometheus中默认的抓取度量指标数据的超时设置为10s。如果你的导出器超过了这个时间长度,你需要在你的导出器用户文档中明确指出。

推送

一些应用程序和监控系统仅仅推送几个度量指标,如:statsd, grphite和collected.

有两点需要考虑:第一点, 你的度量指标什么时间过期?Collected和Graphite导出器定时导出度量指标数据,但是当他们停止工作后,我们也想要停止提供这些度量指标。Collected包含我们经常使用的过期时间,而Graphite则不会,在导出器中它是一个标志。

Statsd有很大不同,它处理的是事件,而不是度量指标。最好的数据模型是在服务所在的目标实例上运行一个Statsd导出器,当应用程序重启后,这个导出器也重启,并清空度量指标数据。

第二点,这些应用程序和监控系统允许用户发送delta或者原生计数器。你应该尽可能地依赖原生计数器,这是Prometheus的通用模型。

对于服务级别的度量指标(例如:服务级别的批量任务),你应该用导出器把这些度量指标数据推送到Push gateway中,当推送完成后退出。对于实例级别的度量指标,目前还没有明确的模式。选项不是滥用node导出器的textfile收集器,而是依赖当前内存状态。

抓取失败

当前有两种模式,当导出器同实例服务通信没有响应或者其他问题时,称之为抓取失败。

第一种,返回响应吗:5xx错误。

第二种,有一个度量指标名称为myexporter_up(例如:haproxy_up), 判断一个导出器是否工作时通过这个度量指标变量的值0/1状态。0:表示不工作;1:表示工作。

第二种模式是比较好的,当抓取失败时,我们能够通过一些有用的度量指标数据判断导出器服务的当前工作状态,例如:haproxy导出器提供了进程统计信息,第一种对于用户来说,更加容易处理,但是up是非常通用的处理方式(因为你不能辨别出事导出器服务有问题,还是应用程序有问题)

登录页面

如果用户访问http://yourexporter/时返回一个带有导出器名称的简单页面, 并且通过/metrics链接到Prometheus系统中国,这对用户是非常友好的。

端口

在一台服务器上用户可以有多个导出器和Prometheus组件,因此有一个唯一的端口变得非常容易

https://github.com/prometheus/prometheus/wiki/Default-port-allocations , 这个是我们官方的端口规划。

宣布

一旦你对外准备宣布你写的导出器时,请发送一封邮件,并且发送一个PR到这个可用导出器的列表