Prometheus & PromQL

1 Prometheus简介和架构

Prometheus 是由 SoundCloud 开源监控告警解决方案。架构图如下:

如上图,Prometheus主要由以下部分组成:

  • Prometheus Server:用于抓取和存储时间序列化数据
  • Exporters:主动拉取数据的插件
  • Pushgateway:被动拉取数据的插件
  • Altermanager:告警发送模块
  • Prometheus web UI:界面化,也包含结合Grafana进行数据展示或告警发送

prometheus本身是一个以进程方式启动,之后以多进程和多线程实现监控数据收集、计算、查询、更新、存储的这样一个C/S模型运行模式。

2 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 创建配置文件
[root@VM-10-48-centos ~] mkdir /data/config/prometheus && cd /data/config/prometheus
[root@VM-10-48-centos prometheus]vim prometheus.yml
global:
scrape_interval: 60s
evaluation_interval: 60s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
labels:
instance: prometheus

- job_name: linux
static_configs:
- targets: ['10.1.10.48:9100']
labels:
instance: localhost
- job_name: 'Nginx'
static_configs:
- targets: ['10.1.10.48:9145']

容器启动(请提前配置好docker环境及镜像加速)

1
docker run -it --name prometheus -d -p 9090:9090 -v /data/config/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus
1
docker run -d -p 9100:9100 -v "/proc:/host/proc:ro" -v "/sys:/host/sys:ro" -v "/:/rootfs:ro"   --net="host" prom/node-exporter

访问 http://localhost:9100/metrics,可以看到当前 node exporter 获取到的当前主机的所有监控数据,如下所示:

3 PromQL

3.1 什么是PromQL

PromQL(Prometheus Query Language)是 Prometheus 内置的数据查询语言,它能实现对事件序列数据的查询、聚合、逻辑运算等。它并且被广泛应用在 Prometheus 的日常应用当中,包括对数据查询、可视化、告警处理当中。

简单地说,PromQL 广泛存在于以 Prometheus 为核心的监控体系中。所以需要用到数据筛选的地方,就会用到 PromQL。例如:监控指标的设置、报警指标的设置等等。

3.2 PromQL基础用法

当 Prometheus 通过 Exporter 采集到相应的监控指标样本数据后,我们就可以通过 PromQL 对监控样本数据进行查询。

当我们直接使用监控指标名称查询时,可以查询该指标下的所有时间序列。我们这里启动 Prometheus 服务器,并打开 http://localhost:9090/graph 地址。在查询框中,我们输入:prometheus_http_requests_total 并点击执行。

可以看到我们查询出了所有指标名称为 prometheus_http_requests_total 的数据。

PromQL 支持户根据时间序列的标签匹配模式来对时间序列进行过滤,目前主要支持两种匹配模式:完全匹配正则匹配

3.3 完全匹配

PromQL 支持使用 = 和!= 两种完全匹配模式。

  • 等于。通过使用 label=value 可以选择那些标签满足表达式定义的时间序列。
  • 不等于。通过使用 label!=value 则可以根据标签匹配排除时间序列。

例如上面查询出了所有指标名称为 prometheus_http_requests_total 的数据。这时候我们希望只查看错误的请求,即过滤掉所有 code 标签不是 200 的数据。那么我们的 PromQL 表达式可以修改为:prometheus_http_requests_total{code!="200"}

从上图可以看到,查询出的结果已经过滤掉了所有 code 不为 200 的数据。

3.4 正则匹配

PromQL 还可以使用正则表达式作为匹配条件,并且可以使用多个匹配条件。

  • 正向匹配。使用 label=~regx 表示选择那些标签符合正则表达式定义的时间序列。
  • 反向匹配。使用 label!~regx 进行排除。

例如查询指标 prometheus_http_requests_total 中,所有 handler 标签以 /api/v1 开头的记录,那么表达式为:prometheus_http_requests_total{handler=~"/api/v1/.*"}

3.5 范围查询

上面直接通过类似 prometheus_http_requests_total 表达式查询时间序列时,同一个指标同一标签只会返回一条数据。这样的表达式我们称之为瞬间向量表达式,而返回的结果称之为瞬间向量

而如果我们想查询一段时间范围内的样本数据,那么我们就需要用到区间向量表达式,其查询出来的结果称之为区间向量。时间范围通过时间范围选择器 [] 进行定义。例如,通过以下表达式可以选择最近 5 分钟内的所有样本数据

