prometheus-PromQL进阶-5

PromQL聚合计算

一、说明

        大多数指标采集下来之后,我们也不可能一个一个去看它的样本值,这没什么实际的意义,假设我们的Web服务器跑了个二十个实例,这些实例上主机的响应时长,我们也不可能一个节点一个节点的看,所以在多数情况下,很有可能把多个Target上的同一个指标合并起来统一进行计算,比如求他的平均值之类的;

        在监控场景中,单个指标的意义不大,往往需要联合并可视化一组指标,这一组指标很可能是来自于不同Target的同一指标,也有可能是位于某一个对应的指标名称下的多个纬度,然后将这多个纬度给他联合起来进行计算,那么这种联合机制就是所谓的聚合操作,比如,计数、求和、平均值、分位数、标准差和方差等统计函数,将这些函数应用于时间序列样本之上,就可以生成具有统计学意义的结果,我们也可以对查询结果,按照某种分类机制进行分组(group by),将查询结果按组进行聚合计算,这也是较为常见的需求,例如分组统计、分组求平均值、分区求和等;

        对于聚合操作,PromQL提供很多内置的聚合函数,它可以针对一组值进行计算,并返回单个值,或少量几个值作为结果,Prometheus内置提供了11个聚合函数,也称之为聚合运算符,这些运算仅支持应用于单个即时向量的元素,这也就意味着这些聚合函数是不支持使用在范围向量之上的,其返回值也是具有少量元素的新向量或标量,这些聚合运算符既可以基于向量表达式返回结果中的时间序列的所有标签纬度进行分组聚合,也可以基于指定标签纬度分组后再进去分组聚合;

二、聚合表达式

  • **sum():**对样本值求和;
  • **avg(): ** 对样本值求平均值;
  • **count():**对分组内的时间序列进行数量计算;
  • **stddev():**对样本值求标准差,以帮助用户了解数据的波动大小;
  • **stdvar():**对样本值求方差,它是求取标准差过程中的中间状态;
  • **min():**求取最小值,返回其时间序列及其值;
  • **max():**求样本值中最大的值,返回其时间序列及其值;
  • **topk():**逆序返回分组内的样本值,最大的前k个时间序列及其值;
  • **bottomk():**顺序返回分组内的样本值,最小的前k个时间序列及其值;
  • **quantile():**分为数,用于评估数据的分布状态,该函数会返回分组内指定分为数的值,即数值落在小于指定分位区间的比例;
  • **count_values():**对分组内的时间序列的样本值进行数量统计;

2.1、使用

PromQL中的聚合操作语法格式可采用如下面两种格式之一

<aggr-op>([parameter,]<vector expression>)[without|by (label list)]
<aggr-op> [without|by (label list) ([parameter,]<vector expression>)]

# aggr:      聚合操作符;
# op:        operater;
# without:   从结果向量中删除由without子句指定的标签,未指定的那部分标签则用做分组标准;
# vector expression:向量表达式;
# by:        功能与without刚好相反,它仅使用by子句中指定的标签进行聚合,结果向量中出现但未被by子句指定的标签则会忽略;

# 分组求和
sum (node_filesystem_avail_bytes) by (instance)

# 分组统计
count by (mountpoint)(node_filesystem_avail_bytes{mountpoint=~'/boot|/'})

需要注意的是,当使用表达式count(http_requests_total),返回的数据类型,依然是瞬时向量。用户可以通过内置函数scalar()将单个瞬时向量转换为标量 (标量只有一个数字,没有时序)。

