momo's Blog.

从零开始的Prometheus学习之旅

字数统计: 5.4k阅读时长: 21 min
2021/02/05 Share

概念

数据模式

指标名称和标签

每个时间序列都可以通过 指标名称 和可选的键值对label 来进行标识

比如说: http_requests_total 标识请求收到的HTTP的总数. 它可以包含数字,字母,以及下划线和冒号。

PS: 需要注意的事, : 冒号是用户定义的 record rules 保留的, exporter 或者其他工具不应该直接使用它们.

标签来负责标识指标的维度, 比如关于http_requests_total指标的所在集群,所属业务, 所属应用.

上述命名规则都必须匹配正则表达式[a-zA-Z_][a-zA-Z0-9_]*
指标和标签命名规范

Samples

Samples 构成实际的时间序列数据。每个Samples包括

  • 一个float64值
  • 毫秒精度的时间戳

Metric 类型

Prometheus客户端库提供了四种Metric类型。

Counter(计数器)

用来计数一个只增不减的指标, 只能在重启时增加或者归零. 一般来说,可以用计数器标识服务器请求数,服务器错误数,HTTP 状态码数量等等,这些指标是不存在减少的性质。

所以,不要试图用Counter类型来针对上下波动的指标进行收集,比如:当前正在运行的进程数
一个计数器是

Gauge(仪表盘)

gauge 用于标识一个可上下波动的单个数值, 是一个比较常用的类型。比如:CPU,内存使用率,以及其他动态上升下降的其他指标

Histogram (直方图)

在大多数情况下人们都倾向于使用某些量化指标的平均值,例如 CPU 的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统 API 调用的平均响应时间为例:如果大多数 API 请求都维持在 100ms 的响应时间范围内,而个别请求的响应时间需要 5s,那么就会导致某些 WEB 页面的响应时间落到中位数的情况,而这种现象被称为长尾问题。

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

Histogram 在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图。

Histogram 类型的样本会提供三种指标(假设指标名称为 <basename>):

  • 样本的值分布在 bucket 中的数量,命名为 _bucket{le="<上边界>"}。解释的更通俗易懂一点,这个值表示指标值小于等于上边界的所有样本数量。

  • 所有样本值的大小总和,命名为 <basename>_sum
  • 样本总数,命名为 <basename>_count。值和 _bucket{le="+Inf"} 相同

可以通过 histogram_quantile() 函数来计算 Histogram 类型样本的分位数。分位数可能不太好理解,我举个例子,假设你要计算样本的 9 分位数(quantile=0.9),即表示 90% 的样本的值。Histogram 还可以用来计算应用性能指标值(Apdex score)。

Summary(摘要)

与 Histogram 类型类似,用于表示一段时间内的数据采样结果(通常是请求持续时间或响应大小等),但它直接存储了分位数(通过客户端计算,然后展示出来),而不是通过区间来计算。

  • 样本值的分位数分布情况,命名为 <basename>{quantile="<φ>"}

  • 所有样本值的大小总和,命名为 <basename>_sum
  • 样本总数,命名为 <basename>_count

Jobs and instance (作业和实例)

用Prometheus术语来说, 你可以抓取的端点称为instance,通常对应于单个进程。那具有相同目的的实例集合,我们称为Job. (例如:抓取数据库, 抓取容器状态)

  • job: api-server
    • instance 1: 1.2.3.4:5670
    • instance 2: 1.2.3.4:5671
    • instance 3: 5.6.7.8:5670
    • instance 4: 5.6.7.8:5671

自动生成的标签和时间序列

自动添加的标签有

  • job: 目标所属的已配置作业名称
  • instance: <host>:<port>抓取的目标网址的一部分

PromQL 查询

数据类型

  • 瞬时向量(Instant vector) 返回查询metric时所有的匹配到符合条件的时间序列, 每个时间序列包含一个样本值, 所有的样本共享一个时间戳

  • 范围向量(Range vector) 返回查询metric时所有的匹配到符合条件的时间序列, 每个时间序列返回多个随着时间变化的数据点

  • 标量(Scalar) 一个简单的数字浮点值
  • 字符串(String) 一个简单的字符串值;目前未使用

时间序列选择器(Time series Selectors)

瞬时向量选择器 (Instant vector selectors)

