4、PromQL进阶

Prometheus入门 / 2022-05-22

本文系朱政科《Prometheus云原生监控:运维与开发实战》学习笔记

数学不好学PromQL简直是灾难。。。

上一章节用了较长的篇幅介绍了Prometheus实战的核心:PromQL,本章节将会深入介绍内置函数、HTTP API、记录规则、告警规则、metric_relabel_configsrelabel_configs6方面的PromQL的最佳实践与性能优化方法。

一、Prometheus内置函数

Prometheus内置有很多函数,以此对时序数据多样化处理。在官网的最新文档上,截止至本文撰稿日期:2022-05-12,共有以下表格内置函数。

官方对函数并没有做分类,仅仅是按照字母顺序排序,以下表格为朱政科老师归纳:

函数类型 数量 函数名称
动态标签 2 label_replace、label_join
数学运算 11 abs、exp、ln、log2、log10、sqrt、ceil、floor、round、clamp、clamp_max、clamp_min
类型转换 2 vector、scalar
时间和日期 9 time、minute、hour、month、year、day_of_month、day_of_week、day_in_month、timestamp
多对多逻辑运算 1 absent
排序 2 sort、sort_desc
计时器 4 rate、increase、irate、resets
仪表盘 6 changes、deriv、predict_linear、delta、idelta、holt_winters
直方图 1 histogram_quantile
时间聚合 8 avg_over_time、min_over_time、max_over_time、sum_over_time、count_over_time、quantile_over_time、stddev_over_time、stdvar_over_time、last_over_time、present_over_time、absent_over_time

sgn,返回一个向量,将所有样本值都转换为符号,样本值为正,则为1;为负,则为-1;等于0,则为0.

还有三角函数

1.1、动态标签函数

在理想的情况下,系统中不同资源实例使用的标签名称和标签值应该是一致的。但实际由于生产环境要接入的资源类型千奇百怪,必然有不一致的情况,所以有时候无法有效控制被监控的资源实例及其性能数据。label_replacelabel_join提供了对时间序列标签的自定义能力,通过对生产环境中的指标进行标准化管理,能够更好地与客户端或者可视化工具进行配合。

1.1.1、标签分类

针对监控系统,一般有拓补标签(topological label)和模式标签(schematic label)两种分类方式对已有标签操作,以此更好地管理时序数据。

拓补标签

通过物理或者逻辑组来区分服务组件,比如Prometheusjob(作业,可理解为资源类型)、instance(实例,可理解为具体的个体/类型下面的个体)。

  • job,根据采集配置中的作业名称配置的,通常用于描述正在监控的资源的类型。
  • instance,多用于识别具体目标,通常是资源的IP地址和端口。
模式标签

多用于将拓补中同一级别的时序匹配在一起,比如URL、HTTP状态码等信息。

1.1.2、label_replace

为了能够让客户端的图表可读性更强,label_replace函数通过正则表达式为时间序列添加额外的标签。

label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string)

该函数并不会删除指标名称,而是依次对v中的每一条时序进行处理,通过regex匹配src_label的值。如果匹配,则将匹配部分的(replacement)替代内容写入dst_label标签中,其中匹配内容的$1用匹配到的第一部分替换;$2用匹配到的第二部分进行替换;如果正则表达式不匹配,则不改变时间序列。

基于上面的理论,来个例子加深理解:

先在表达式浏览器输入:up,获取当前运行exporter的所有实例及其状态。

up{instance=“localhost:9090”, job=“prometheus”} 1
up{instance=“localhost:9100”, job=“prometheus”} 1

表达式浏览器输入:

up执行后得到时间序列;host为自定义标签名称;$1为匹配到的第一部分;

instance为源标签;(local.*):(.*)为正则表达式

注意,使用了冒号后代表这个正则将源标签的值切成两部分,第一部分匹配给$1

label_replace(up, "host", "$1", "instance", "(local.*):(.*)")

返回结果:

正则将源标签instance的值localhost:9090切成两部分,第1部分为localhost$1,那么自定义标签则为localhost

up{host=“localhost”, instance=“localhost:9090”, job=“prometheus”} 1
up{host=“localhost”, instance=“localhost:9100”, job=“prometheus”} 1

以上例子表明label_replace函数是在时间序列的头部插入自定义标签,标签值为正则表达式匹配到的内容。

1.1.3、label_join

label_join也不会删除源指标名称,它是将时间序列汇总的Vsrc_label源标签的值,以separator(分隔符)作为连接符号写入一个新的标签dst_label中。label_join参数如下:

// src_label可以有任意个
label_join(v instant-vector, dst_label string, separator string, src_label1 string, src_label2 string, ...)

来个例子:

先看看原始数据

prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 581

执行表达式:

label_join(prometheus_http_requests_total, "combinded", "-", "instance", "job")

输出很多结果,其中上面的那条变成这样:

combinded”作为自定义的标签名称,值是横杠“-”作为连接符号将“instance”和“job”两个标签连起来的结果。