2.2、内置函数

        内置函数不是聚合函数,内置的函数它可以在对应的时间序列上做计算的,所以我们可以将他们理解为Prometheus的函数,这些函数就没有聚合函数的约束和限制了,他们各自所适用时间序列类型略有不同;

  • **rate(range-vector):**rate函数需要传递一个范围向量,对这个范围向量对应范围内的数据做增长率计算,一般仅用于Conter计数器上,因为对Guage类型的数据做rate是没有意义的,Guage本身就代表了随着时间变化的量,而Conter没能直接表达随时间变化的结果,所以才需要使用rate进行计算,rate的计算结果还可以求和,将rate和sum联合起来计算时,是先算rate后求和;
  • **increase函数:**和rate函数类似,increase函数在prometheus中,是专门用来针对Counter这种持续增长的数值截取一段时间的增量趋势的,这样的话,我们就可以利用它来得到CPU当前针对上一分钟的增量值了,对于rate函数,是取得平均每秒的数量的,比如我们取的是1m,那么就会将这个1m的值除以60s,最终得到每秒的平均值,那么对于increase函数来说,它就比rate函数要粗糙一些了,它是取段时间增量的总量,但是它并不对秒进行除法运算,比如说
    • increase(node_network_receive_bytes_total[1m]),它就是取node_network_receive_bytes_total 一分钟内,从开始到结束,增长的总量作为数值返回,它不进行除法运算;
    • 语法:increase(node_cpu_seconds_total{instance=“192.168.0.237:9100”}[1m]) # 截取cpu在这一分钟之内的增量值
  • sum: increase非常好用,但是它之能计算单核CPU的在一个时间区间内的增量值,但是又因为我们现在的服务器都是多核CPU,运维人员也一般都是关注整体的CPU示例比率,很少去关注每一核的CPU占比值,所以这个时候我们的sum就可以发挥作用了,它就是字面意思,求和,那么我们可以利用sum将所有核心的CPU使用量进行求和,然后再使用increase进行范围增量取值;
    • 语法:sum(increase(node_cpu_seconds_total{mode=“idle”}[1m]))
      by:by,即分组,可以看到上面的sum得到的结果是一个大结果,并且只有一个结果,如果我们要统计多台服务器的CPU增量率,那么如果不使用by也会变成一条线,因为sum是把所有的结果集,不管是什么内容,全部进行加和了,那么这个时候我们就需要用到by函数,将结果进行分组,那么这个例子,我们就可以以instance这个label作为分组的key了,by后面需要接一个lebel;
    • 语法:sum(increase(node_cpu_seconds_total{mode=“idle”}[1m])) by (instance)
  • **topk:**该函数可以从大量数据中取出排行前N的数值,N可以自定义。比如监控了100台服务器的320个CPU,用这个函数就可以查看当前负载较高的那几个,用于报警;
    • topk(3,sum(increase(node_cpu_seconds_total{mode=“user”}[1m])) by (instance)) 比如前3名CPU使用量最大的机器

2.3、指标及表达式

由此图可以看到,这个值使用了大量的标签,并且指标为1
在这里插入图片描述
promhttp_metric_handler_requests_total 抓取数据所产生的HTTP请求总数
在这里插入图片描述
查询使用了 promhttp_metric_handler_requests_total 指标的sum运算符, 它将所有的请求叠加,但没有按作业分类,使用子句By可以按特定维度聚合,通过graph可以查看时序数据
在这里插入图片描述

        rate()函数用来计算一定范围内时间序列的每秒平均增长率,只能与计数器一起使用,自适应强,例如在资源重启时会重置计数器,并且通过推断来处理时间序列中的间隔,如一次漏掉的数据抓取, rate()函数最适合用于增长较慢的计数器或用于警报的场景
在这里插入图片描述
单位缩写: s秒, m分钟,h小时,d天,w周

2.4、二元运算符

        二元运算符也是Prometheus的最强大的功能之一,二元运算符又是一种非常高级的运算逻辑的一个非常重要的表达式,PromQL支持基本的算数和逻辑运算,我们完全可以将两个向量表达式链接起来,逐一比较,类似就并集、交集等;

        支持使用操作符链接两个操作数就称之为二元运算符,它支持两个标量之间进行运算,比如1+1,支持即时向量和标量之间运算,比如1+sum (node_filesystem_avail_bytes) by (instance),同时也支持两个即时向量之间的运算;

  • 算数运算

    +:加法;
    -:减法;
    *:乘法;
    /:除法;
    ^:幂运算;
    
  • 比较运算

    ==:等值比较;
    !=:不等值比较;
    >:大于;
    <:小于;
    >=:大于等于;
    <=:小于等于;
    
  • 逻辑运算(目前仅支持在两个即时向量之间进行,不支持标量参与运算)

    and:并且;
    or:或;
    unless:除了;
    