瞬时向量选择器可以在给定的时间戳(瞬时 Instant)下选择一组时间序列和某个样本的单个样本值: 最简单的方式, 指定一个metric名称, 这就是一个瞬时向量, 其中包含具有该 metric 名称的所有时间序列的元素

1
http_requests_total

通过在花括号 ({}) 中添加过标签,可以过滤出自己需要的时间序列

1
http_requests_total{job="prometheus",group="canary"}

也支持正则表达式和取反操作

  • =:选择与提供的字符串完全相同的标签。
  • !=:选择不等于提供的字符串的标签。
  • =~:选择与提供的字符串进行正则表达式匹配的标签。
  • !~:选择与提供的字符串不进行正则表达式匹配的标签。

例如:选择所有指标为 http_requests_total, 并且环境是staging, testing 以及development 并且HTTP的方法不是GET的时间序列

1
http_requests_total{environment=~"staging|testing|development",method!="GET"}

瞬时选择器必须指定一个名称metric, 或者指定一个不匹配空字符串标签

1
2
3
4
5
6
7
8
http_requests_total # 可以
http_requests_total{} # 可以

# 这里匹配到了空字符串
{job=~".*"} # 不可以

{job=~".+"} # 可以!
{job=~".*",method="get"} # 可以!

通过使用内部变量__name__, 标签匹配也可以应用于metric name。例如: 表达式http_requests_total 等效于 {__name__="http_requests_total"} 并且也支持条件匹配=, !=, =~, !~.

以下表达式选择名称以job: 开头的所有metric

1
{__name__=~"job:.*"}

metric 名称不能使用Prometheus 中的关键字 bool,on, ignoring. group_leftgroup_right
所以,以下表达式是非法的

1
on{} # Bad!

如果非要使用这些指标,可以使用__name__标签来匹配

1
{__name__="on"} # Good!

范围向量选择器 (Range Vector Selectors)

范围选择器和瞬时选择器用法大致相同, 唯一的区别是需要加上[]以表示时间

如: 获取5分钟的数据

1
http_requests_total{job="prometheus"}[5m]

持续时间(Time Durations)

  • ms -毫秒
  • s -秒
  • m - 分钟
  • h - 小时
  • d -天-假设一天总是24小时
  • w -周-假设一周始终为7天
  • y -年-假设一年总是365天

这些时间可以随时进行串联组合,但是单位必须是从长到短,并且只能出现一次

以下一些示例

1
2
3
4
5h
1h30m
5m
10s

偏移量修改器 (Offset modifier)

通过使用Offset 可以改变瞬时向量和范围向量的时间偏移

比如: 查询瞬时向量 5分钟之前的值

1
http_requests_total offset 5m

注意: offset 修饰符必须跟在瞬时选择器的后面

1
sum(http_requests_total{method="GET"} offset 5m) // GOOD.

以下是不正确的

1
sum(http_requests_total{method="GET"}) offset 5m

范围选择器和瞬时选择器使用方法相同
这里返回http_requests_total一周前的5分钟数据

1
rate(http_requests_total[5m] offset 1w)

运算操作 (operator)

对于两个

算数运算符

  • + (加法)
  • - (减法)
  • * (乘法)
  • / (除法)
  • % (取余)
  • ^ (幂操作)

Prometheus 提供了, 标量与标量之间, 瞬时向量与标量之间, 瞬时向量与瞬时向量之间的运算
scalar/scalar vector/scalar vector/vector

  • 标量与标量之间(Between two scalars), 两个浮点数之间的求值运算, 并返回结果

  • 瞬时向量与标量之间(Between an instant vector and a scalar), 瞬时向量的值与标量之间的计算, 例如可以把一个时间序列的值乘以2, 则结果是另一个向量,结果是原始向量的值乘以2,并且metric名称被删除,只保留label

  • 瞬时向量与瞬时向量之间(Between two instant vectors), 以左侧的时间序列的结果, 并将其匹配到右侧的元素, 从而得出结果。 结果的指标名称会被删除, 如果在右侧向量匹配左侧的时间序列条目,则不会将结果输出.

第三个只靠描述有点抽象, 让我们看一个例子.

  • kubelet_http_requests_total{} 这里当做左侧的时间序列,并且这个时间序列只有一个条目

  • 这里是右侧的时间序列,并且有很多条目

  • 让我们把这两个相加