1
prometheus_http_requests_total{}[5m]

通过查询结果可以看到,此时我们查询出了所有的样本数据,而不再是一个样本数据的统计值。

除了使用 m 表示分钟以外,PromQL 的时间范围选择器支持其它时间单位:

1
2
3
4
5
6
s - 秒
m - 分钟
h - 小时
d - 天
w - 周
y - 年

3.6 时间位移操作

在瞬时向量表达式或者区间向量表达式中,都是以当前时间为基准:

1
2
3
4
# 瞬时向量表达式,选择当前最新的数据
prometheus_http_requests_total{}
# 区间向量表达式,选择以当前时间为基准,5分钟内的数据
prometheus_http_requests_total{}[5m]

如果查询 5 分钟前的瞬时样本数据,或昨天一天的区间内的样本数据呢?这个时候我们就可以使用位移操作,位移操作的关键字为 offset。

1
2
3
4
5
6
# 查询 5 分钟前的最新数据
http_request_total{} offset 5m
# 往前移动 1 天,查询 1 天前的数据
# 例如现在是 2020-10-07 00:00:00
# 那么这个表达式查询的数据是:2020-10-05 至 2020-10-06 的数据
http_request_total{}[1d] offset 1d

3.7 聚合操作

一般情况下,我们通过 PromQL 查询到的数据都是很多的。PromQL 提供的聚合操作可以用来对这些时间序列进行处理,形成一条新的时间序列。

以我们的 prometheus_http_requests_total{code!="200"} 指标为例,不加任何条件我们查询到的数据为:

从上图可以看出,一共有3条数据,这3条数据的value综合为:26

第一个表达式,计算一共有几条数据count(prometheus_http_requests_total{code!="200"})

第二个表达式,计算所有数据的 value 总和:sum(prometheus_http_requests_total{code!="200"})

3.8 标量

在 PromQL 中,标量是一个浮点型的数字值,没有时序。例如:26

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

如上图所示,我们将 sum 操作的值用 scalar 转换了一下,最终的结果就是一个标量了。

4 PromQL 操作符

PromQL 还支持丰富的操作符,用户可以使用这些操作符对进一步的对事件序列进行二次加工。这些操作符包括:数学运算符,逻辑运算符,布尔运算符等等。

4.1 数学运算符

数学运算符比较简单,就是简单的加减乘除等。

例如我们通过 prometheus_http_response_size_bytes_sum 可以查询到 Prometheus 这个应用的 HTTP 响应字节总和。但是这个单位是字节,我们希望用 MB 显示。那么我们可以这么设置:prometheus_http_response_size_bytes_sum/8/1024

最终显示的数据就是以 MB 作为单位的数值。

PromQL 支持的所有数学运算符如下所示:

  • + (加法)
  • - (减法)
  • * (乘法)
  • / (除法)
  • % (求余)
  • ^ (幂运算)

4.2 布尔运算符

布尔运算符支持用户根据时间序列中样本的值,对时间序列进行过滤。例如我们可以通过 prometheus_http_requests_total 查询出每个接口的请求次数,但是如果我们想筛选出请求次数超过 20 次的接口呢?

此时我们可以用下面的 PromQL 表达式:

1
prometheus_http_requests_total > 100

可以看到我们将所有 value 值超过 100 的数据都筛选了出来,从其指标名称可以看出对应的接口名。

从上面的图中我们可以看到,value 的值还是具体的数值。但如果我们希望对符合条件的数据,value 变为 1。不符合条件的数据,value 变为 0。那么我们可以使用 bool 修饰符。

我们使用下面的 PromQL 表达式:

1
prometheus_http_requests_total > bool 100

从下面的执行结果可以看到,这时候并不过滤掉数据,而是将 value 的值变成了 1 或 0。

目前,Prometheus 支持以下布尔运算符如下:

  • * == (相等)
  • != (不相等)
  • > (大于)
  • < (小于)
  • >= (大于等于)
  • <= (小于等于)

4.3 集合运算符

通过集合运算,可以在两个瞬时向量与瞬时向量之间进行相应的集合操作。目前,Prometheus 支持以下集合运算符:

  • and 与操作
  • or 或操作
  • unless 排除操作

4.3.1 and 与操作

vector1 and vector2 进行一个与操作,会产生一个新的集合。该集合中的元素同时在 vector1 和 vector2 中都存在。