三、向量匹配

即时向量匹配运算是PromQL的特色之一,运算时PromQL会为左侧向量中的每个元素找到匹配的元素,其匹配行为有两种基本类型,即一对一和一对多;
在这里插入图片描述

3.1、一对一匹配

        即时向量的一对一匹配指的是,从运算符的两边表达式所获取的即时向量间一次比较,并找到唯一匹配(标签完全一致)的样本值,因为这是即时向量,意味着当前时间获取的最新值,当找不到匹配项的值时,则不会出现在结果中,这一操作在使用AlterManager做报警时大量使用;

语法:
    <vector expr> <bin-op> ignoring(<label list>) <vector expr> 
    <vector expr> <bin-op>on(<label list>) <vector expr>
        ignoring:定义匹配检测室需要忽略的标签;
        on:定义匹配检测时只使用的标签;

示例

  • 找到http_requests_total指标标签为3开头响应吗的时间序列,并取出5分钟以内的指标数据,然后使用rate求得平均值,可能有302、303等能匹配到很多个时间序列,分别求出各类3xx响应码的请求增长速率;

    rate(prometheus_http_requests_total{code=~"3.*"}[5m])
    
  • 计算响应码为3xx5分钟的平均速率是否大于所有请求5分钟速率的10%

    rate(http_requests_total{status_code=~'3.*'}[5m]) > 0.1 * rate(http_requests_total[5m])
    
  • 以http响应码统计速率为例,首先第一批为单类请求方法和响应码指标统计速率,第二批为单请求方法统计速率,不做响应码限制

    # 第一批(请求方法和响应码统计)
    rate(http_errors{method="get",status_code='500'}[5m]) 24    # 统计请求方法为get响应码为500的每五分钟的平均增长速率为24个
    rate(http_errors{method="get",status_code='404'}[5m]) 30
    rate(http_errors{method="put",status_code='501'}[5m]) 3
    rate(http_errors{method="post",status_code='500'}[5m]) 6
    rate(http_errors{method="post",status_code='404'}[5m]) 21
    
    # 第二批(请求方法统计)
    rate(http_requests{method="get"}[5m]) 600 # 统计get请求每五分钟的平均增长速率为600
    rate(http_requests{method="delete"}[5m]) 34
    rate(http_requests{method="post"}[5m]) 120
    
  • 上述示例,当我们要求分别求出post、get请求错误码为500在整个http_requests对应的method的占比时,我们可以通过以下规则实现,需要使用ingoring来忽略标签,因为一边响应码500一边为所有的响应码,可能有2xx、3xx等所有需要忽略响应码,其他标签完全匹配即可;

    # example
    #  响应码为500 / 整个http请求  结果通过method进行一一匹配
    rate(http_errors{status_code='500'}[5m]) / ingoring(code) rate(http_requests[5m]) 
    
    # result
    {method='get'} 0.04    # 24 / 600
    {method='post'} 0.05    # 6 / 120
    # 因为上述example忽略了code标签,所以只需要method一对一匹配即可,最终得到的值就是
    rate(http_errors{method="get",status_code=~'500'}[5m])和rate(http_requests{method="get"}[5m])匹配,rate(http_errors{method="post",status_code='404'}[5m])和rate(http_requests{method="post"}[5m])匹配;
    

3.2、一对多匹配

对于"一"侧的每个元素,可与"多"侧的多个元素进行匹配,至于哪一边是"多",可以明确使用group_left或group_right来指定

