本文系朱政科《Prometheus云原生监控:运维与开发实战》学习笔记
数学不好学PromQL简直是灾难。。。
上一章节用了较长的篇幅介绍了Prometheus
实战的核心:PromQL
,本章节将会深入介绍内置函数、HTTP API
、记录规则、告警规则、metric_relabel_configs
、relabel_configs
这6
方面的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_replace
和label_join
提供了对时间序列标签的自定义能力,通过对生产环境中的指标进行标准化管理,能够更好地与客户端或者可视化工具进行配合。
1.1.1、标签分类
针对监控系统,一般有拓补标签(topological label
)和模式标签(schematic label
)两种分类方式对已有标签操作,以此更好地管理时序数据。
拓补标签
通过物理或者逻辑组来区分服务组件,比如Prometheus
用job
(作业,可理解为资源类型)、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
也不会删除源指标名称,它是将时间序列汇总的V
个src_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))
结果如下所示:
我电脑
CPU
是6
核心,因此也就是2.718^6=403
{} | 403.4287934927351 |
---|
1.2.3、ln
ln
和exp
正好相反,给定一个瞬时向量,将返回其样本值的自然对数。
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
floor
跟ceil
类似,只是返回的是向下四舍五入的整数。
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
种逻辑运算符:and
、or
、unless
都是以多对多的方式工作,它们是可以进行多对多工作的运算符。
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
多数情况下不会为查询到的瞬时向量进行排序,需要手动使用sort
或sort_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
)的函数主要有四种:rate
、increase
、irate
、resets
。它们都仅接受区间向量(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
个:changes
、deriv
、predict_linear
、delta
、idelta
、holt_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>0
且tf≤1
。
holt_winters(v range-vector, sf scalar, tf scalar)
holt_winters
函数是一种时间序列分析和预报方法,它适用于含有线性趋势和周期波动的非平稳序列,利用指数平滑法(EMA
)让模型参数可以不断适应非平稳序列的变化,并对未来趋势进行短期预报。
holt_winters
在holt
模型的基础上引入了winters
周期项(又称季节项),可以用来处理月度数据(周期为12
)、季度数据(周期为4
)、星期数据(周期为7
)等时间序列中固定周期的波动行为。引入多个winters
还可以处理多种周期并存的情况。z
指数平滑法的基本思想:随着时间变化,权重以指数方式下降,将权重按照指数级进行衰减,最终年代久远的数据权重接近于0
。指数平滑法有几种不通的形式:
- 一次指数平滑法,针对没有趋势和季节性的序列。
- 二次指数平滑法,针对没有趋势但有季节性的序列。
- 三次指数平滑法针对有趋势也有季节性的序列
以下根据0.3
和0.6
两个趋势系数,获取最近1h
内存使用的平滑指数趋势:
holt_winters(process_resident_memory_bytes[1h], 0.3, 0.6)
1.8.6、changes
计算区间向量中每个样本数据值变化的次数,如样本数据值没有发生变化,则返回1
。changes
看似与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
来访问Prometheus
的HTTP 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
的表达式查询分为query
和query_range
两种,分别通过/api/v1/query
和/api/v1/query_range
查询当前或一定时间范围内的计算结果。
2.3.1、query(瞬时查询)
使用 query
这API
可以查询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"}
里面的job
和instance
。
{"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
下会有instance
和job
信息,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:
Prometheus
在prometheus
主程序同级目录下有一个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
重载配置文件,再访问告警界面就出现了添加的规则
然后我停掉node_exporter
,就开始报警了,此时模板的变量变成了想要的值,易读性更好。
四、指标的采集与存储
实际生产环境较为复杂,存在各种各样的资源实例,为了统一规范化所有监控资源的监控数据,有必要通过重新标记(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
页面,鼠标悬停标签集会出现如下图所示:
这是一组重新标记前的默认的元数据标签信息,完整的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
相反。
注意:
- 重新标记的默认操作是
replace
,也是最常用的操作。 drop
和keep
可以看作是过滤器,但是,除了这两个的其它操作,无论正则是否匹配,处理都将继续进行,以下为drop
和keep
对relabel_configs
的影响:- 在
relabel_configs
中,会导致目标不被采集。 - 在
metric_relabel_configs
中,会导致时间序列不被采集。 - 在
alert_relabel_configs
中,会导致告警不发送到alertmanager
。 - 在
write_relabel_configs
中,会导致时间序列不发送到远程写入点。
- 在
4.2、metric_relabel_configs存储指标
Prometheus
从数据源拉取数据后,会对原始数据进行编辑,metric_relabel_configs
是job
内的配置块,作用是在采集后保存数据前重新编辑标签,而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:查询超时的时间