例如我们有 vector1 为 A B C,vector2 为 B C D,那么 vector1 and vector2 的结果为:B C。

4.3.2 or 或操作

vector1 and vector2 进行一个或操作,会产生一个新的集合。该集合中包含 vector1 和 vector2 中的所有元素。

例如我们有 vector1 为 A B C,vector2 为 B C D,那么 vector1 or vector2 的结果为:A B C D。

4.3.3 unless 排除操作

vector1 and vector2 进行一个或操作,会产生一个新的集合。该集合首先取 vector1 集合的所有元素,然后排除掉所有在 vector2 中存在的元素。

例如我们有 vector1 为 A B C,vector2 为 B C D,那么 vector1 unless vector2 的结果为:A。

5 PromQL 聚合操作

Prometheus 还提供了聚合操作符,这些操作符作用于瞬时向量。可以将瞬时表达式返回的样本数据进行聚合,形成一个新的时间序列。目前支持的聚合函数有:

  • sum (求和)
  • min (最小值)
  • max (最大值)
  • avg (平均值)
  • stddev (标准差)
  • stdvar (标准方差)
  • count (计数)
  • count_values (对 value 进行计数)
  • bottomk (后 n 条时序)
  • topk (前 n 条时序)
  • quantile (分位数)

5.1 sum 求和

用于对记录的 value 值进行求和。

例如:sum(prometheus_http_requests_total) 表示统计所有 HTTP 请求的次数。

5.2 min 最小值

返回所有记录的最小值。

prometheus_http_requests_total 指标所有数据如下图所示:

当我们执行如下 PromQL 时,会筛选出最小的记录值。

1
min(prometheus_http_requests_total)

5.3 max 最大值

返回所有记录的最大值。

当我们执行如下 PromQL 时,会筛选出最大的记录值。

1
max(prometheus_http_requests_total)

5.4 avg 平均值

avg 函数返回所有记录的平均值。

当我们执行如下 PromQL 时,会筛选出最大的记录值。

1
avg(prometheus_http_requests_total)

5.5 count 计数

count 函数返回所有记录的计数(即:有多少条记录)。

例如:count(prometheus_http_requests_total) 表示统计所有 HTTP 请求记录的技数。

5.6 bottomk 后几条

bottomk 用于对样本值进行排序,返回当前样本值后 N 位的时间序列。

例如获取 HTTP 请求量后 5 位的请求,可以使用表达式:

1
bottomk(5, prometheus_http_requests_total)

5.7 topk 前几条

topk 用于对样本值进行排序,返回当前样本值前 N 位的时间序列。

例如获取 HTTP 请求量前 5 位的请求,可以使用表达式:

1
topk(5, prometheus_http_requests_total)

6 PromQL 内置函数

PromQL 提供了大量的内置函数,可以对时序数据进行丰富的处理。例如 irate () 函数可以帮助我们计算监控指标的增长率,不需要我们去手动计算。

6.1 rate 增长率

我们知道 counter 类型指标的特点是只增不减,在没有发生重置的情况下,其样本值是不断增大的。为了能直观地观察期变化情况,需要计算样本的增长率。

increase(v range-vector) 函数是 PromQL 中提供的众多内置函数之一。其中参数 v 是一个区间向量,increase 函数获取区间向量中的第一个后最后一个样本并返回其增长量。因此,可以通过以下表达式 Counter 类型指标的增长率:

1
increase(node_cpu[2m]) / 120

这里通过 node_cpu [2m] 获取时间序列最近两分钟的所有样本,increase 计算出最近两分钟的增长量,最后除以时间 120 秒得到 node_cpu 样本在最近两分钟的平均增长率。并且这个值也近似于主机节点最近两分钟内的平均 CPU 使用率。

除了使用 increase 函数以外,PromQL 中还直接内置了 rate (v range-vector) 函数,rate 函数可以直接计算区间向量 v 在时间窗口内平均增长速率。因此,通过以下表达式可以得到与 increase 函数相同的结果:

1
rate(node_cpu[2m])

需要注意的是使用 rate 或者 increase 函数去计算样本的平均增长速率,容易陷入「长尾问题」当中,其无法反应在时间窗口内样本数据的突发变化。

例如,对于主机而言在 2 分钟的时间窗口内,可能在某一个由于访问量或者其它问题导致 CPU 占用 100% 的情况,但是通过计算在时间窗口内的平均增长率却无法反应出该问题。