结果只有一个条目, 并且删除了 指标名称, 结果为8. 计算的过程,就是先列出左边的条目,随后去匹配右边的条目, 如果找到相同的条目以后 在进行运算符的计算。

  • 如果右边没有匹配到条目, 则不会参与计算

比较运算符

  • == (等于)
  • != (不相等)
  • > (大于)
  • < (少于)
  • >= (大于等于)
  • <= (小于等于)
  • 标量与标量之间(Between two scalars), 标量之间运算必须添加bool修饰符,并且返回0(false) 或者 1(true).

  • 瞬时向量与标量之间(Between an instant vector and a scalar), 默认是匹配模式, 跟加减运算一样,会拿左侧的条目,依次匹配右侧的条目, 当匹配成功以后,会计算操作, 然后生成一个新的时间序列, 把结果为true则保留, 为false则去除。 如果想要保留所有匹配到的条目, 则需要添加bool修饰符. 并且保留条目的结果会变成0 or 1,指标名称也会被删除。

  • 瞬时向量与瞬时向量之间(Between two instant vectors), 两个向量之间进行操作,默认情况下会先充当过滤器, 左侧的向量过滤右侧的向量, 然后在比较匹配后的条目. 表达式不正确,或者没有匹配到的的条目会被删除,

逻辑/集合二元操作符

逻辑/集合二元操作符只能作用在即时向量, 包括:

  • and 交集

  • or 并集

  • unless 补集

  • vector1 and vector2: 得到一个由vector1元素组成的向量,其中vector2中的元素具有完全匹配的标签集,其他元素被删除。

  • vector1 or vector2:会产生一个新的向量,该向量包含vector1中所有的样本数据,以及vector2中没有与vector1匹配到的样本数据。

  • vector1 unless vector2:会产生一个新的向量,新向量中的元素由vector1中没有与vector2匹配的元素组成。

向量匹配

向量与向量之间进行运算操作时会基于默认的匹配规则:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行运算,如果没找到匹配元素,则直接丢弃。
接下来将介绍在PromQL中有两种典型的匹配模式:一对一(one-to-one),多对一(many-to-one)或一对多(one-to-many)。

一对一(one-to-one)向量匹配

一对一匹配模式会从操作符两边表达式获取的瞬时向量依次比较并找到唯一匹配(标签完全一致)的样本值。默认情况下,使用表达式:

1
vector1 <operator> vector2

在操作符两边表达式标签不一致的情况下,可以使用on(label list)或者ignoring(label list)来修改便签的匹配行为。使用ignoreing可以在匹配时忽略某些便签。而on则用于将匹配行为限定在某些便签之内。

1
2
<vector expr> <bin-op> ignoring(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) <vector expr>

例如有这两个向量

1
2
3
4
5
6
7
8
9
10
method_code:http_errors:rate5m{method="get", code="500"}  24
method_code:http_errors:rate5m{method="get", code="404"} 30
method_code:http_errors:rate5m{method="put", code="501"} 3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21


method:http_requests:rate5m{method="get"} 600
method:http_requests:rate5m{method="del"} 34
method:http_requests:rate5m{method="post"} 120

请求例子:

1
method_code:http_errors:rate5m{code="500"} / ignoring(code) method:http_requests:rate5m

这将返回一个结果向量,其中包含每个方法的状态码为500的HTTP请求的部分,这是在过去5分钟内测量的结果。如果没有ignoring(code),就不会有匹配,因为metrics 无法匹配到完全相同的标签集。带有put和del方法的条目没有匹配,不会在结果中显示.

1
2
{method="get"}  0.04            //  24 / 600
{method="post"} 0.05 // 6 / 120

多对一和一对多两种匹配模式指的是“一”侧的每一个向量元素可以与”多”侧的多个元素匹配的情况。在这种情况下,必须使用group修饰符:group_left或者group_right来确定哪一个向量具有更高的基数(充当“多”的角色).