# 语法
    <vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr> 
    <vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr> 
    <vector expr> <bin-op>on(<label list>)  group_right(<label list>) <vector expr>
    <vector expr> <bin-op>on(<label list>)  group_left(<label list>)<vector expr>
        ignoring:定义匹配检测室需要忽略的标签;
        on:定义匹配检测时只使用的标签;
        group_left/group_right:指定多的一方;

**示例:**以http响应码统计速率为例,首先第一批为单类请求方法和响应码指标统计速率,第二批为单请求方法统计速率,不做响应码限制

# 第一批(请求方法和响应码统计)
rate(http_errors{method="get",status_code='500'}[5m]) 24    # 统计请求方法为get响应码为500的每五分钟的平均增长速率为24个
rate(http_errors{method="get",status_code='404'}[5m]) 30
rate(http_errors{method="put",status_code='501'}[5m]) 3
rate(http_errors{method="post",status_code='500'}[5m]) 6
rate(http_errors{method="post",status_code='404'}[5m]) 21

# 第二批(请求方法统计)
rate(http_requests{method="get"}[5m]) 600 # 统计get请求每五分钟的平均增长速率为600
rate(http_requests{method="delete"}[5m]) 34
rate(http_requests{method="post"}[5m]) 120

**示例:**当我们要求分别求出post、get请求错误码为500在整个http_requests对应的method的占比时,我们可以通过以下规则实现,需要使用ingoring来忽略标签,因为一边想码为500一边为所有的响应码,可能有2xx、3xx等所以需要忽略响应码,其他标签完全匹配即可;

# example
# 响应码为500 / 整个http请求  结果通过method进行一对多匹配,左侧为多,右侧为一
rate(http_errors[5m]) / ingoring(code) group_left rate(http_requests[5m]) 

# result
    {method='get',code='500'} 0.04      # 24 / 600
    {method='get',code='404'} 0.05      # 30 / 600
    {method='post',code='500'} 0.05    # 6 / 120
    {method='post',code='404'} 0.175  # 6 / 120

# 因为上述example忽略了code标签,所以只需要method一对多匹配即可,http_requests为多的一方,http_errors为一的一方,最终得到的值就是
rate(http_errors{method="get",status_code='500'}[5m])和rate(http_requests{method="get"}[5m])匹配,
rate(http_errors{method="get",status_code='404'}[5m])和rate(http_requests{method="get"}[5m])匹配,
rate(http_errors{method="post",status_code='500'}[5m])和rate(http_requests{method="post"}[5m])匹配,
rate(http_errors{method="post",status_code='404'}[5m])和rate(http_requests{method="post"}[5m])匹配;

四、聚合示例

4.1、CPU使用率

# 计算每种CPU模式的每秒使用率, PromQL有一个名为irate的函数,用于计算范围向量中时间序列增加的每秒即时速率
irate(node_cpu_seconds_total[5m])	# 只是计算每秒的速率,这样没多大的用处

在这里插入图片描述
        现在将我们的irate函数放在avg聚合中,并添加了一个由instance标签聚合的by子句。通过使用来自
所有指标和所有模式的值,这将生成三个新指标,分别表示每台主机的平均CPU使用率

avg(irate(node_cpu_seconds_total{job="nodes", mode="idle"}[5m])) by (instance)

在这里插入图片描述
但这个指标还是不太准确,它仍然包括idle的值,并且它没有表示成百分比的形式。我们将查询每
个实例的idle使用率,因为它已经是一个比率,将它乘以100可以转换为百分比

avg(irate(node_cpu_seconds_total{job="nodes", mode="idle"}[5m])) by (instance) * 100

在这里插入图片描述

我们为irate查询添加了一个值为idle的mode标签,表示仅查询idle数据。我们按实例对结果进行了平均,并且还乘上100以转换为百分比的形式,这样就得到了每个主机5分钟范围内idle使用率的平均百分比。我们可以用100减去这个值,结果就是CPU使用率的百分比,如下所示