为了解决该问题,PromQL 提供了另外一个灵敏度更高的函数 irate(v range-vector)。irate 同样用于计算区间向量的计算率,但是其反应出的是瞬时增长率。irate 函数是通过区间向量中最后两个样本数据来计算区间向量的增长速率。

这种方式可以避免在时间窗口范围内的「长尾问题」,并且体现出更好的灵敏度,通过 irate 函数绘制的图标能够更好的反应样本数据的瞬时变化状态。

1
irate(node_cpu[2m])

irate 函数相比于 rate 函数提供了更高的灵敏度,不过当需要分析长期趋势或者在告警规则中,irate 的这种灵敏度反而容易造成干扰。因此在长期趋势分析或者告警中更推荐使用 rate 函数。

6.2 predict_linear 增长预测

在一般情况下,系统管理员为了确保业务的持续可用运行,会针对服务器的资源设置相应的告警阈值。例如,当磁盘空间只剩 512MB 时向相关人员发送告警通知。这种基于阈值的告警模式对于当资源用量是平滑增长的情况下是能够有效的工作的。

但是如果资源不是平滑变化的呢?比如有些某些业务增长,存储空间的增长速率提升了高几倍。这时,如果基于原有阈值去触发告警,当系统管理员接收到告警以后可能还没来得及去处理问题,系统就已经不可用了。

因此阈值通常来说不是固定的,需要定期进行调整才能保证该告警阈值能够发挥去作用。那么还有没有更好的方法吗?

PromQL 中内置的 predict_linear(v range-vector, t scalar) 函数可以帮助系统管理员更好的处理此类情况,predict_linear 函数可以预测时间序列 v 在 t 秒后的值。

它基于简单线性回归的方式,对时间窗口内的样本数据进行统计,从而可以对时间序列的变化趋势做出预测。例如,基于 2 小时的样本数据,来预测主机可用磁盘空间的是否在 4 个小时候被占满,可以使用如下表达式:

1
predict_linear(node_filesystem_free{job="node"}[2h], 4 * 3600) < 0

7 Metric 指标

在 Prometheus 中,我们所有的信息都以 Metrics(指标) 的形式存在。

Metrics 由 metric name 和 label name 组成。

1
<metric name>{<label name>=<label value>, ...}

例如下面的 api_http_requests_total 就是 metrics name(指标名称),而 method 就是 label name(标签)。而 metric name 加上 label name 就是一个完整的 Metric。

1
api_http_requests_total{method="POST", handler="/messages"}

7.1 Metric Type 指标类型

Prometheus 中主要有四种不同的指标类型,用来适应不同的指标类型。

  • counter 计数器
  • gauges 计量器
  • histogram 柱状图
  • summary 汇总

counter 计数器

数据从 0 开始累计,理想状态下应该是永远增长或者是不变。

适用于例如机器开机时间、HTTP 访问量等数值。

gauges 量器

获取一个返回值,采集回来是多少就是多少。数值可能升高,也可能降低。

适用于例如硬盘容量、CPU 内存使用率等数值。

histogram 柱状图

counter 和 gauges 反应的是数值的情况,而 histogram 则是反应数值的分布情况。

histogram 柱状图反映了样本的区间分布梳理,经常用来表示请求持续时间、响应大小等信息。

例如我们 1 分钟内有 1000 个 http 请求,我们想要知道大多数的请求耗时是多少。这时我们使用评价耗时可能不太准,但是我们使用 histogram 柱状图就可以看出这些请求大多数都是分布在哪个耗时区间。

Histogram 与 summary

为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在 010ms 之间的请求数有多少而 1020ms 之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram 和 Summary 都是为了能够解决这样问题的存在,通过 Histogram 和 Summary 类型的监控指标,我们可以快速了解监控样本的分布情况。

Histogram 指标直接反应了在不同区间内样本的个数,区间通过标签 len 进行定义。而 summary 则是使用中位数反映样本的情况。

这篇文章我们介绍了 Prometheus 的几个关键概念:

  • Metric 指标
  • Metric Type 指标类型
  • 工作 (Job)和实例(Instance)

它们之间的关系如下图所示:

一个任务(Job)可以有多个实例(Instance),一个实例上可以有多个指标(Metric),一个指标只会有一个指标类型(Metric Type)。