1
2
3
4
<vector expr> <bin-op> ignoring(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> ignoring(<label list>) group_right(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_left(<label list>) <vector expr>
<vector expr> <bin-op> on(<label list>) group_right(<label list>) <vector expr>

多对一和一对多两种模式一定是出现在操作符两侧表达式返回的向量标签不一致的情况。因此需要使用ignoring和on修饰符来排除或者限定匹配的标签列表。
例如,使用表达式:

1
method_code:http_errors:rate5m / ignoring(code) group_left method:http_requests:rate5m

该表达式中,左向量method_code:http_errors:rate5m包含两个标签method和code。而右向量method:http_requests:rate5m中只包含一个标签method,因此匹配时需要使用ignoring配出匹配的标签code。 在排除匹配标签后,右向量中的元素可能匹配到多个左向量中的元素 因此该表达式的匹配模式为多对一,需要使用group修饰符group_left指定左向量是多的一边。

1
2
3
4
{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 // 21 / 120

聚合运算符(Aggregation operators)

  • sum (求和)
  • min (最小值)
  • max (最大值)
  • avg (平均值)
  • stddev (标准差)
  • stdvar (标准方差)
  • count (计数)
  • count_values (对value进行计数)
  • bottomk (样本值最小的k个元素)
  • topk (样本值最大k个元素)
  • quantile (分位数)

这些运算符汇总所有的标签维度, 不过我们可以通过without or by 来保留和剔除不同的维度标签.这些语句可以在表达式之前或者之后使用

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

或者

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

label list 是未加引号的标签列表, 其中已逗号分隔. 例如: (label1, label2)(label1, label2,) 是有效语法

without 从结果向量中删除给出的标签,而其他所有的标签保留在结果中。by 则相反, 他会丢弃没有列出的标签,只保留匹配到的标签.

parameter 目前只有 count_values, quantile, topkbottomk 需要

例如:

如果指标http_requests_total 时间序列 有application, instance, group 标签, 我们可以通过以下方法计算每个applicationgroup 在所有instance中所有可见的HTTP请求总数

1
sum without (instance) (http_requests_total)

等效于

1
sum by (application, group) (http_requests_total)

如果我们对所有维度的HTTP请求总数感兴趣, 则可以简单地编写为:

1
sum(http_requests_total)

要计算运行每个构建版本的二进制文件数量,我们可以编写为

1
count_values("version", build_version)

为了获得所有实例中最大的5个HTTP请求计数,我们可以编写:

1
topk(5, http_requests_total)

算数优先级(Binary operator precedence)

  1. ^
  2. *, /, %
  3. +, -
  4. ==, !=, <=, <, >=, >
  5. and, unless
  6. or

优先级从上到下, 如果遇到同级运算符,则默认从左到右, 比如: 2 * 3 % 2 等同于 (2*3)/2. 但是有一个例外,^ 是右关联,如: 2 ^ 3 ^ 2 等同于 2 ^ (3 ^ 2)

方法(function)

参考官方文档: https://prometheus.io/docs/prometheus/latest/querying/functions/

告警模板

Prometheus中的告警规则允许你基于PromQL表达式定义告警触发条件,Prometheus后端对这些触发规则进行周期性计算,当满足触发条件后则会触发告警通知。默认情况下,用户可以通过Prometheus的Web界面查看这些告警规则以及告警的触发状态。当Promthues与Alertmanager关联之后,可以将告警发送到外部服务如Alertmanager中并通过Alertmanager可以对这些告警进行进一步的处理。

定义告警规则

1
2
3
4
5
6
7
8
9
10
11
groups:
- name: example
rules:
- alert: TargetDown
expr: 100 * (count by(job, namespace, service) (up == 0) / count by(job, namespace, service) (up)) > 10
for: 10s
labels:
severity: warning
annotations:
message: value: {{ $value }}
label: {{ $labels }}

在告警规则文件中,我们可以将一组相关的规则设置定义在一个group下。在每一个group中我们可以定义多个告警规则(rule)。一条告警规则主要由以下几部分组成:

  • name: 定义告警规则在哪个组,一般用于区分不同功能的告警规则
  • alert: 告警规则的名称
  • expr: 基于PromQL表达式告警触发条件,用于计算是否有时间序列满足该条件。
  • for: 评估等待时间,可选参数。用于表示只有当触发条件持续一段时间后才发送告警。在等待期间新产生告警的状态为pending。
  • labels: 自定义标签,允许用户指定要附加到告警上的一组附加标签。
  • annotations: 用于指定一组附加信息,比如用于描述告警详细信息的文字等,annotations的内容在告警产生时会一同作为参数发送到Alertmanager。

为了能够让Prometheus能够启用定义的告警规则,我们需要在Prometheus全局配置文件中通过rule_files指定一组告警规则文件的访问路径,Prometheus启动后会自动扫描这些路径下规则文件中定义的内容,并且根据这些规则计算是否向外部发送通知:

1
2
rule_files:
[ - <filepath_glob> ... ]

默认情况下Prometheus会每分钟对这些告警规则进行计算,如果用户想定义自己的告警计算周期,则可以通过evaluation_interval来覆盖默认的计算周期:

1
2
global:
[ evaluation_interval: <duration> | default = 1m ]

模板化

一般来说,在告警规则文件的annotations中使用summary描述告警的概要信息,description用于描述告警的详细信息。同时Alertmanager的UI也会根据这两个标签值,显示告警信息。为了让告警信息具有更好的可读性,Prometheus支持模板化label和annotations的中标签的值。
通过$labels.<labelname>变量可以访问当前告警实例中指定标签的值。$value则可以获取当前PromQL表达式计算的样本值。

1
2
3
4
# To insert a firing element's label values:
{{ $labels.<labelname> }}
# To insert the numeric expression value of the firing element:
{{ $value }}

一个表达式,计算的结果成为样本(samples),数据类型是这样的

1
2
3
4
type sample struct {
Labels map[string]string
Value float64
}

每个表达式计算出的值,是一个[]sample列表。列表中的元素(element), 是一个单独的sample, 每个sample是一个告警项

表达式样本列表一个有两个sample元素,所以触发两条告警规则.

方法(Functions)

go template除了默认的方法, prometheus还内置了额外的方法

queries

Name Arguments Returns Notes
query query string []sample Queries the database, does not support returning range vectors.
first []sample sample Equivalent to index a 0
label label, sample string Equivalent to index sample.Labels label
value sample float64 Equivalent to sample.Value
sortByLabel label, []samples []sample Sorts the samples by the given label. Is stable.
  • 使用query

query方法,可以在模板中使用表达式在数据库中查询,返回的是列表模式的[]sample

1
{{ query "100 * count by(job, namespace, service) (up == 0)" | printf "%s" }}

query返回了列表,列表中包含两个sample

  • 使用first

first将返回[]sample中的第一个元素

1
query用法: {{ query "100 * count by(job, namespace, service) (up == 0)" | first}}

  • 使用label

label 将返回sample的标签值, 他需要两个参数,一个是要查询的label一个是查询对象sample

1
message: query用法: {{ query "100 * count by(job, namespace, service) (up == 0)" | first | label "job"}}

  • 使用value

返回sample表达式的值

1
message: query用法: {{ query "100 * count by(job, namespace, service) (up == 0)" | first | value}}
  • 使用sortByLabel

通过给定标签,进行排序

1
message: query用法: {{ query "100 * count by(job, namespace, service) (up == 0)" | sortByLabel "job" | printf "%s"}}

参考

CATALOG
  1. 1. 概念
    1. 1.1. 数据模式
      1. 1.1.1. 指标名称和标签
      2. 1.1.2. Samples
    2. 1.2. Metric 类型
      1. 1.2.1. Counter(计数器)
      2. 1.2.2. Gauge(仪表盘)
      3. 1.2.3. Histogram (直方图)
      4. 1.2.4. Summary(摘要)
    3. 1.3. Jobs and instance (作业和实例)
    4. 1.4. 自动生成的标签和时间序列
  2. 2. PromQL 查询
    1. 2.1. 数据类型
    2. 2.2. 时间序列选择器(Time series Selectors)
      1. 2.2.1. 瞬时向量选择器 (Instant vector selectors)
      2. 2.2.2. 范围向量选择器 (Range Vector Selectors)
      3. 2.2.3. 持续时间(Time Durations)
      4. 2.2.4. 偏移量修改器 (Offset modifier)
    3. 2.3. 运算操作 (operator)
      1. 2.3.1. 算数运算符
      2. 2.3.2. 比较运算符
      3. 2.3.3. 逻辑/集合二元操作符
    4. 2.4. 向量匹配
      1. 2.4.1. 一对一(one-to-one)向量匹配
    5. 2.5. 聚合运算符(Aggregation operators)
    6. 2.6. 算数优先级(Binary operator precedence)
    7. 2.7. 方法(function)
  3. 3. 告警模板
    1. 3.1. 定义告警规则
    2. 3.2. 模板化
    3. 3.3. 方法(Functions)
      1. 3.3.1. queries
  4. 4. 参考