一 、设计架构
监控系统的总体架构大多是类似的,都有数据采集、数据处理存储、告警动作触发和告警,以及对监控数据的展示。
下面是 Prometheus 的架构:
Prometheus Server 负责定时从 Prometheus 采集端 Pull(拉) 监控数据。Prometheus 采集端可以是实现了 /metrics 接口的服务,可以是从第三方服务导出监控数据的 exporter,也可以是存放短生命周期服务监控数据的 Pushgateway。相比大多数采用 Push(推) 监控数据的方式,Pull 使得 Promethues Server 与被采集端的耦合度更低,Prometheus Server 更容易实现水平拓展。
对于采集的监控数据,Prometheus Server 使用内置时序数据库 TSDB 进行存储。同时也会使用这些监控数据进行告警规则的计算,产生的告警将会通过 Prometheus 另一个独立的组件 Alertmanager 进行发送。Alertmanager 提供了十分灵活的告警方式,并且支持高可用部署。
对于采集到的监控数据,可以通过 Prometheus 自身提供的 Web UI 进行查询,也可以使用 Grafana 进行展示。
二、配置 Prometheus
Prometheus 的配置项是整个组件的核心,配置是声明式的,这点我觉得是蛮棒的,降低使用门槛,如果想熟练的使用 Prometheus,还是需要把配置文档好好阅读几遍,Prometheus 的官方文档质量还是蛮高的。最基础的配置如下:
global
定义 Prometheus 拉取监控数据的周期 scrape_interval 和 告警规则的计算周期 evaluation_interval。
rule_files
指定了告警规则的定义文件。
scrape_configs
scrape_configs 是 Prometheus 最为重要的配置之一,指定了 Prometheus 实例如何抓取 metrics
示例:
global:
# How frequently to scrape targets by default.
[ scrape_interval: <duration> | default = 1m ]
# How long until a scrape request times out.
[ scrape_timeout: <duration> | default = 10s ]
# How frequently to evaluate rules.
[ evaluation_interval: <duration> | default = 1m ]
# The labels to add to any time series or alerts when communicating with
# external systems (federation, remote storage, Alertmanager).
external_labels:
[ <labelname>: <labelvalue> ... ]
# Rule files specifies a list of globs. Rules and alerts are read from
# all matching files.
rule_files:
[ - <filepath_glob> ... ]
# A list of scrape configurations.
scrape_configs:
[ - <scrape_config> ... ]
# Alerting specifies settings related to the Alertmanager.
alerting:
alert_relabel_configs:
[ - <relabel_config> ... ]
alertmanagers:
[ - <alertmanager_config> ... ]
# Settings related to the remote write feature.
remote_write:
[ - <remote_write> ... ]
# Settings related to the remote read feature.
remote_read:
[ - <remote_read> ... ]
scrape_configs
# scrape_configs
# job 是 Prometheus 中最基本的调度单位,一个 job 可能会拥有多个 instances,就像一个服务可能会拥有多个实例一样。
job_name: <job_name>
# 抓取时间间隔。
[ scrape_interval: <duration> | default = <global_config.scrape_interval> ]
# 抓取超时时间。
[ scrape_timeout: <duration> | default = <global_config.scrape_timeout> ]
# 默认的 web path 是 /metrics,现在对整个社区来说也是一个约定俗成的东西,基本不会有变动。
[ metrics_path: <path> | default = /metrics ]
# 协议格式 http/https?
[ scheme: <scheme> | default = http ]
# 查询还可以带参数,但一般也没这个必要。
params:
[ <string>: [<string>, ...] ]
# tls 证书配置,不过目前如果使用的服务都是跑在 kubernetes 集群内,且无对外部暴露的话,也不用考虑 tls。
tls_config:
[ <tls_config> ]
# 代理配置。
[ proxy_url: <string> ]
# 下面的都是服务发现的配置,Prometheus 原生地提供了多种服务发现的方案。
# 这里只简单介绍 static_sd_config 和 kubernetes_sd_configs 两种。
# 目的是为了结合服务发现实现 Prometheus 的热更新,不必再手动地更新配置。
# 使用 prometheus-operator 本质上是与 kubernetes_sd_configs 相结合,只是 operator 帮我们屏蔽了这些复杂性。
# 对于其他服务发现体系的,可以到官网上查看具体的配置项。
# https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config
# kubernetes_sd_configs 实现的思路其实就是通过 Kubernetes REST API 获取对应的资源的信息。
# 包括 node/service/pod/endpoints/ingress
# kubernetes 会给每种资源注入自己的信息,当然资源本身也可以自定义一些附带信息,如 labels/annotation 等。
# 获取到的数据都以 __meta 作为前缀,在 Prometheus 中,双下划线为前缀的 metrics 是不会被暴露到外部的。
# 所以可用 relabels 来将 __meta_* metrics 转换为自己想要的形式。
kubernetes_sd_configs:
[ - <kubernetes_sd_config> ... ]
# 如果你是单机使用并且没有任何服务发现体系的话,可以用 static_configs
# <static_config>
# targets:
# targets 就是上面指的一个 job 可能会有多个 instances 的情况,列表类型。
# [ - '<host>' ]
# Labels assigned to all metrics scraped from the targets.
# labels:
# [ <labelname>: <labelvalue> ... ]
static_configs:
[ - <static_config> ... ]
# relabel 涉及到几种 action,action 指的是你可以根据正则捕获结果对 label 进行何种操作,是丢弃呢,还是改写呢?
# 具体内容可以参考下面这篇博客
# https://www.li-rui.top/2019/04/16/monitor/Prometheus%E4%B8%ADrelabel_configs%E7%9A%84%E4%BD%BF%E7%94%A8/
#
# actions 类型如下
#
# replace 根据正则匹配标签的值进行替换标签
# keep 根据正则匹配标签的值保留数据采集源
# dro 根据正则匹配标签的值剔除数据采集源
# hashmod hash 模式
# labelmap 根据正则匹配标签的名称进行映射
# labeldrop 根据正则匹配标签的名称剔除标签
# labelkeep 根据正则匹配标签的名称保留标签
relabel_configs:
[ - <relabel_config> ... ]
alertingRule
采集了 metrics 可以被告警系统使用,所以我们需要根据手上掌握的数据来定义告警规则。首先来看看官方给出的一个基础示例。:
groups:
- name: example
rules:
- alert: HighRequestLatency
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
for: 10m
labels:
severity: page
annotations:
summary: High request latency
规则模板如下
# The name of the alert. Must be a valid metric name.
# 告警规则名字。
alert: <string>
# The PromQL expression to evaluate. Every evaluation cycle this is
# evaluated at the current time, and all resultant time series become
# pending/firing alerts.
# promQL 表达式,需要符合 promQL 语法
expr: <string>
# Alerts are considered firing once they have been returned for this long.
# Alerts which have not yet fired for long enough are considered pending.
# 规则匹配的持续时间
[ for: <duration> | default = 0s ]
# Labels to add or overwrite for each alert.
# labels 用于提供附带信息,在 alertmanagers 中可以用到,可以为 golang 标准模板语法。
labels:
[ <labelname>: <tmpl_string> ]
# Annotations to add to each alert.
# Annotations 用于提供附带信息,在 alertmanagers 中可以用到,可以为 golang 标准模板语法。
annotations:
[ <labelname>: <tmpl_string> ]
recordingRule
Prometheus 提供一种记录规则(Recording Rule) 来支持后台计算的方式,可以实现对复杂查询的 PromQL 语句的性能优化,提高查询效率。记录规则的基本思想是,它允许我们基于其他时间序列创建自定义的 meta-time 序列。
在 Prometheus Operator 中已经有了大量此类规则,比如:
groups:
- name: k8s.rules
rules:
- expr: |
sum(rate(container_cpu_usage_seconds_total{image!="", container!=""}[5m])) by (namespace)
record: namespace:container_cpu_usage_seconds_total:sum_rate
- expr: |
sum(container_memory_usage_bytes{image!="", container!=""}) by (namespace)
record: namespace:container_memory_usage_bytes:sum
上面的这两个规则就完全可以执行上面我们的查询,它们会连续执行并以很小的时间序列将结果存储起来。
sum(rate(container_cpu_usage_seconds_total{job="kubelet", image!="", container_name!=""}[5m])) by (namespace)
将以预定义的时间间隔进行评估,并存储为新的指标,新指标与内存查询相同,效率更高
namespace:container_cpu_usage_seconds_total:sum_rate
同样的,我们先来看官方给出的基础示例。
groups:
- name: example
rules:
- record: job:http_inprogress_requests:sum
expr: sum(http_inprogress_requests) by (job)
rule 规则模板如下
# The name of the time series to output to. Must be a valid metric name.
record: <string>
# The PromQL expression to evaluate. Every evaluation cycle this is
# evaluated at the current time, and the result recorded as a new set of
# time series with the metric name as given by 'record'.
# promQL 表达式,需要符合 promQL 语法
expr: <string>
# Labels to add or overwrite before storing the result.
labels:
[ <labelname>: <labelvalue> ]
当一个 node_exporter 被安装和运行在被监控的服务器上后
使用简单的 curl命令 就可以看到 exporter 帮我们采集到的 metrics 数据,以 key/value 形式展现和保存。curl localhost:9100/metrics:9100 为exporter 默认端口号。
[root@node-3 ~]# curl localhost:9100/metrics | grep node_cpu
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0# HELP node_cpu_guest_seconds_total Seconds the cpus spent in guests (VMs) for each mode.
# TYPE node_cpu_guest_seconds_total counter
node_cpu_guest_seconds_total{cpu="0",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="0",mode="user"} 0
node_cpu_guest_seconds_total{cpu="1",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="1",mode="user"} 0
node_cpu_guest_seconds_total{cpu="10",mode="nice"} 0
node_cpu_guest_seconds_total{cpu="10",mode="user"} 0
node_cpu_guest_seconds_total{cpu="11",mode="nice"} 0
三、使用 Prometheus Web UI
一般的当 Prometheus 启动之后,可以在浏览器输入 http://localhost:9090 打开 Prometheus Web UI
这里我使用了 Ingress来做代理转发,并修改本地 Host 实现的。(细节还待研究)
四、采集数据格式及分类
4.1 采集数据的格式
Prometheus 使用 metric 表示监控度量指标,它由 metric name (度量指标名称)和 labels (标签对)组成:
<metric name>{<label name=<label value>, ...}
Exporter 接口数据规范格式如下:
# HELP <监控指标名称> <监控指标描述>
# TYPE <监控指标名称> <监控指标类型>
<监控指标名称>{ <标签名称>=<标签值>,<标签名称>=<标签值>...} <样本值1> <时间戳>
<监控指标名称>{ <标签名称>=<标签值>,<标签名称>=<标签值>...} <样本值2> <时间戳>
*时间戳应为采集数据的时间,是可选项,如果 Exporter 没有提供时间戳的话,Prometheus Server 会在拉取到样本数据时将时间戳设置为当前时间;
metric name 指明了监控度量指标的一般特征,比如 http_requests_total 代表收到的 http 请求的总数。metric name 必须由字母、数字、下划线或者冒号组成。冒号是保留给 recording rules 使用的,不应该被直接使用。
labels 体现了监控度量指标的维度特征,比如 http_requests_total{method=“POST”, status="200“} 代表 POST 响应结果为 200 的请求总数。Prometheus 不仅能很容易地通过增加 label 为一个 metric 增加描述维度,而且还很方便的支持数据查询时的过滤和聚合,比如需要获取所有响应为 200 的请求的总数时,只需要指定 http_request_total{status=“200”}。
Prometheus 将 metric 随时间流逝产生的一系列值称之为 time series(时间序列)。某个确定的时间点的数据被称为 sample(样本),它由一个 float64 的浮点值和以毫秒为单位的时间戳组成。
4.2 采集数据的分类
在了解过 Prometheus 采集数据的格式之后,我们来了解一下它的分类。Prometheus 将采集的数据分为 Counter、Gauge、Histogram、Summary 四种类型。
需要注意的是,这只是一种逻辑分类,Prometheus 内部并没有使用采集的数据的类型信息,而是将它们做为无类型的数据进行处理。这在未来可能会改变。
下面,我们将具体介绍着四种类型。
Counter
Counter 是计数器类型,适合单调递增的场景,比如请求的总数、完成的任务总数、出现的错误总数等。它拥有很好的不相关性,不会因为重启而重置为 0。
Gauge
Gauge 用来表示可增可减的值,比如 CPU 和内存的使用量、IO 大小等。
Histogram
Histogram 是一种累积直方图,它通常用来描述监控项的长尾效应。
举个例子:
假设使用 Hitogram 来分析 API 调用的响应时间,使用数组 [30ms, 100ms, 300ms, 1s, 3s, 5s, 10s] 将响应时间分为 8 个区间。那么每次采集到响应时间,比如 200ms,那么对应的区间 (0, 30ms], (30ms, 100ms], (100ms, 300ms] 的计数都会加 1。最终以响应时间为横坐标,每个区间的计数值为纵坐标,就能得到 API 调用响应时间的累积直方图。
Summary
Summary 和 Histogram 类似,它记录的是监控项的分位数。什么是分位数?举个例子:假设对于一个 http 请求调用了 100 次,得到 100 个响应时间值。将这 100 个时间响应值按照从小到大的顺序排列,那么 0.9 分位数(90% 位置)就代表着第 90 个数。
通过 Histogram 可以近似的计算出百分位数,但是结果并不准确,而 Summary 是在客户端计算的,比 Histogram 更准确。不过,Summary 计算消耗的资源更多,并且计算的指标不能再获取平均数或者关联其他指标,所以它通常独立使用。
4.3 数据采集流程
target:采集目标,Prometheus Server 会从这些目标设备上采集监控数据
sample: Prometheus Server 从 targets 采集回来的数据样本
meta label: 执行 relabel 前,target 的原始标签。可在 Prometheus 的 /targets 页面或发送 GET /api/v1/targets 请求查看。
4.3.1 relabel (targets 标签修改/过滤)
relabel 是 Prometheus 提供的一个针对 target 的功能,relabel 发生 Prometheus Server 从 target 采集数据之前,可以对 target 的标签进行修改或者使用标签进行 target 筛选。注意以下几点:
Prometheus 在 relabel 步骤默认会为 target 新增一个名为 instance 的标签,并设置成 “address” 标签的值;(下图instance标签值为address标签值)
在 relabel 结束后,以 “__” 开头的标签不会被存储到磁盘;
meta label 会一直保留在内存中,直到 target 被移除。
relabel 配置
relabel 的基本配置项:
source_labels: [, …] #需要进行 relabel 操作的 meta labels
target_label: #relabel 操作的目标标签,当使用 action 为 “replace” 时会把替换的结果写入 target_label
regex: #正则表达式,用于在 source_labels 的标签值中提取匹配的内容。默认为"(.*)"
modulus: #用于获取源标签值的哈希的模数
replacement: #regex 可能匹配到多个内容,replacement 指定要使用哪一个匹配内容进行替换,默认为 “$1”,表示使用第一个匹配的内容
action: <relabel_action> #定义对 source_labels 进行何种操作,默认为 “replace”
scrape_configs:
- job_name: prometheus
relabel_configs:
- source_labels: ["__address__"] #我们要替换的 meta label 为"__address__"
target_label: "host" #给 targets 新增一个名为 "host" 的标签
regex: "(.*):(.*)" #将匹配的内容分为两部分 groups--> (host):(port)
replacement: $1 #将匹配的 host 第一个内容设置为新标签的值
action: replace
五 、Recording rules案例分析
recording rules作用
recording rules 是提前设置好一个比较花费大量时间运算或经常运算的表达式,其结果保存成一组新的时间序列数据。当需要查询的时候直接会返回已经计算好的结果,这样会比直接查询快,也减轻了PromQl的计算压力,同时对可视化查询的时候也很有用,可视化展示每次只需要刷新重复查询相同的表达式即可。
配置示范如下:
- expr: node_cpu_seconds_total * on (node) group_left(type) ecms_node_type * on (node) group_left(label_cloud_product) kube_node_labels
record: ecms_node_cpu_seconds_total
- expr: avg by (mode,node_name)(irate(ecms_node_cpu_seconds_total{label_cloud_product=""}[5m]) OR irate(ecms_node_cpu_seconds_total{label_cloud_product="enabled",type="Physical"}[5m])) * 100
record: ecms_node_cpu_utilization
ecms_node_cpu_seconds_total:
expr_rules 为:
node_cpu_seconds_total * on (node) group_left(type) ecms_node_type * on (node) group_left(label_cloud_product) kube_node_labels
将这个expr拆分为三块:
1.node_cpu_seconds_total
2.on (node) group_left(type) ecms_node_type
3.on (node) group_left(label_cloud_product) kube_node_labels
在node_cpu_seconds_total侧包括ecms_node_type的type标签
其中:on表示指定要参与匹配的标签,ignoring表示忽视不参与匹配的标签
表示kube_node_labels,ecms_node_type,node_cpu_seconds_total 这三个metric用仅用node标签匹配,其他标签不参与。根据node的匹配度,然后把label_cloud_product和type标签,添加到node_cpu_seconds_total中。这边的group_left的有点类似sql中的left join的意思。即node_cpu_seconds_total中如存在node标签,而在ecms_node_type和kube_node_labels不存在的话,这个sample是要匹配上的,而不是废弃。
这边的SQL语法可以学习下 PromQL 语法
ecms_node_cpu_utilization:
这个较为简单,表示ecms_node_cpu_seconds_total这个metric的标签为label_cloud_product为空的sample,或者是标签为label_cloud_product=enable,type="Physical"的sample5分钟采样的irate算法,最后按照mode,node_name求平均值
irate算法
irate 适用于变化频率高的 counter 类型数据,计算范围向量中时间序列的每秒平均增长率。基于最后两个数据点。当 counter 出现单调性中断会自动进行调整,与 rate 不同的是,irate 只会选取时间范围内最近的两个点计算,当选定的时间范围内仅包含两个数据点时,不考虑外推情况,rate 和 irate 并无明显区别。
group_left用法:
Many-to-one / one-to-many 向量匹配
这种匹配模式下,某一边会有多个元素跟另一边的元素匹配。这时就需要使用 group_left 或 group_right 组修饰符来指明哪边匹配元素较多,左边多则用 group_left,右边多则用 group_right。其语法如下:
<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>
参考:
https://prometheus.io/docs/prometheus/latest/configuration/recording_rules/
https://prometheus.io/docs/prometheus/latest/querying/functions/
https://blog.csdn.net/ActionTech/article/details/105786785
https://github.com/chenjiandongx/prometheus101