prometheus_http_requests_total{code=“200”, combinded=“localhost:9090-prometheus”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 789

1.2、数学运算函数

数学运算针对瞬时向量执行,源指标名称会在数学运算后的返回值中被剔除。常见的有:abs、exp、ln、log2、log10、sqrt、ceil、floor、round、clamp_max、clamp_min等。

1.2.1、abs

给定一个值,abs将返回所有样本值跟给定的值的绝对距离,也就是所有样本值跟给定值差多少。

先来看看源数据:进程打开的文件描述符数量

process_open_fds{instance=“localhost:9090”, job=“prometheus”} 39
process_open_fds{instance=“localhost:9100”, job=“prometheus”} 9

执行abs,查询各进程与给定值20的差距有多少

abs(process_open_fds - 20)

结果如下所示:

{instance=“localhost:9090”, job=“prometheus”} 19
{instance=“localhost:9100”, job=“prometheus”} 11
1.2.2、exp

exp会将瞬时向量的值作为自然数e(约等于2.718)的次方;当次方过大时,返回值是+lnf;次方是NaN时,返回值也是NaN

exp(count(node_cpu_core_throttles_total))

结果如下所示:

我电脑CPU6核心,因此也就是2.718^6=403

{} 403.4287934927351
1.2.3、ln

lnexp正好相反,给定一个瞬时向量,将返回其样本值的自然对数。

ln(vector(7.38))

结果如下所示:

ln算的其实就是自然数e的多少次方等于它里面的数,比如下面的例子,代表e的平方(1.998约等于2)等于7.38

{} 1.998773638612381
1.2.4、log2

给定一个瞬时向量,返回各个样本值的以2为底的对数,参考ln

log2(vector(8))

结果如下:

2^3=8

{} 3

1.2.5、log10

类似log2,返回各个样本值的以10为底的对数。

log10(vector(100))

结果:

10^2=100

{} 2
1.2.6、sqrt

给定一个瞬时向量,返回各个样本值的平方根

sqrt(vector(100))

结果:

{} 10
1.2.7、ceil

给定瞬时向量,返回各个样本值向上四舍五入到最接近的整数。

ceil(vector(12.33))

结果:

{} 13
1.2.8、floor

floorceil类似,只是返回的是向下四舍五入的整数。

floor(vector(12.33))

结果:

{} 12
1.2.9、round

round函数用于返回向量中样本值最接近某个整数的所有样本。它有两个参数,除了必须的瞬时向量外,还有个可选的标量to_nearest,默认值为1,表示样本返回的是最接近1的整数倍的值。也可以将该参数指定为任意值,表示样本返回的是最接近它的整数倍的值,如果给定的值恰好在两个整数之间,则四舍五入。

不指定参数
round(vector(12.33))  // 返回:{} 12
round(vector(12.53))  // 返回:{} 13
指定参数
round(vector(9999), 100)  // 返回:{} 10000
round(vector(848), 100)  // 返回:{} 800
1.2.10、clamp_max

clamp_max的函数的值输入一个瞬时向量和一个标量上限值,其中标量上限值是瞬时向量的最大值,即瞬时向量不能超过给定的上限,如果没超则保持原样,超过则强制改为上限值。它是为了避免在实际环境中度量指标返回的样本值远远超出正常范围的情况发生。

clamp_max(vector(848), 100)  // 返回结果:{} 100
clamp_max(vector(84), 100)  // 返回结果:{} 84
1.2.11、clamp_min

clamp_max相反,clamp_min是下限,其它同上。

clamp_min(vector(84), 100)  // 返回结果:{} 100
clamp_min(vector(184), 100)  // 返回结果:{} 184

1.3、类型转换函数

PromQL提供了两个分别支持向量vector和标量scalar的类型转换函数。

1.3.1、vector

给定一个标量scalar,将返回无标签的向量。上面1.2小节已经使用过多次,这里就不再展示示例。

1.3.2、scalar

给定一个瞬时向量,返回其唯一的时间序列的值作为一个标量。如果给定的向量的样本值的数量大于1或等于0,则返回NaN;换句话说,指定的时间序列只能有一个样本值。

注意:执行scalar后的返回值会剔除所有标签。

scalar函数除了在处理标量、常量时非常有用,还可以使一些表达式变得更简单。比如,查询今年启动了哪些服务器:

process_start_time_seconds为查询当前启动的所有服务器

year(process_start_time_seconds) == scalar(year())  // 方法一
year(process_start_time_seconds) == on() group_left year()  // 方法2

1.4、时间和日期函数

PromQL提供了9个处理时间和日期的函数,但其实Prometheus只有UTC格林威治时间,并不区分时区。

1.4.1、time

time函数返回指定标量中的样本值的时间戳(从计算机元年:1970-01-01到现在的秒数)

time()

返回:

scalar 1652410671.12
time实践

推荐使用time来记录监控事件的发生时间而不是事件的持续时间,如下所示:

查询机器运行时长

time() - process_start_time_seconds

返回结果:

{instance=“localhost:9090”, job=“prometheus”} 93890.34899997711
{instance=“localhost:9100”, job=“prometheus”} 93890.32899999619
1.4.2、minute

返回当前时间(UTC格林威治时间)是小时内第几分钟,返回值为:0-59

minute()

返回结果:

执行时的时间为:2022-05-13 11:12

{} 12
1.4.3、hour

类似minute,返回执行时UTC时间一天中的第几个小时,返回值为:0-23

执行时的时间为:2022-05-13 11:17

hour()  // 返回结果:{} 3
1.4.4、month

类似上面,返回执行时UTC时间中的月份,返回值为:1-12

执行时间为:2022-05-13

month()  // 返回结果:{} 5
1.4.5、year

类似上面,返回执行时UTC时间中的年份。

执行时间为:2022-05-13

year()  // 返回结果:{} 2022
1.4.6、day_of_week

类似上面,返回执行时UTC时间中的星期几,返回值为:0-6,其中0为星期日。

执行时间为:2022-05-13

day_of_week()  // 返回结果:{} 5
1.4.7、day_of_month

类似上面,返回执行时UTC时间的一个月中的哪一天,返回值为:1-31

执行时间为:2022-05-13

day_of_month()  // 返回结果:{} 13
1.4.8、days_in_month

类似上面,返回执行时UTC时间当月的总天数,返回值为:28-31

执行时间为:2022-05-13

days_in_month()  // 执行结果:{} 31
1.4.9、timestamp

返回指定向量的样本数据中每个样本的时间戳(依旧是1970-01-01到当前的秒数),Prometheus 2.0以后才有的函数。

查询当前所有启动的实例最后一次采集的时间戳

timestamp(up)

返回结果:

{instance=“localhost:9090”, job=“prometheus”} 1652412716.373
{instance=“localhost:9100”, job=“prometheus”} 1652412709.942

1.5、多对多逻辑运算函数

上章节有介绍了Prometheus中的向量匹配元素主要分为一对一、一对多、多对一、多对多这4种模式。还介绍了3种逻辑运算符:andorunless都是以多对多的方式工作,它们是可以进行多对多工作的运算符。

Prometheus内置函数absent扮演了not的角色,如果传递给absent函数的向量参数具有样本数据,则返回空向量;如传递的向量参数没有样本数据,则返回不带度量指标名称但带有标签的时间序列,其值为1

1.5.1、absent

absent函数常用于检测服务是否正常,如果想提醒某个实例中缺少特定指标,可以用unless

absent(up{instance="localhost:9100"})

返回结果:

如果正常,即向量的样本数据有值,则提示空查询。

Empty query result

我换一个不存在的实例:

absent(up{instance="haha"})

返回:

{instance=“haha”} 1

1.6、排序函数

PromQL多数情况下不会为查询到的瞬时向量进行排序,需要手动使用sortsort_desc连个排序函数。

很明显,sort是升序,而sort_desc是降序,二者相同点是NaN总是排在最后面。

sort(prometheus_http_requests_total)

返回:

prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 1
prometheus_http_requests_total{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/favicon.ico”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 4
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 25
prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 108

改成sort_Desc

sort_desc(prometheus_http_requests_total)

返回:

prometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 114
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 26
prometheus_http_requests_total{code=“200”, handlprometheus_http_requests_total{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”}
114
prometheus_http_requests_total{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”}
26
prometheus_http_requests_total{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”}
5
prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”}
3
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”}
3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”}
3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”}
2
prometheus_http_requests_total{code=“200”, handler=“/favicon.ico”, instance=“localhost:9090”, job=“prometheus”}
2
prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”",}
1er
=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”}
5
prometheus_http_requests_total{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 3
prometheus_http_requests_total{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“200”, handler=“/favicon.ico”, instance=“localhost:9090”, job=“prometheus”} 2
prometheus_http_requests_total{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 1

",

1.7、计数器函数

PromQL关于计数器(counter)的函数主要有四种:rateincreaseirateresets。它们都仅接受区间向量(range vector)作为参数,计算后返回瞬时向量。如果提供的区间向量的范围内只有一个样本,那么将不会有任何计算与输出。

1.7.1、rate

rate函数可以直接计算区间向量在一定时间范围内的平均增长速率,它会在目标变化(比如被采样的实例重启等操作)后自动中断。

此前已多次演示,此处不再进行操作。但建议使用rate函数时,传入的时间范围至少是采集间隔的4倍或以上。比如采集间隔为1/m,那么rate的时间范围应该设置为4m或以上。因为当采集出现延迟或别的意外时,可确保至少有两个样本,或者说有足够多的样本数据作为参考。

1.7.2、irate

irate仅适用于绘制快速剧烈变化的计数器,长期趋势分析或告警推荐使用rate函数。

irate函数是PromQL针对长尾效应专门提供的灵敏度更高的函数,其更多反映的是瞬时增长速率,因为它是通过对区间向量中的最后两个样本数据来计算区间向量的增长速率。同样会在被采集目标重启等操作时自动中断计算。

irate函数速率短时间内剧烈变化会重置for语句,形成的图形有很多波峰。上下起伏过大,且指标完全稳定的情况较少。尤其是当被检测对象处于异常状态时,irate会产生过于敏感的告警,并带有很多误报。而rate函数利用的是多个样本数据的平均值,所以少有波峰,更适合长期趋势分析。

1.7.3、increase

increase是计算区间向量的增长量,但它是获取区间向量中首个和末尾的样本数据进行计算,同样会在被采集目标异常(比如重启关机等)时自动中断计算。

increase是在rate函数之上的语法糖,它的主要作用是增加图标和数据的可读性。其使用rate函数记录规则的使用率,以便持续跟踪样本值的变化趋势。

以下为:每个时间序列在过去5分钟内HTTP请求的增长速率

increase(prometheus_http_requests_total[5m])

返回:

{code=“200”, handler=“/-/ready”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“200”, handler=“/api/v1/label/:name/values”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“200”, handler=“/api/v1/metadata”, instance=“localhost:9090”, job=“prometheus”} 1.0526204987315921
{code=“200”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 2.1052409974631843
{code=“200”, handler=“/api/v1/query_range”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“200”, handler=“/api/v1/series”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“200”, handler=“/favicon.ico”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“200”, handler=“/graph”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“200”, handler=“/metrics”, instance=“localhost:9090”, job=“prometheus”} 19.99978947590025
{code=“302”, handler=“/”, instance=“localhost:9090”, job=“prometheus”} 0
{code=“400”, handler=“/api/v1/query”, instance=“localhost:9090”, job=“prometheus”} 0
1.7.4、resets

resets函数接受一个区间向量的参数,对每条时间序列返回一个计数器重置的次数,两个连续样本数据之间的值有减少则认为是一次计数器重置,返回值就是进程重启的次数。

resets函数可以用于怀疑计数器的重置频率超过了正常频率了的场景,比如以下例子:查询过去1h内进程的CPU时间重置了多少次

我将node_exporter重启了一下,所以node_exporter计数器重置了。

resets(process_cpu_seconds_total[1h])

返回:

{instance=“localhost:9090”, job=“prometheus”} 0
{instance=“localhost:9100”, job=“prometheus”} 1

1.8、仪表盘函数

仪表盘函数(gauge)主要有6个:changesderivpredict_lineardeltaideltaholt_winters

它们和上文的计数器函数有点类似,都是接受一个区间向量作为参数计算后返回一个瞬时向量,但是仪表盘函数的值更具有参考意义。

1.8.1、predict_linear

predict_linear函数可以预测时间序列在指定时间(单位:秒)后的值。它是基于简单的线性回归,对指定时间范围内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测。

根据1个小时内的磁盘大小的样本数据预测一天后磁盘会不会满

predict_linear(node_filesystem_size_bytes{device="/dev/nvme0n1p6", fstype="ext4", instance="localhost:9100", job="prometheus", mountpoint="/"}[1h], 3600*24) <= bool 0

返回:

预测结果是磁盘不可能会在一天后占满

{device=“/dev/nvme0n1p6”, fstype=“ext4”, instance=“localhost:9100”, job=“prometheus”, mountpoint=“/”} 0
1.8.2、deriv

接受一个区间向量参数,返回时间序列的导数,仅能用于仪表盘函数。

计算1h内的样本计算常驻内存每秒变化的速度

deriv(process_resident_memory_bytes[1h]) / 1024

返回:

{instance=“localhost:9090”, job=“prometheus”} -7.477623494639646
{instance=“localhost:9100”, job=“prometheus”} 3.911283422348437
1.8.3、delta

可以计算区间向量中首个元素与末尾元素之间的差值,由于该值被外推到指定的整个时间范围,因此即使样本值是整数,推算出来的值可能会是非整数。delta类似与increase函数,但是没有计数器重置。

查看当前CPU温度与1h前的CPU温度的差异

delta(node_hwmon_temp_celsius{instance="localhost:9100"}[1h])

实际上,应尽量避免使用这个函数,因为它很可能被一些异常(峰值和低谷)数值过度影响结果,更推荐使用deriv函数对前后时间的情况进行对比。

1.8.4、idelta

idelta函数是计算最新的两个样本值之间的差值,主要用于高级定制场景,它和记录规则允许用户按需求定制而不会对PromQL产生影响。

仅适用于仪表盘类型时序数据

idelta(process_resident_memory_bytes[1h])

返回:

{instance=“localhost:9090”, job=“prometheus”} 1298432
{instance=“localhost:9100”, job=“prometheus”} 790528
1.8.5、holt_winters

该函数实现了著名的霍尔特-温特指数平滑算法,有兴趣可自行了解,我看不懂。

基于给定的区间向量计算时间序列的平滑值。其中,平滑因子sf越低,对旧数据的重视程度越高;趋势因子tf越高,对数据的趋势的考虑就越多,sf>0tf≤1

holt_winters(v range-vector, sf scalar, tf scalar)

holt_winters函数是一种时间序列分析和预报方法,它适用于含有线性趋势和周期波动的非平稳序列,利用指数平滑法(EMA)让模型参数可以不断适应非平稳序列的变化,并对未来趋势进行短期预报。

holt_wintersholt模型的基础上引入了winters周期项(又称季节项),可以用来处理月度数据(周期为12)、季度数据(周期为4)、星期数据(周期为7)等时间序列中固定周期的波动行为。引入多个winters还可以处理多种周期并存的情况。z

指数平滑法的基本思想:随着时间变化,权重以指数方式下降,将权重按照指数级进行衰减,最终年代久远的数据权重接近于0。指数平滑法有几种不通的形式:

  1. 一次指数平滑法,针对没有趋势和季节性的序列。
  2. 二次指数平滑法,针对没有趋势但有季节性的序列。
  3. 三次指数平滑法针对有趋势也有季节性的序列

以下根据0.30.6两个趋势系数,获取最近1h内存使用的平滑指数趋势:

holt_winters(process_resident_memory_bytes[1h], 0.3, 0.6)
1.8.6、changes

计算区间向量中每个样本数据值变化的次数,如样本数据值没有发生变化,则返回1changes看似与resets函数很像,但resets直接就是返回计数器重置的次数,也就是重启的次数,且resets只能用于递增的计数器类型;而changes适用于可随时变化的仪表盘类型,代表变化次数,但有变化不一定就是重启。

来个例子吧!process_start_time_seconds指标代表进程启动的时间戳,进程不重启的情况下该指标时间戳是不会变的,当它发生变化时就一定是进程异常过,此时的changes可以充当检测进程等发生故障而导致崩溃或重启的次数。

changes(process_start_time_seconds[1d])

返回:

0代表有变化

{instance=“localhost:9090”, job=“prometheus”} 1
{instance=“localhost:9100”, job=“prometheus”} 2

因此,changes常用于检测服务是否处于不断重启的恶性循环中。

1.9、直方图函数

详情见上章节6.3小节

1.10、时间聚合函数

上章节7.6小节中,我们有讲到avg函数,但它仅能用于瞬时向量;针对区间向量,有类似的函数:<aggregation>_over_time(aggregation即聚合),它们共有8个。

1.10.1、avg_over_time

计算区间向量内每个度量指标的平均值

// 计算进程CPU在1h内的平均使用量
avg_over_time(process_cpu_seconds_total[1h])
1.10.2、min_over_time

计算区间向量中每个度量指标的最小值

min_over_time(process_cpu_seconds_total[1h])
1.10.3、max_over_time

计算区间向量中每个度量指标的最大值

max_over_time(process_cpu_seconds_total[1h])
1.10.4、sum_over_time

计算区间向量中每个度量指标的总和

sum_over_time(process_cpu_seconds_total[1h])

返回:

{instance=“localhost:9090”, job=“prometheus”} 2677.14
{instance=“localhost:9100”, job=“prometheus”} 10370.53
1.10.5、count_over_time

计算区间向量汇总每个度量指标的样本数据数量

count_over_time(process_cpu_seconds_total[1h])

返回:

{instance=“localhost:9090”, job=“prometheus”} 240
{instance=“localhost:9100”, job=“prometheus”} 238
1.10.6、quantile_over_time

计算区间向量内每个度量指标的样本数据的值的分位数,可参考上章节7.13小节中关于瞬时向量的中位数计算。

quantile_over_time(0.5, process_cpu_seconds_total[1h])

返回:

{instance=“localhost:9090”, job=“prometheus”} 11.370000000000001
{instance=“localhost:9100”, job=“prometheus”} 15.07
1.10.7、stddev_over_time

计算区间向量中每个度量指标的总体标准差,可参考上章节7.8小节瞬时向量用法。

1.10.8、stdvar_over_time

计算区间向量内每一个度量指标的总体标准方差

二、HTTP API

2.1、简述

Prometheus官网提供了一个关于HTTP API的单独小节。Prometheus服务端允许用户通过/api/v1来访问PrometheusHTTP API

比如,当我们在浏览器输入表达式:up,在浏览器开发者工具可以看到实际请求URL为:

http://127.0.0.1:9090/api/v1/query?query=up&time=1652919961.823

根据这个URL,我们可以直接在命令行发起请求,得到的结果是一样的:

curl "http://127.0.0.1:9090/api/v1/query?query=up&time=1652919961.823"

返回:

{"status":"success","data":{"resultType":"vector","result":[
  {"metric":{"__name__":"up","instance":"localhost:9090","job":"prometheus"},
    "value":[1652919961.823,"1"]},
  {"metric":{"__name__":"up","instance":"localhost:9100","job":"prometheus"},
    "value":[1652919961.823,"1"]}]}}

2.2、API响应格式

HTTP API使用的格式是通用的JSON,且调用成功后会返回2开头的HTTP状态码,否则返回4或5开头的错误码。

API调用成功后,Prometheus会返回JSON格式的响应内容,并且在data键值对中返回查询结果。其中,resultType代表返回结果的数据类型,有:

  • matrix,区间向量
  • vector,瞬时向量
  • scalar,标量
  • string,字符串
{
  "status": "success",
  "data": {
    "resultType": "matrix",  // "matrix"|"vector"|"scalar"|"string"
    "result": [
      {
        "metric": {
          "__name__": "up",
          "instance": "localhost:9090",
          "job": "prometheus"
        },
        "values": [[1652858118.819, "1"]]
      },
    ]}}

2.3、表达式查询

HTTP API的表达式查询分为queryquery_range两种,分别通过/api/v1/query/api/v1/query_range查询当前或一定时间范围内的计算结果。

2.3.1、query(瞬时查询)

使用 queryAPI可以查询PromQL在特定时间点的计算结果,参数如下所示:

http://127.0.0.1:9090/api/v1/query?query=time%28%29&time=1652863195.087
  • query=PromQL表达式,%28%29代表括号
  • time=|:指定时间,符合rfc格式或时间戳都行。
  • timeout=:超时时间,可选参数。
2.3.2、query_range(区间查询)

为保证请求不对服务器造成过大压力,query_range步长大于11000时,会被Prometheus直接拒绝并返回异常。

query_range可以直接用于查询PromQL表达式在一段时间内的计算结果,参数如下:

  • query=PromQL表达式
  • start=|:起始时间
  • end=|:结束时间
  • step=:查询时间步长,1个采集周期为1步长,比如1/m即1步长。
  • timeout=:超时时间,可选参数
# 注意,使用区间查询必须要在URL里面将query参数改成query_range才行,否则报错。
# 注意,为不错过数据,要保证查询的时间范围比步长大,比如1/m,步长为5,那么时间范围必须大于5m,否则可能出现漏掉数据的情况。
http://127.0.0.1:9090/api/v1/query_range?query=prometheus_http_requests_total&start=1652884456.817&end=1652884756.817&step=15
2.3.3、元数据管理

元数据:即数据本身的属性,比如本文为Markdown文件,元数据即指文件的类型、大小、位置、创建时间、访问时间、修改时间等属性。

HTTP API的元数据管理主要由三部分组成,分别是:通过标签选择器查找时间序列、获取标签名称、查询标签值。

标签找序列

下面表达式返回与特定标签集匹配的时间序列的列表

GET /api/v1/series
POST /api/v1/series

其请求参数如下:

  • match[]=:表示标签选择器是时间序列选择器,必须提供至少1个参数
  • start=|:起始时间
  • end=|:结束时间

如为POST方法,需在请求中添加标头:--data-urlencode

curl "http://localhost:9090/api/v1/series?match[]=up"

返回

{"status":"success","data":[{"__name__":"up","instance":"localhost:9090","job":"prometheus"},{"__name__":"up","instance":"localhost:9100","job":"prometheus"}]}

返回结果的data部分,是k/v键值对组成的对象列表。

获取标签名称

下面表达式返回标签名称列表

GET /api/v1/labels
POST /api/v1/labels

参数与上面一致

curl "http://localhost:9090/api/v1/labels"

返回结果的data部分是字符串形式的标签名称的列表,比如up{instance="localhost:9090", job="prometheus"}里面的jobinstance

{"status":"success","data":["__name__","address","alertmanager","alertname","alertstate","bios_date","bios_release","bios_vendor","bios_version","board_asset_tag","board_name","board_serial","board_vendor","board_version","branch","broadcast","call","capacity_level","cause","chassis_asset_tag","chassis_serial","chassis_vendor","chassis_version","chip","chip_name","clocksource","code","collector","config","core","cpu","device","dialer_name","domainname","duplex","endpoint","event","firmware_revision","fstype","goversion","handler","id","index","instance","interval","ip","job","label","le","listener_name","machine","major","manufacturer","minor","mode","model","model_name","mountpoint","name","nodename","operation","operstate","package","path","power_supply","pretty_name","product_family","product_name","product_serial","product_sku","product_uuid","product_version","quantile","queue","reason","release","revision","role","rule_group","scope","scrape_job","sensor","serial","serveriry","slice","state","status","sysname","system_vendor","technology","time_zone","type","usb_type","version","version_codename","version_id","zone"]}
查询标签值

下面表达式返回指定的标签名称的标签值列表

 GET /api/v1/label/<label_name>/values

参数依旧同上,返回结果的data部分是字符串形式的标签值的列表

curl "http://localhost:9090/api/v1/label/instance/values"

返回:

{"status":"success","data":["localhost:9090","localhost:9100"]}

2.4、其它拓展

HTTP API还有一些其它用法,接下来通过案例来演示一番。

2.4.1、查询参数(state)

下面表达式返回Prometheus目标发现的当前状态

GET /api/v1/targets

其参数有:

  • state=||

默认情况下,活跃中目标和已删除目标都是响应内容的一部分。返回值汇总的labels表示重新贴标签后的标签集,discoveredLabels表示重新标记发生之前在服务发现期间检索到的未修改标签。

对于已过滤掉的目标,仍会返回空列表,其它值将被忽略。

curl "http://localhost:9090/api/v1/targets?state=active"
{
  "status": "success",
  "data": {
    "activeTargets": [
      {
        "discoveredLabels": {
          "__address__": "localhost:9090",
          "__metrics_path__": "/metrics",
          "__scheme__": "http",
          "__scrape_interval__": "15s",
          "__scrape_timeout__": "10s",
          "job": "prometheus"
        },
        "labels": {
          "instance": "localhost:9090",
          "job": "prometheus"
        },
        "scrapePool": "prometheus",
        "scrapeUrl": "http://localhost:9090/metrics",
        "globalUrl": "http://sanxi-PC:9090/metrics",
        "lastError": "",
        "lastScrape": "2022-05-19T09:43:32.914412891+08:00",
        "lastScrapeDuration": 0.003400195,
        "health": "up",
        "scrapeInterval": "15s",
        "scrapeTimeout": "10s"
      },
      {
        "discoveredLabels": {
          "__address__": "localhost:9100",
          "__metrics_path__": "/metrics",
          "__scheme__": "http",
          "__scrape_interval__": "15s",
          "__scrape_timeout__": "10s",
          "job": "prometheus"
        },
        "labels": {
          "instance": "localhost:9100",
          "job": "prometheus"
        },
        "scrapePool": "prometheus",
        "scrapeUrl": "http://localhost:9100/metrics",
        "globalUrl": "http://sanxi-PC:9100/metrics",
        "lastError": "",
        "lastScrape": "2022-05-19T09:43:41.485017372+08:00",
        "lastScrapeDuration": 0.028664864,
        "health": "up",
        "scrapeInterval": "15s",
        "scrapeTimeout": "10s"
      }
    ],
    "droppedTargets": []
  }
}
2.4.2、rules

/rules API返回告警和记录当前加载的规则列表,还返回由每个实例触发的活跃中告警的各个告警规则。

GET /api/v1/rules

其参数如下所示,如不指定参数,则默认不过滤返回所有内容。

  • type=|
    • alerts,返回所有告警规则的列表
    • record,返回所有记录规则的列表
curl "http://127.0.0.1:9090/api/v1/rules?type=alerts"

返回:

{
  "status": "success",
  "data": {
    "groups": [
      {
        "name": "test-alert-rule",
        "file": "/usr/local/prometheus/alert_rules.yml",
        "rules": [
          {
            "state": "inactive",
            "name": "磁盘满了",
            "query": "predict_linear(node_filesystem_free_bytes{mountpoint=\"/\"}[1h], 3600 * 24) == 0",
            "duration": 60,
            "labels": {
              "serveriry": "critical"
            },
            "annotations": {},
            "alerts": [],
            "health": "ok",
            "evaluationTime": 0.000226793,
            "lastEvaluation": "2022-05-19T14:44:23.780372523+08:00",
            "type": "alerting"
          }
        ],
        "interval": 15,
        "limit": 0,
        "evaluationTime": 0.000231685,
        "lastEvaluation": "2022-05-19T14:44:23.780369205+08:00"
      }
    ]
  }
}
2.4.3、alerts

返回所有已触发的告警规则列表,不需要参数

curl "http://127.0.0.1:9090/api/v1/alerts"
{"status":"success","data":{"alerts":[]}}
2,4,4、alertmanagers

返回所有alertmanager的当前状态概览,活跃中和以删除的都会返回

curl "http://127.0.0.1:9090/api/v1/alertmanagers"
{"status":"success","data":{"activeAlertmanagers":[{"url":"http://localhost:9093/api/v2/alerts"}],"droppedAlertmanagers":[]}}
2.4.5、查询目标元数据

返回当前从目标(target)中采集到的指标的元数据(属性)

GET /api/v1/targets/metadata

请求参数如下:

  • match_target=
  • metric=:检索元数据的指标名称,为空则检索所有度量指标的标准元数据。
  • limit=:要匹配的最大目标数

返回的结果中data部分包含度量指标的元数据和对象的标签列表。比如target列表等信息。一般target下会有instancejob信息,match_target可以指定target

因为查询结果过于庞大,需要使用POST并用content-type重新编码

curl -G http://localhost:9090/api/v1/targets/metadata --data-urlencode 'metric=prometheus_http_requests_total' --data-urlencode 'match_target={instance="localhost:9090"}'

返回:

{
  "status": "success",
  "data": [
    {
      "target": {
        "instance": "localhost:9090",
        "job": "prometheus"
      },
      "type": "counter",
      "help": "Counter of HTTP requests.",
      "unit": ""
    }
  ]
}                                                                                                                                         
2.4.6、查询指标元数据

返回当前从目标中已删除的指标的元数据,参数比较少:

  • metric=:指标名称,用于过滤其元数据,为空则检索所有指标。
  • limit=:限制返回的指标数量,可选。
curl "http://localhost:9090/api/v1/metadata?metric=prometheus_http_requests_total"

返回:

{
  "status": "success",
  "data": {
    "prometheus_http_requests_total": [
      {
        "type": "counter",
        "help": "Counter of HTTP requests.",
        "unit": ""
      }
    ]
  }
}
2.4.7、status

返回Prometheus当前的各种配置,所有用法如下所示:

# 当前加载的配置文件,但不包括其注释信息
GET /api/v1/status/config
# Prometheus配置的标签及其值,2.2+新功能
GET /api/v1/status/flags
# Prometheus服务器运行时的各种属性的信息
GET /api/v1/status/runtimeinfo
# Prometheus服务器的编译信息,不同版本的编译信息可能有所不同。
GET /api/v1/status/buildinfo
# Prometheus时序数据库的各种统计信息,2.14+新功能
# seriesCountByMetricName提供指标名称及其值的计算列表
# labelValueCountByLabelName提供标签名称及其值的计数列表
# memoryInBytesByLabelName提供标签名以及以字节为单位的内存使用列表
# seriesCountByLabelValuePair提供标签的值及其系列技术的列表
GET /api/v1/status/tsdb
2.4.7、TSDB管理员API

此功能对运维及开发很有帮助,作为高级功能,需要单独配置(启动时添加启动参数:--web.enable-admin-api),它给用户开放了时序数据库的权限 。

2.4.7.1、快照

快照功能将所有当前数据创建快照并以snapshots/<datatime>-<rand>为名保存在数据目录之下,且将该目录作为响应返回,也可以选择跳过仅存在与head块中并且尚未压缩到磁盘的快照数据。2.1+新功能。

POST /api/v1/admin/tsdb/snapshot
PUT /api/v1/admin/tsdb/snapshot

参数:

  • skip_head=,是否跳过起始块中存在的数据,可选。
curl -X POST "http://localhost:9090/api/v1/admin/tsdb/snapshot"

返回:

{"status":"success","data":{"name":"20220519T094301Z-163fc33399a4dcb3"}}

我们去确认一下是否真的如此:

sanxi@sanxi-PC:~$ ls /usr/local/prometheus/data/snapshots/
20220519T094301Z-163fc33399a4dcb3

如果用户需要自定义快照或者说Prometheus存储数据的目录,需要在启动时添加--storage.tsdb.path=/PATH

2.4.7.2、删除序列

2.1+新增功能

删除一个时间范围内的指定时序数据,但不会立即删除,而是在压缩数据时进行清理,或者手动发起clean_tombstones请求来立即清理。

POST /api/v1/admin/tsdb/delete_series
PUT /api/v1/admin/tsdb/delete_series

成功则返回204

URL参数:

  • match[]=:可匹配多个标签,用于选择要删除的时序,必须提供至少一个参数。
  • start=|:开始时间,可选,默认最新的时间。
  • end=|:结束时间,可选,默认为最长的可能时间。

如不指定时间,将清除时序数据库中匹配的时间序列的所有数据。

curl -X POST -g http://127.0.0.1:9090/api/v1/admin/tsdb/delete_series?match[]=up&start="2022-05-19T22:00:00+08:00"&end="2022-05-19T22:05:00+08:00"
2.4.7.3、clean tombstone

表示删除delete_series标记为已删除的数据,并清理逻辑删除,删除时间序列后可以使用它来释放磁盘空间,即强制并立即删除数据。

POST /api/v1/admin/tsdb/clean_tombstone
PUT /api/v1/admin/tsdb/clean_tombstone

不需要参数,2.1+新增功能

curl -X POST "http://127.0.0.1:9090/api/v1/admin/tsdb/clean_tombstones"

三、定期执行规则

Prometheus中可以定期执行的两种规则为,两种规则是相辅相成的。

  • 记录规则(Recording Rules
  • 告警规则(Alerting Rules

3.1、记录规则

YAML规则在2.0+后已更新,因此旧的规则文件可能会失效,可以用promtool来升级旧规则文件,它可以用来在不启动prometheus服务器的情况下快速检查规则文件在语句上是否符合新规则。如有效则打印规则信息,返回0并退出;语法错误则报错,返回1并退出。

记录规则可以使用户预先计算经常需要或计算量较大的表达式,并将表达式保存为一组新的时间序列,由此实现对复杂查询的PromQL语句的性能优化并提高查询效率。查询预计算好的结果通常比直接执行表达式快,这对仪表盘很有帮助,因为仪表盘每次刷新时都是重复同样的表达式。

3.1.1、配置记录规则

记录规则存储在Prometheus服务器上,它的自动计算频率由prometheus.yml配置文件中的global配置块中的evaluation_interval参数配置(默认15s),也可以使用interval字句在规则组中覆盖全局配置(prometheus.yml中的rule_files字段指定)。

一般来说,可以在prometheus.yml文件的同级目录下创建名为rules的文件夹,用于保存记录规则。接着在rules文件夹中创建规则文件,命名规则:xxx_rules.yml,再将prometheus.yml中的rule_files字段改为规则文件xxx_rules.yml,如下所示:

# prometheus自定义的规则主要有recording rule和alerting rule两类
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"
  - "alert_rules.yml"  # 此前添加的yml配置文件
  - "rules/first_rules.yml"

这些规则文件可以通过Prometheus服务器发送信号signup),实时重载记录规则,如语法与格式均正确,则生效。

3.1.2、添加记录规则

调用记录规则的查询其实就是用record

创建好xxx_rules_.yml并在prometheus.yml启动后,接下来需要自定义记录规则了。

记录规则在规则组(groups)中定义,其名称必须唯一,如下所示:

groups:
  - name: test_record  # 规则组名必须唯一
    interval: 10s  # 多久计算一次规则
    rules:  # 本组记录规则
      - record: instance_mode:node_cpu:rate5m  # 时序名称,相当于别名。
        expr: sum without(cpu)(rate(node_cpu_seconds_total{instance=~".*:9100"}[5m]))  # PromQL表达式
        labels:
          [<labelname>: <labelvalue>,]

上述案例中的interval将会覆盖prometheus.yml中的global块中的evaluation_interval,规则组内的规则会按照interval规定的时间间隔顺序执行。

规则组内按规则顺序执行,也意味着在后续规则中可以使用之前创建的规则,也就是可以根据规则创建指标,并在后面的规则中复用这些指标。也就是将记录规则设置为参数,比如创建一个含有阈值的规则,该阈值就可以重复使用。此特性仅仅适用于规则组内部,不适用于多个规则组之间。

再对上面的例子解释一下:

  • record:对规则进行命名,推荐格式为:level:metric:operations,可快速定位指标。
    • level:代表规则输出的聚合级别和规则输出的标签
    • metric:度量指标
    • operations:应用于指标的操作列表,最新操作有最高优先级。比如进程CPU查询可以命名为:instance:process_cpu_seconds_total:avg_rate5m
  • expr:用于保存生成新时间序列的查询
  • labels:用来向新时间序列添加或覆盖新标签
3.1.3、实践

评估Prometheus的整体响应时间,可以用此指标:

prometheus_engine_query_duration_seconds

一般可以通过减少关联查询、减小查询范围区间、使用记录规则等方式进行慢查询的优化。

记录规则对复杂查询的PromQL表达式的性能优化、提高查询效率有着非常重要的作用。比如在成千上万的容器环境中,想要每5分钟统计一次海量的kubernetes节点之间CPU和内存的实际使用率,Prometheus就很难快速查询。这时就可以使用记录规则,具体语法表达如下所示:

groups:
  - name: k8s_rules
    rules:
      - record: namespace:container_cpu_usage_seconds_total:sum_rate
        expr: sum(rate(container_cpu_usage_seconds_total{image!="",container!=""}[5m])) by (namespace)
      - record: namespace:container_memory_usage_bytes:sum
        expr: sum(container_memory_usage_bytes{image!="",container!=""}) by (namespace)

这两个规则可以连续执行CPU和内存的实际使用率的查询,并以很短的时间将结果存储起来。优化后的CPU和内存的实际使用率查询方式如下:

// CPU
sum(namespace:container_cpu_usage_seconds_total:sum_rate) / avg_over_time(sum(kube_node_status_allocatable_cpu_cores)[5m:5m])

// 内存
sum(namespace:container_memory_usage_bytes:sum) / avg_over_time(sum(kube_node_status_allocatable_memory_bytes)[5m:5m])

注意:使用记录规则时,一定要遵循记录规则要一步到位,直接算出需要的值,避免算出一个中间结果再去做聚合。

对于聚合很复杂的告警,可以先写一条规则,再针对该规则产生的新指标来建告警,这种方式可以帮助我们更高效地建立分级告警机制(不同阈值对应不同的紧急程度)

3.1.4、重载规则

Prometheus发送信号需要在服务端启动时添加参数:--web.enable-lifecycle,添加好参数并启动Prometheus后,可以发起HTTP请求来重载配置。

以下为添加了以上启动参数的Prometheusservice文件

[Unit]
# 描述文字
Description=Prometheus Server 2.33.3
# 文档链接
Documentation=https://github.com/prometheus
# 依赖启动项,即需要在网络启动后再启动prometheus。
After=network.target

[Service]
Type=simple
User=sanxi
NotifyAccess=all
KillMode=process
WorkingDirectory=/usr/local/prometheus
ExecStop=/bin/kill -TERM $MAINPID
ExecReload=/bin/kill -HUP $MAINPID
EnviromentFile=--web.enable-admin-api
ExecStart=/usr/local/prometheus/prometheus --config.file="/usr/local/prometheus/prometheus.yml" --web.enable-admin-api --web.enable-lifecycle

[Install]
WantedBy=multi-user.target

  • shutdown:停止Prometheus
  • reload:重载配置
curl -X POST "http://localhost:9090/-/reload"
3.1.5、检查规则groups:

Prometheusprometheus主程序同级目录下有一个promtool小工具,可以用来检查配置文件语法是否有误。

# 检查主配置文件
/usr/local/prometheus/promtool check config prometheus.yml
# 检查规则文件
/usr/local/prometheus/promtool check rules rules/first_rules.yml 

3.2、告警规则

介绍完记录规则,再来看看告警规则。在记录规则创建的rules目录下,可以新增xxx_alerts,yml文件作为告警规则。

告警规则可以基于PromQL表达式定义告警条件,并将有关触发告警的通知发送到外部服务器。只要告警表达式在给定的时间点生成一个或多个矢量元素,告警就被这些元素的标签集视为活动状态。

groups:
  - name: test_alert
    rules:
      - alert: IfNodeUp  # 告警规则名称必须唯一
        expr: up{instance="localhost:9100"} != 1  # PromQL表达式
        for: 5m  # 达到阈值后多久报警
        labels:  # 自定义标签
          level: error
        annotations:  # 自定义附加信息
          description: "instance: {{ $labels.instance }} was down!"
          summary: 节点异常

以上为一次成功的示例,告警规则语法即注释描写的内容,需要额外说下annotation

// $labelname可访问当前告警实例中指定标签的值
{{ $labels.<labelname> }
 // $values可以获取expr的表达式计算结果
 {{ $value }}

写好上述规则后,使用HTTP发起reload重载配置文件,再访问告警界面就出现了添加的规则

image-20220520165640851

然后我停掉node_exporter,就开始报警了,此时模板的变量变成了想要的值,易读性更好。

image-20220520161625620

四、指标的采集与存储

实际生产环境较为复杂,存在各种各样的资源实例,为了统一规范化所有监控资源的监控数据,有必要通过重新标记(relabel)的方式,建立标准化的度量指标。在Prometheus的运行过程中,有两个阶段可以进行重新标记:采集前、采集后。

采集前主要依赖服务发现(service discovery),通过relabel_configs的方式自动对服务发现的目标进行重新标记;而采集后主要指标保存在存储系统之前,依赖作业内的metric_relabel_configs实现。

4.1、relabel_configs采集指标

4.1.1、简述

relabel_configs是采集配置scrape_configs里面的配置模块

relabel_configs(重新标记)是一个功能强大的工具,可以在目标被采集数据前重写它的标签集,每个采集配置可以配置多个重写标签,并按照配置的顺序应用与每个目标的标签集。重写标签之后,以双下划线__开头的标签将会被从标签集中删除。如果只需要临时存储的标签,可以使用_tmp作为前缀标识。在实际生产中,relabel_configs不仅可以分组收集数据,也可以降低内存。

这种发生在采集样本数据之前,对目标(target)的实例标签进行重写的机制,在Prometheus中称为Relabeling行为机制。可以通过配置文件中的relabel_configs字段自定义重写标签,除了可以修改标签,还可以为采集的指标添加新标签。

Relabeling最基本的应用场景就是基于target实例中包含的metadata标签,动态地添加或者覆盖标签。在http://localhost:9090/targets页面,鼠标悬停标签集会出现如下图所示:

image-20220520220327473

这是一组重新标记前的默认的元数据标签信息,完整的relabel_config配置如下:

  • __adress__:当前目标的地址,格式::
  • __scheme__:采集该目标服务所使用的HTTP方法,有HTTP或HTTPS
  • ___metric_path__:采集目标的服务访问地址的路径
  • __param_<name>_:采集目标时包含的请求参数

上图中的instance标签内容与__adress__相对应,这其实发生了一次标签重写。在使用比如consul等服务发现时,还会包含以下元数据标签信息:

  • __meta_consul_address:consul地址
  • __meta_consul_dc:consul服务所在的数据中心
  • __meta_consulmetadata:服务的元数据信息
  • __meta_consul_node:服务所在的consul节点的信息
  • __meta_consul_service_address:服务访问地址
  • __meta_consul_service_id:服务ID
  • __meta_consul_service_port:服务端口
  • __meta_consul_service:服务名称
  • __meta_consul_tags:服务包含的标签信息

综上所述,可以根据consul提供的元数据:__meta_consul_dc将其重写为dc

scrape_configs:
  - job_name: node_exporter
    consul_sd_configs:
      - server: localhost:8500
        service:
          - node_exporter
    relabel_configs:
      - source_labels: ["__meta_consul_dc"]
        target_label: "dc"

在第一个重新标记relabel_configs后,__meta标签就会被丢弃。

4.1.2、配置讲解

以下为完整的relabel_configs配置:

relabel_configs:
  - source_labels: [<labelname>,...]  # 源标签名,可多个
    separator: <string>  # 拼接符号,多个源标签会按照此处拼接符号进行拼接,默认为分号;,可选
    target_label: <labelname>  # 替换后的标签名称
    regex: <regex>  # 匹配的正则表达式,默认为(.*)
    modulus: <unit64>  # 系数,对source_labels值进行散列运算
    replacement: <string>  # $1为原标签值,如有多个匹配组,可使用$1$2代替内容,default=$1
    action: <relabel_action>  # 定义当前relabel_configs对元数据标签的搜索方式,默认为replace。

relabel常见的action类型有如下几种:

  • replace:默认值。它会根据正则表达式的配置匹配source_labels的值(多个则用separator拼接),并将匹配到的值写入target_label中。如果有多个匹配组,可以使用$1,$2确定写入内容;如匹配不到内容,则不对target_label进行重写操作。
  • keep:用于选择,丢弃source_labels的值中没有匹配到regex正则表达式内容的target实例,即匹配不到的全丢弃。
  • drop:用于排除,即丢弃用regex正则表达式匹配到source_labels的值对应的target实relabel_configs例,即匹配到的全丢弃。
  • hashmod:以modulus的值为系数,计算source_labels的散列值。
  • labelmap:根据正则的定义去匹配target实例中有标签的名称,并以匹配到的内容作为新标签名,其值作为新标签的值。
  • labeldrop:删除正则匹配到的目标的source_labels
  • labelkeep:删除正则不匹配的目标的source_labels,和labeldrop相反。

注意:

  1. 重新标记的默认操作是replace,也是最常用的操作。
  2. dropkeep可以看作是过滤器,但是,除了这两个的其它操作,无论正则是否匹配,处理都将继续进行,以下为dropkeeprelabel_configs的影响:
    • relabel_configs中,会导致目标不被采集。
    • metric_relabel_configs中,会导致时间序列不被采集。
    • alert_relabel_configs中,会导致告警不发送到alertmanager
    • write_relabel_configs中,会导致时间序列不发送到远程写入点。

4.2、metric_relabel_configs存储指标

Prometheus从数据源拉取数据后,会对原始数据进行编辑,metric_relabel_configsjob内的配置块,作用是在采集后保存数据前重新编辑标签,而relabel_configs是在采集前进行重新标记。

metric_relabel_configs的主要应用场景是:将监控数据中不需要的部分丢弃而不保存。它的参数用法和relabel_configs一样。JVM

4.2.1、保留指标

既然可以在数据存储YYY之前删除指标,那么使用keep操作就可以仅保留正则相匹配的指标。

metric_relabel_configs:
  - source_labels: [__name__]  # __name__即所有指标名称
    regex: "DEMO.*"  # 以DEMO开头的指标
    action: keep  # 仅保留以DEMO开头的指标
4.2.2、删除指标

删除多个指定的标签的指标

metric_relabel_configs:
  - source_labels: [__name__]
    separator: ","  # 分隔符,默认为分号;
    regex: "(HHH|YYY)"
    action: drop  # 删除HHH和YYY指标
4.2.3、替换标签

实际上是根据已有标签生成一个新的标签

许多cAdvisor指标都有一个ID标签,其中包含正在运行的进程的名称。以下案例对进程ID重新定义标签,放入container_id

metric_relabel_configs:
  - source_labels: [id]
    regex: "/.*"
    replacement: "$1"
    target_label: container_id
4.2.4、删除最大指标

结合前面学习的topk函数,在采集指标时删除最大的指标。

首先在Prometheus表达式浏览器执行PromQL语句:

topk(20, count by(__name__, job)({__name__=~".+"}))

这将会按指标名称和作业返回最大的20个时间序列,假设最大的有问题的指标叫做my_too_large_metric,可以在metric_relabel_configs中配置采集后删除它。

metric_relabel_configs:
  - source_labels: [__name__]
    regex: "my_too_large_metric"
    action: drop
4.2.5、提取标签

待补充

五、性能调优

尽管Prometheus通过许多功能来限制一些PromQL查询对用户的监控产生较大的影响,但实际工作中还是可能会出现没有涵盖到的一些问题。

Prometheus 2.12.0+开始,Prometheus关闭时正在运行的任何查询都将在下次启动时打印。所以当发生意外导致系统崩溃时,下次启动Prometheus时日志中会出现诸如These queries didn't finish in prometheus's last run的信息。日志信息里面提到的PromQL可能就是耗尽Prometheus资源的罪魁祸首,可以考虑对其进行优化等工作;如果是别的原因导致Prometheus异常终止,那么可以考虑丢弃不好的PromQL,比如下面一些参数:

  • –query.max-concurrency:查询最大并发值
  • –query.max-samples:查询最大样本数
  • –query.timeout:查询超时的时间
世间微尘里 独爱茶酒中