100 - avg(irate(node_cpu_seconds_total{job="nodes", mode="idle"}[5m])) by (instance) * 100

现在我们有三个指标,每台主机一个,展示了5分钟范围内平均使用CPU的百分比
在这里插入图片描述

4.2、内存使用率

  • node_memory_MemTotal_bytes:主机上的总内存。
  • node_memory_MemFree_bytes:主机上的可用内存。
  • node_memory_Buffers_bytes:缓冲缓存中的内存。
  • node_memory_Cached_bytes:页面缓存中的内存
(node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100
  1. 先得算出还有多少空闲内存: node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes
  2. node_memory_MemTotal_bytes 减去空闲内存,得出使用多少
  3. 使用率 在除 总数node_memory_MemTotal_bytes 得出百分比
  4. 百分比 * 100 得出 百分率
    在这里插入图片描述

4.3、磁盘使用率

        例如,node_filesystem_size_bytes指标显示了被监控的每个文件系统挂载的大小。我们可以使用与内存指标类似的查询来生成在主机上使用的磁盘空间的百分比

(node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_free_bytes{mountpoint="/"} )/node_filesystem_size_bytes{mountpoint="/"} * 100

# 如果有多个就直接指定挂载目录即可

在这里插入图片描述

# 也可以匹配正则表达式
(node_filesystem_size_bytes{mountpoint=~"/|/run"} - node_filesystem_free_bytes{mountpoint=~"/|/run"} )/node_filesystem_size_bytes{mountpoint=~"/|/run"} * 100

在这里插入图片描述
对于磁盘空间,我们确实需要了解指标的趋势和方向。我们通常要回答的问题是:“考虑到现在磁盘的使用情况,以及它的增长,我们会在什么时候耗尽磁盘空间?”

实际上Prometheus提供了一种机制,通过一个名为predict_linear的函数,我们可以构造一个查询来
回答这个问题。我们来看一个例子:

predict_linear(node_filesystem_size_bytes{mountpoint="/"}[1h], 4*3600) < 0
# 如果不配置 <0 ,那么将得出还有多少秒才会使用完

在这里插入图片描述
        我们选择一小时的时间窗口[1h],并将此时间序列快照放在predict_linear函数中。该函数使用简单的线性回归,根据以前的增长情况来确定文件系统何时会耗尽空间。该函数参数包括一个范围向量,即一小时窗口,以及未来需要预测的时间点。这些都是以秒为单位的,因此这里使4*3600秒,即四小时。最后<0过滤出小于0的值,即文件系统空间不足。

        因此,如果基于最后一小时的增长历史记录,文件系统将在接下来的四个小时内用完空间,那么查询将返回一个负数

4.4、服务状态

配置来自systemd收集器的数据,它向我们展示了主机上的服务状态和其他各种systemd配置。服务的状态在node_systemd_unit_state指标中暴露出来。对于收集的每个服务和服务状态都有一个指标。在示例中,我们只收集Docker、node_exporter守护进程的指标

node_systemd_unit_state{name="node_exporter.service",state="active"}

# systemctl脚本中添加
ExecStart=/usr/local/node_exporter/node_exporter --collector.systemd --collector.systemd.unit-include="(服务1|服务2).service"

# 如果没有这个参数需要在prometheus中添加
    params:
      collect[]:
        ....
        - systemd

# 以及查看是否添加状态
curl -g -X GET http://192.168.0.232:9100/metrics?collect[]=system

在这里插入图片描述

五、持久化查询

        到目前为止,我们只是在表达式浏览器中运行查询。虽然查看该查询的输出很方便,但结果仍然是临时存储在Prometheus服务器上,而且如果没有保存下次还需要重新编写语句,如果我们希望能够将我们精心编写好的复杂查询语句给它保留起来,那么Prometheus就提供了这种接口,即持久查询。我们可以通过以下三种方式使查询持久化

  • 记录规则:根据查询创建新指标。
    • 跨多个时间序列生成聚合。
    • 预先计算消耗大的查询。
    • 产生可用于生成警报的时间序列
  • 警报规则:从查询生成警报。
  • 可视化:使用Grafana等仪表板可视化查询

配置记录规则

# 通过在prometheus服务器上定义 
rule_files:
  # - "first_rules.yml"
  # - "second_rules.yml"

# 与prometheus.yml是同级目录,创建 rules文件夹,定义规则也是yml格式
mkdir rules
cd rules/
touch node_rules.yml

# 添加规则文件
rule_files:
  - "node_rules.yml"

添加记录规则

命名规则: 一般推荐的格式为 level:metric:operations

  • level:表示聚合级别,以及规则输出的标签。
  • metric:指标名称,除了使用rate()或irate()函数剥离_total计数器之外,应该保持不变
  • operation:应用于指标的操作列表,一般最新的操作放在前面
# node_rules.yml
groups:
- name: node_rules
  rules:
  - record: instance:node_cpu:avg_rate5m	# 名称可以定义成这样
  	# 5分钟内cpu的平均使用率
    expr: avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100
    # expr 保存生成新时间序列的查询

        记录规则在规则组中定义,这里的规则组叫作node_rules。规则组名称在服务器中必须是唯一的。规则组内的规则以固定间隔顺序执行。默认情况下,这是通过全局evaluate_interval来控制的,但你可以使用interval子句在规则组中覆盖

        规则组内规则执行的顺序性质意味着你可以在后续规则中使用之前创建的规则。这允许你根据规则创建指标,然后在之后的规则中重用这些指标。这仅在规则组内适用,规则组是并行运行的,因此不建议跨组使用规则

# 间隔
groups:
- name: node_rules
  interval: 10s	# 这将更新规则组为每10秒运行一次,而不是全局的15秒
  rules:
	....

完整记录

groups:
- name: node_rules
  rules:
  - record: instance:node_cpu:avg_rate5m
    expr: avg(irate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100
    labels:
      metric_type: aggregation
  - record: instance:node_memory_usage:percentage
    expr: (node_memory_MemTotal_bytes - (node_memory_MemFree_bytes + node_memory_Buffers_bytes + node_memory_Cached_bytes)) / node_memory_MemTotal_bytes * 100
  - record: instance:node_filesystem_usage:percentage
    expr: (node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_free_bytes{mountpoint="/"}) /node_filesystem_size_bytes{mountpoint="/"} * 100

在这里插入图片描述

六、聚合和警报

        除了记录规则还有一种规则也能够保存下来,即告警规则,告警规则是另一种定义在Prometheus配置文件中的一种PromQL表达式,它通常是一种Bool表达式,返回值要么是True要么是False,主要用于告警;

        服务器可以查询和聚合时间序列数据,并创建规则来记录常用的查询和聚合,如:计算变化的速率,或者产生类似求和等聚合,这样就不必重新创建常用的聚合,如:用于调试,并且预计算可能经每次需要时运行查询性能更好

        prometheus还可以定义警报规则,比如资源使用异常 ,但prometheus没有内置警报工具,而是将警报从prometheus推送到alertmanager的单独服务器,alertmanager可以管理、整合和分发各种警报到不同目的地,如:它可以在发出警报时发送电子邮件,并能够防止重复发送

规则是另一种定义在Prometheus配置文件中的一种PromQL表达式,它通常是一种Bool表达式,返回值要么是True要么是False,主要用于告警;

        服务器可以查询和聚合时间序列数据,并创建规则来记录常用的查询和聚合,如:计算变化的速率,或者产生类似求和等聚合,这样就不必重新创建常用的聚合,如:用于调试,并且预计算可能经每次需要时运行查询性能更好

        prometheus还可以定义警报规则,比如资源使用异常 ,但prometheus没有内置警报工具,而是将警报从prometheus推送到alertmanager的单独服务器,alertmanager可以管理、整合和分发各种警报到不同目的地,如:它可以在发出警报时发送电子邮件,并能够防止重复发送

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值