前言
Prometheus(普罗米修斯)是最初在SoundCloud上构建的开源系统监视和警报工具包。自2012年成为社区开源项目,该项目拥有非常活跃的开发人员和用户社区。Prometheus 于2016年加入了 Cloud Native Computing Foundation (云原生云计算基金会,简称CNCF),成为继Kubernetes之后的第二个托管项目。
Exporter是一个采集监控数据并通过Prometheus监控规范对外提供数据的组件,能为Prometheus提供监控的接口。
Exporter将监控数据采集的端点通过HTTP服务的形式暴露给Prometheus Server,Prometheus Server通过访问该Exporter提供的Endpoint端点,即可获取到需要采集的监控数据。不同的Exporter负责不同的业务。
Prometheus # 开源的系统监控和报警框架,灵感源自Google的Borgmon监控系统
AlertManager # 处理由客户端应用程序(如Prometheus server)发送的警报。它负责将重复数据删除,分组和路由到正确的接收者集成,还负责沉默和抑制警报
Node_Exporter # 用来监控各节点的资源信息的exporter,应部署到prometheus监控的所有节点
PushGateway # 推送网关,用于接收各节点推送的数据并暴露给Prometheus server
- 官网:https://prometheus.io
- 项目托管:https://github.com/prometheus
- 文档:https://prometheus.io/docs/introduction/overview/
- prometheus各组件下载:https://prometheus.io/download/
部署安装
Centos 7 搭建Prometheus+Grafana监控系统
Prometheus 特点
作为新一代的监控框架,Prometheus 具有以下特点:
- 多维数据模型:由度量名称和键值对标识的时间序列数据
- PromSQL:一种灵活的查询语言,可以利用多维数据完成复杂的查询
- 不依赖分布式存储,单个服务器节点可直接工作
- 基于HTTP的pull方式采集时间序列数据
- 推送时间序列数据通过PushGateway组件支持
- 通过服务发现或静态配置发现目标
- 多种图形模式及仪表盘支持(grafana)
- 适用于以机器为中心的监控以及高度动态面向服务架构的监控
Prometheus 组织架构
Prometheus 由多个组件组成,但是其中许多组件是可选的:
- Prometheus Server:用于收集指标和存储时间序列数据,并提供查询接口
- client Library:客户端库(例如Go,Python,Java等),为需要监控的服务产生相应的/metrics并暴露给Prometheus Server。目前已经有很多的软件原生就支持Prometheus,提供/metrics,可以直接使用。对于像操作系统已经不提供/metrics,可以使用exporter,或者自己开发exporter来提供/metrics服务。
- push gateway:主要用于临时性的 jobs。由于这类 jobs 存在时间较短,可能在 Prometheus 来 pull 之前就消失了。对此Jobs定时将指标push到pushgateway,再由Prometheus Server从Pushgateway上pull。
这种方式主要用于服务层面的 metrics:
- exporter:用于暴露已有的第三方服务的 metrics 给 Prometheus。
- alertmanager:从 Prometheus server 端接收到 alerts 后,会进行去除重复数据,分组,并路由到对收的接受方式,发出报警。常见的接收方式有:电子邮件,pagerduty,OpsGenie, webhook 等。
- Web UI:Prometheus内置一个简单的Web控制台,可以查询指标,查看配置信息或者Service Discovery等,实际工作中,查看指标或者创建仪表盘通常使用Grafana,Prometheus作为Grafana的数据源;
注:大多数 Prometheus 组件都是用 Go 编写的,因此很容易构建和部署为静态的二进制文件。
Prometheus 数据模型
Prometheus将所有数据存储为时间序列;具有相同度量名称以及标签属于同一个指标。
每个时间序列都由度量标准名称和一组键值对(也成为标签)唯一标识。
时间序列格式:
<metric name>{<label name>=<label value>, ...}
示例:
api_http_requests_total{method="POST", handler="/messages"}
度量名称{标签名=值}值
HELP 说明指标是干什么的
TYPE 指标类型,这个数据的指标类型
注:度量名通常是一英文命名清晰。标签名英文、值推荐英文。
Prometheus 指标类型
• Counter:递增的计数器
适合:API 接口请求次数,重试次数。
• Gauge:可以任意变化的数值
适合:cpu变化,类似波浪线不均匀。
• Histogram:对一段时间范围内数据进行采样,并对所有数值求和与统计数量、柱状图
适合:将web 一段时间进行分组,根据标签度量名称,统计这段时间这个度量名称有多少条。
适合:某个时间对某个度量值,分组,一段时间http相应大小,请求耗时的时间。
• Summary:与Histogram类似
PromQL介绍
https://prometheus.fuckcloudnative.io/di-san-zhang-prometheus/di-4-jie-cha-xun/basics
PromQL (Prometheus Query Language) 是 Prometheus 自己开发的数据查询 DSL 语言,语言表现力非常丰富,内置函数很多,在日常数据可视化以及rules 告警中都会使用到它。
表达式数据类型
在prometheus的表达式中,一个表达式或子表达式可以分为以下四种类型之一:
即时向量(Instant vector):一组时间序列,每个时间序列包含一个样本,所有样本共享相同的时间戳
范围向量(Range vector):一组时间序列,其中包含每个时间序列随时间变化的一系列数据点
标量(Scalar):一个简单的数字浮点值
字符串(String):一个简单的字符串值,目前未使用
运算符:
https://prometheus.fuckcloudnative.io/di-san-zhang-prometheus/di-4-jie-cha-xun/operators
Prometheus 的查询语言支持基本的逻辑运算和算术运算。对于两个瞬时向量, 匹配行为可以被改变。
prometheus 查询语句中,支持常见的各种表达式操作符:
- 算术二元运算符:
+
加法-
减法*
乘法/
除法%
模^
幂等
二元运算操作符支持 scalar/scalar(标量/标量)
、vector/scalar(向量/标量)
、和 vector/vector(向量/向量)
之间的操作。
# 例如,如果我们想根据 node_disk_bytes_written 和 node_disk_bytes_read 获取主机磁盘IO的总量:
node_disk_bytes_written + node_disk_bytes_read
# 该表达式返回结果的示例如下所示:
{device="sda",instance="localhost:9100",job="node_exporter"}=>1634967552@1518146427.807 + 864551424@1518146427.807
{device="sdb",instance="localhost:9100",job="node_exporter"}=>0@1518146427.807 + 1744384@1518146427.807
- 比较(布尔)运算符:
==
(相等)!=
(不相等)>
(大于)<
(小于)>=
(大于等于)<=
(小于等于)
布尔运算符被应用于 scalar/scalar(标量/标量)
、vector/scalar(向量/标量)
,和vector/vector(向量/向量)
。默认情况下布尔运算符只会根据时间序列中样本的值,对时间序列进行过滤。我们可以通过在运算符后面使用 bool
修饰符来改变布尔运算的默认行为。使用 bool 修改符后,布尔运算不会对时间序列进行过滤,而是直接依次瞬时向量中的各个样本数据与标量的比较结果 0
或者 1
。
在两个标量之间进行布尔运算,必须提供 bool 修饰符,得到的结果也是标量,即 0
(false
)或 1
(true
)。例如:
2 > bool 1 # 结果为 1
瞬时向量和标量之间的布尔运算,这个运算符会应用到某个当前时刻的每个时序数据上,如果一个时序数据的样本值与这个标量比较的结果是 false
,则这个时序数据被丢弃掉,如果是 true
, 则这个时序数据被保留在结果中。如果提供了 bool 修饰符,那么比较结果是 0
的时序数据被丢弃掉,而比较结果是 1
的时序数据被保留。例如:
# 表示 prometheus_http_requests_total 结果中大于 100 的数据
prometheus_http_requests_total > 100 # 结果为 true 或 false
http_requests_total > bool 100 # 结果为 1 或 0
瞬时向量与瞬时向量直接进行布尔运算时,同样遵循默认的匹配模式:依次找到与左边向量元素匹配(标签完全一致)的右边向量元素进行相应的操作,如果没找到匹配元素,或者计算结果为 false,则直接丢弃。如果匹配上了,则将左边向量的度量指标和标签的样本数据写入瞬时向量。如果提供了 bool 修饰符,那么比较结果是 0
的时序数据被丢弃掉,而比较结果是 1
的时序数据(只保留左边向量)被保留。
- 逻辑(集合)运算符:
使用瞬时向量表达式能够获取到一个包含多个时间序列的集合,我们称为瞬时向量。 通过集合运算,可以在两个瞬时向量与瞬时向量之间进行相应的集合操作。目前,Prometheus 支持以下集合运算符:
and
(并且)or
(或者)unless
(排除)
vector1 and vector2 会产生一个由 vector1
的元素组成的新的向量。该向量包含 vector1 中完全匹配 vector2
中的元素组成。
vector1 or vector2 会产生一个新的向量,该向量包含 vector1
中所有的样本数据,以及 vector2
中没有与 vector1
匹配到的样本数据。
vector1 unless vector2 会产生一个新的向量,新向量中的元素由 vector1
中没有与 vector2
匹配的元素组成。
# 表示 prometheus_http_requests_total 结果中等于 5 或者 2 的数据
prometheus_http_requests_total == 5 or prometheus_http_requests_total == 2
- 聚合运算符:sum、min、max、avg、stddev、stdvar、count、count_values、bottomk、topk、quantile
Prometheus 还提供了下列内置的聚合操作符,这些操作符作用域瞬时向量。可以将瞬时表达式返回的样本数据进行聚合,形成一个具有较少样本值的新的时间序列。
sum
(求和)min
(最小值)max
(最大值)avg
(平均值)stddev
(标准差)stdvar
(标准差异)count
(计数)count_values
(对 value 进行计数)bottomk
(样本值最小的 k 个元素)topk
(样本值最大的k个元素)quantile
(分布统计)
这些操作符被用于聚合所有标签维度,或者通过 without
或者 by
子语句来保留不同的维度。
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
其中只有 count_values
, quantile
, topk
, bottomk
支持参数(parameter)。
without
用于从计算结果中移除列举的标签,而保留其它标签。by
则正好相反,结果向量中只保留列出的标签,其余标签则移除。通过 without 和 by 可以按照样本的问题对数据进行聚合。
# prometheus_http_requests_total 结果中最大的数据
max(prometheus_http_requests_total) 表示
- 二元运算符优先级
(^)> (*, /, %) > (+, -) > (==, !=, <=, <, >=, >) > (and, unless) > (or) 的原则。
在 Prometheus 系统中,二元运算符优先级从高到低的顺序为:
^
*
,/
,%
+
,-
==
,!=
,<=
,<
,>=
,>
and
,unless
or
具有相同优先级的运算符是满足结合律的(左结合)。例如,2 * 3 % 2
等价于 (2 * 3) % 2
。运算符 ^
例外,^
满足的是右结合,例如,2 ^ 3 ^ 2
等价于 2 ^ (3 ^ 2)
。
查询条件:
prometheus 存储的是时序数据,而它的时序是由metric名称和一组标签构成的,其实metric名称也可以写出标签的形式,例如prometheus_http_requests_total等价于{name="prometheus_http_requests_total"}。
例如:选择指标名称为 prometheus_http_requests_total
的所有时间序列:
prometheus_http_requests_total
可以通过向花括号({}
)里附加一组标签来进一步过滤时间序列。
例如:一个简单的查询相当于是对各种标签的筛选:
prometheus_http_requests_total{code="200"}
# 查询metric名称为prometheus_http_requests_total,code 为 "200" 的数据
时间序列过滤器
- 瞬时向量过滤器
瞬时向量过滤器允许在指定的时间戳内选择一组时间序列和每个时间序列的单个样本值。在最简单的形式中,近指定指标(metric)名称。这将生成包含此指标名称的所有时间序列的元素的瞬时向量。
例如:选择指标名称为 http_requests_total
,job
标签值为 prometheus
,group
标签值为 canary
的时间序列:
http_requests_total{job="prometheus",group="canary"}
PromQL 还支持用户根据时间序列的标签匹配模式来对时间序列进行过滤,目前主要支持两种匹配模式:完全匹配和正则匹配。
总共有以下几种标签匹配运算符:
= : 选择与提供的字符串完全相同的标签。
!= : 选择与提供的字符串不相同的标签。
=~ : 选择正则表达式与提供的字符串(或子字符串)相匹配的标签。
!~ : 选择正则表达式与提供的字符串(或子字符串)不匹配的标签。
查询条件支持正则匹配,例如:
prometheus_http_requests_total{code!="200"} #表示查询 code 不为 "200" 的数据
prometheus_http_requests_total{code=~"2.."} #表示查询 code 为 "2xx" 的数据
prometheus_http_requests_total{code!~"2.."} #表示查询 code 不为 "2xx" 的数据
示例:
# 选择指标名称为 http_requests_total,环境为 staging、testing 或 development,HTTP 方法为 GET 的时间序列
http_requests_total{environment=~"staging|testing|development",method!="GET"}
所有的 PromQL 表达式必须至少包含一个指标名称,或者一个不会匹配到空字符串的标签过滤器。
以下表达式是非法的(因为会匹配到空字符串):
{job=~".*"} # 非法!
以下表达式是合法的:
{job=~".+"} # 合法!
{job=~".*",method="get"} # 合法!
除了使用 <metric name>{label=value}
的形式以外,我们还可以使用内置的 __name__
标签来指定监控指标名称。例如:表达式 http_requests_total
等效于 {__name__="http_requests_total"}
。也可以使用除 =
之外的过滤器(=
,=~
,~
)。以下表达式选择指标名称以 job:
开头的所有指标:
{__name__=~"job:.*"}
Prometheus 中的所有正则表达式都使用 RE2语法。
区间向量过滤器
区间向量与瞬时向量的工作方式类似,唯一的差异在于在区间向量表达式中我们需要定义时间选择的范围,时间范围通过时间范围选择器 []
进行定义,以指定应为每个返回的区间向量样本值中提取多长的时间范围。
时间范围通过数字来表示,单位可以使用以下其中之一的时间单位:
s
- 秒m
- 分钟h
- 小时d
- 天w
- 周y
- 年
例如:选择在过去 5 分钟内指标名称为 http_requests_total
,job
标签值为 prometheus
的所有时间序列:
http_requests_total{job="prometheus"}[5m]
时间位移操作
在瞬时向量表达式或者区间向量表达式中,都是以当前时间为基准:
http_request_total{} # 瞬时向量表达式,选择当前最新的数据
http_request_total{}[5m] # 区间向量表达式,选择以当前时间为基准,5分钟内的数据
而如果我们想查询,5 分钟前的瞬时样本数据,或昨天一天的区间内的样本数据呢? 这个时候我们就可以使用位移操作,位移操作的关键字为 offset
。
例如,以下表达式返回相对于当前查询时间过去 5 分钟的 http_requests_total
值:
http_requests_total offset 5m
注意:offset
关键字需要紧跟在选择器({}
)后面。以下表达式是正确的:
sum(http_requests_total{method="GET"} offset 5m) // GOOD.
下面的表达式是不合法的:
sum(http_requests_total{method="GET"}) offset 5m // INVALID.
该操作同样适用于区间向量。以下表达式返回指标 http_requests_total
一周前的 5 分钟之内的 HTTP 请求量的增长率:
rate(http_requests_total[5m] offset 1w)
内置函数
prometheus 内置不少函数,方便查询以及数据格式化
常用内置函数:
abs(v instant-vector) # 返回所有样本值均转换为绝对值的输入即时向量v
absent(v instant-vector) # 如果传递给它的即时向量v有任何元素,则返回一个空向量;如果传递给它的即时向量v没有元素,则返回值为1的单元素向量
absent_over_time(v range-vector) # 如果传递给它的范围向量v有任何元素,则返回一个空向量;如果传递给它的范围向量v没有元素,则返回值为1的单元素向量
avg_over_time(range-vector) # 指定时间间隔内范围向量所有元素样本值的平均值
ceil(v instant-vector) # 将即时向量v中所有元素的样本值向上取整到最接近的整数
changes(v range-vector) # 对于范围向量v中的时间序列,返回其值在提供的时间范围内变化的次数作为一个即时向量
clamp_max(v instant-vector, max scalar) # 将即时向量v中所有元素的样本值锁定上限为标量max
clamp_min(v instant-vector, min scalar) # 将即时向量v中所有元素的样本值锁定下限为标量min
count_over_time(range-vector) # 指定时间间隔内范围向量所有元素样本值的计数
day_of_month(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的月份。返回值是1到31
day_of_week(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的星期几。返回值是从0到6,其中0表示星期日
days_in_month(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的月份中的天数。返回值是28到31
delta(v range-vector) # 计算范围向量v中每个时间序列元素的第一个值与最后一个值之间的差,并返回具有给定增量和相同标签的即时向量。delta 应仅与Gauge一起使用
deriv(v range-vector) # 使用简单的线性回归来计算范围向量v中时间序列的每秒导数。deriv 应仅与Gauge一起使用
exp(v instant-vector) # 计算即时向量v中的所有元素的指数函数。特殊情况是:Exp(+Inf) = +Inf、Exp(NaN) = NaN
floor(v instant-vector) # 将即时向量v中所有元素的样本值向下取整到最接近的整数
hour(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的一天中的小时。返回值是从0到23
idelta(v range-vector) # 计算范围向量v中最后两个样本之间的差,并返回具有给定增量和相同标签的即时向量。idelta 应仅与Gauge一起使用
increase(v range-vector) # 计算范围向量v中时间序列的增加。单调性中断(例如由于目标重新启动而导致的计数器重置)会自动进行调整。increase 应仅与Counter一起使用
irate(v range-vector) # 计算范围向量v中时间序列的每秒瞬时增加率。单调性中断(例如由于目标重新启动而导致的计数器重置)会自动进行调整
label_join(v instant-vector, dst_label string, separator string, src_label_1 string, src_label_2 string, ...) # 对于即时向量v中的每个时间序列,使用分隔符separator将所有源标签src_labels的值连接在一起,并返回带有标签值的目的标签dst_label的时间序列。src_labels可以有任意多个
label_replace(v instant-vector, dst_label string, replacement string, src_label string, regex string) # 对于即时向量v中的每个时间序列,使用正则表达式regex匹配标签 src_label。如果匹配,则返回时间序列,并将标签dst_label替换为replacement的扩展。$1用第一个匹配的子组替换,$2再用第二个匹配的子组替换。如果正则表达式不匹配,则时间序列不变
max_over_time(range-vector) # 指定时间间隔内范围向量所有元素样本值的最大值
min_over_time(range-vector) # 指定时间间隔内范围向量所有元素样本值的最小值
minute(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的小时分钟。返回值是从0到59
month(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的一年中的月份。返回值是从1到12,其中1表示一月
rate(v range-vector) # 计算范围向量v中时间序列的每秒平均增长率。单调性中断(例如由于目标重新启动而导致的计数器重置)会自动进行调整
resets(v range-vector) # 对于范围向量v中的每个时间序列,将提供的时间范围内的计数器重置次数作为即时向量返回,两个连续样本之间值的任何下降都被视为计数器重置。resets 应仅与Counter一起使用
round(v instant-vector, to_nearest=1 scalar) # 将即时向量v中所有元素的样本值四舍五入为最接近的整数
scalar(v instant-vector) # 给定一个单元素即时向量v,返回该单个元素的样本值作为标量。如果即时向量v不是单元素向量,scalar则将返回NaN
sort(v instant-vector) # 将即时向量v中元素的样本值升序排列
sort_desc(v instant-vector) # 与sort相同,但以降序排列
sum_over_time(range-vector) # 指定时间间隔内范围向量所有元素样本值的总和
time() # 返回自1970年1月1日UTC以来的秒数
timestamp(v instant-vector) # 返回即时向量v的每个样本的时间戳,作为自1970年1月1日UTC以来的秒数
vector(s scalar) # 返回标量s作为不带标签的向量
year(v=vector(time()) instant-vector) # 返回UTC中每个给定时间的年份
示例:
# 将结果由浮点数转为整数的 floor 和 ceil,
floor(avg(prometheus_http_requests_total{code="200"}))
ceil(avg(prometheus_http_requests_total{code="200"}))
# 查看 prometheus_http_requests_total 5分钟内平均每秒的数据,
rate(prometheus_http_requests_total[5m])
# 返回metric名称是http_requests_total的所有时间序列
http_requests_total
# 返回所有metric名称是http_requests_total、job是apiserver、handler是/api/comments的时间序列
http_requests_total{job="apiserver", handler="/api/comments"}
# 返回5分钟内metric名称是http_requests_total、job是apiserver、handler是/api/comments的时间序列
http_requests_total{job="apiserver", handler="/api/comments"}[5m]
# 返回所有metric名称是http_requests_total、job以server结尾的时间序列
http_requests_total{job=~".*server"}
# 返回所有metric名称是http_requests_total、status不是4xx的时间序列
http_requests_total{status!~"4.."}
# 返回过去30分钟内metric名称是http_requests_total时间序列的5分钟速率,分辨率为1分钟
rate(http_requests_total[5m])[30m:1m]
# 返回所有metric名称是http_requests_total时间序列的每秒速率,以最近5分钟为单位
rate(http_requests_total[5m])
# 返回每个实例中未使用的内存,以MiB为单位
(instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024
# 根据app和proc求和,返回每个实例中未使用的内存的总和,以MiB为单位
sum by (app, proc) (instance_memory_limit_bytes - instance_memory_usage_bytes) / 1024 / 1024