监控一直是我们在开发过程中老生常谈的话题,大多数开发认为监控是运维的工作,我们只需要关注业务逻辑,打印对应的日志,增加上报数据,或者是用一些轻量的开源监控即可,是这样么?
其实随着云原生的架构演进,微服务化越来越常见,同时底层框架越来越复杂,开发是越来越简单,但是监控却越来越复杂。
(1)往往业务逻辑就是由各个模块调用聚合组成,这个时候作为开发可能做的更多的监控各个模块中各种指标,增加监控的埋点,能在出现异常的情况下迅速定位模块的问题;
(2)传统的监控方式已经很难面对云上的大规模集群,和适配云上各种业务场景;
因此本文主要介绍云原生使用较多的指标曲线和监控工具 Prometheus
,包括架构,配置,PromQL,一些进阶知识和问题。
1、监控方法论
1.1 MDD思想
MDD(Monitor-Driven Development)是主张以指标驱动软件迭代的开发方式,它强调在软件开发过程中,监控和度量是至关重要的,MDD思想在监控中的应用主要体现在以下几个方面:
- 监控需求的定义:在软件开发过程中,开发人员需要定义监控需求,明确需要监控的指标和阈值,这些需求应该与业务需求和用户需求紧密关联,以确保监控能够及时发现并解决问题;
- 监控数据的采集和处理:在MDD中,开发人员需要采集和处理监控数据,以便及时发现异常情况,监控数据可以来自于系统日志、性能指标、用户行为等多个方面,开发人员需要通过数据分析和处理,找到问题的根本原因,并及时采取措施;
- 监控系统的自动化:在MDD中,监控系统应该是自动化的,能够自动采集和处理监控数据,并及时发送警报,开发人员需要通过自动化工具和技术,实现监控系统的自动化,从而提高监控效率和准确性;
- 监控结果的反馈和改进:在MDD中,开发人员需要不断反馈监控结果,及时发现和解决问题,并不断改进监控系统,监控结果的反馈可以来自于用户反馈、系统日志、性能指标等多个方面,开发人员需要对监控结果进行分析和处理,以便不断改进监控系统,提高软件质量和稳定性;
优点:
监控是软件开发的核心,开发人员通过监控系统的运行状态,就能及时发现并解决问题,从而提高软件的质量和稳定性,我个人觉得如果你是架构师,使用MDD开发方式能在后期系统维护上做到事半功倍的效果。
1.2 Google的黄金指标
SRE
读过《SRE Google运维解密》的书应该了解Google的四大黄金法则,其指标如下:
延迟:(Latency)服务请求所需的耗时,例如HTTP延时分位值,当然这里需要区分成功请求和失败请求;
流量:(Traffic)衡量服务容量需求,比如QPS,TPS等;
错误:(Errors)请求失败QPS,用于衡量错误发生的情况,比如HTTP的5XX错误;
饱和度:(Saturation)衡量资源的使用情况,比如内存,CPU,I/O,磁盘使用情况等;
优点:
通过监控以上指标能总体反应系统的健康状态,并结合时序数据能预测系统什么时候能达到瓶颈。
1.3 USE方法
USE方法(Utilization, Saturation, and Errors Method)是一种系统性能分析方法,用于评估计算机系统的瓶颈和资源利用率,这种方法是由Brendan Gregg提出的,它的基本思想是通过检查系统的资源利用率(Utilization)、饱和度(Saturation)和错误(Errors)来找出系统性能的问题。
使用率:关注系统资源的使用情况,这里主要包括CPU,内存,网络,磁盘等;
饱和度:资源使用排队的平均状态,和Google的饱和度有差别,以CPU为例指CPU的平均运行排队长度;
错误:错误数,和Google的错误是一样,衡量错误情况;
USE方法的主要目标是确定系统中存在的资源瓶颈,从而帮助优化系统性能,以下是使用USE方法的一般步骤:
- 为每个资源定义利用率、饱和度和错误指标。这些资源可能包括CPU、内存、磁盘、网络等;
- 收集和监控这些指标的数据;
- 分析数据,找出存在问题的资源;
- 优化问题资源,提高系统性;
优点:
通过USE方法是一种有效的系统性能分析方法,可以帮助我们找出系统中的资源瓶颈,并提供改进的方向。
1.3 RED方法
RED方法是Weave Cloud基于Google的四大黄金法则结合Prometheus和Kubernetes容器实践得出来的方法论,适应对于云原生应用和微服务框架的应用监控和度量。
Rate:每秒接收的请求数;
Errors:每秒失败的请求数; Duration:每个请求花费的时间;
2、时序数据库
2.1 什么是时序数据库?
时序数据是随时间不断产生的一系列数据,简单来说,就是带时间戳的数据,时序数据库 (Time Series Database,TSDB) 是优化用于摄取、处理和存储时间戳数据的数据库,对比其他SQL和NoSQL ,其中时序数据库是专门用于存储和处理时间序列数据的数据库,支持时序数据高效读写、高压缩存储、插值和聚合等功能。
2.2 有哪些时序数据库?
时序数据库的发展趋势,可以从DB-engines获得,具体时序数据库列表:db-engines.com/en/ranking/…
时序数据库
可以看到 InfluxDB
,Prometheus
等都是比较常见时序数据库,其用于监控领域比较常见。
3、Prometheus架构
官方架构
上图是Prometheus的官方架构图,其包括如下几个部分:
- Prometheus Server:Prometheus的核心模块,主要是采集和存储各个时序指标数据,并提供查询的功能,其中存储包括本地存储和远程存储
- Pushgateway:通常Prometheus采用拉取模式,但是也提供推模式,可以推送数据到pushgateway来实现和拉取数据一样的效果,比如一些定时任务等临时作业
- Exporter:Exporter是Prometheus的监控对象,主要是收集数据,比如golang程序中导出的
/metrics
数据,官方也提供很多类型的Exporter,比如CPU
,内存等采集 - Service Discovery:在云原生监控中,找到对应的Exporter不可能手动配置,所以Prometheus提供了服务发现机制来采集Exporter节点,支持DNS,Consule,文件和k8s API等方式
- Dashboard:Prometheus提供的Web UI,虽然通常与Grafana组合使用,但是独立的Web UI可以方便快速查询和展示图标
- Alertmanager:独立于Prometheus的告警组件,是独立的服务部署,Alertmanager主要接收Prometheus推送过来的告警,用于管理、整合和分发告警信息,除了这些还提供分组,抑制和静默等告警特性
4、Prometheus实践
这里为了方便读者使用和实验,提供一个 docker-compose
文件,通过 docker-compose up
可以拉起 prometheus
和 alertmanager
:
version: '3'
services:
prometheus:
image: prom/prometheus:v2.30.3
ports:
- 9090:9090
volumes:
- ./prometheus:/etc/prometheus
- prometheus-data:/prometheus
command: --web.enable-lifecycle --config.file=/etc/prometheus/prometheus.yml
alertmanager:
image: prom/alertmanager:v0.23.0
restart: unless-stopped
ports:
- 9093:9093
volumes:
- ./alertmanager:/config
- alertmanager-data:/data
command: --config.file=/config/alertmanager.yml --log.level=debug
volumes:
prometheus-data:
alertmanager-data:
打开浏览器访问:http://{你的IP}:9090
就可以打开页面,可以查询数据如下:
Prometheus
打开浏览器访问:http://{你的IP}:9093
可以看到 alertmanager
页面:
Alertmanager
5、PromQL语法
5.1 基础指标
Prometheus 定义了 4 种不同的指标类型:Counter(计数器)、Gauge(仪表盘)、Histogram(直方图)、Summary(摘要):
- Counter(计数器):Counter 类型代表一种样本数据单调递增的指标,即只增不减,除非监控系统发生了重置。例如,你可以使用 counter 类型的指标来表示服务的请求数、已完成的任务数、错误发生的次数等
- Gauge(仪表盘):Gauge 类型代表一种样本数据可以任意变化的指标,即可增可减。Gauge 通常用于像温度或者内存使用率这种指标数据,也可以表示能随时增加或减少的"总数",例如:当前并发请求的数量
- Histogram(直方图):Histogram 在一段时间范围内对数据进行采样(通常是请求持续时间或响应大小等),并将其计入可配置的存储桶(bucket)中,后续可通过指定区间筛选样本,也可以统计样本总数,最后一般将数据展示为直方图
- Summary(摘要):与 Histogram 类型类似,用于表示一段时间内的数据采样结果(通常是请求持续时间或响应大小等),但它直接存储了分位数(通过客户端计算,然后展示出来),而不是通过区间来计算
其中Prometheus的基础数据格式:
<metric name>{<label name>=<label value>, ...}
例如,指标名称为 http_requests_total,标签为 method="POST" 和 handler="/messages" 的时间序列可以表示为:
http_requests_total{method="POST", handler="/messages"}
5.1 基础PromQL
Prometheus通过指标名称(metrics name)以及对应的一组标签(labelset)唯一定义一条时间序列,指标名称反映了监控样本的基本标识,而label则在这个基本特征上为采集到的数据提供了多种特征维度,用户可以基于这些特征维度过滤,聚合,统计从而产生新的计算后的一条时间序列。
- 查询某个指标数据,以
http_requests_total
为例:http_requests_total{}
- 查询某个指标数据中的某一个label,如:
http_requests_total{instance="localhost:9090"}
,查询instance
为localhost:9090
的数据 - 正则表达式(
label=~regx
),如:http_requests_total{environment=~"staging|testing|development",method!="GET"}
,查询environment
匹配并且method
不为GET
方法的数据 - 查询范围,通过区间向量表达式(
[]
),如:http_requests_total{}[5m]
查询最近5分钟内的数据,这里时间范围可以用:s(秒),m(分钟),h(小时),d(天),w(周),y(年) - 时间位移查询,使用
offset
,如:http_request_total{}[1d] offset 1d
查询昨天一天的区间内的样本数据 - 聚合函数
sum
,表示一段区间内获取数据总量,如:sum(http_request_total)
查询系统所有http请求的总量 - 聚合函数
avg
,表示一段区间内平均值,如:avg(node_cpu) by (mode)
按照mode计算主机CPU的平均使用时间
5.2 PromQL操作符
PromQL操作符包括数学运算符,逻辑运算符,布尔运算符等。
- 数学运算,支持+(加法),-(减法),*(乘法),/(除法),%(求余),^(幂运算),如:
http_requests_total / 60
- 布尔运算,支持==(相等),!=(不相等),>(大于),<(小于),>=(大于等于),<=(小于等于),如:
http_requests_total{} > 0
- 操作符包括一些聚合操作,其语法如:
<aggr-op>([parameter,] <vector expression>) [without|by (<label list>)]
without
,用于移除计算结果中的标签,如:sum(http_requests_total) without (instance)
将http_requests_total
中的instance
标签移除聚合by
,用于保留计算结果的标签,类似mysql的group,如:sum(http_requests_total) by (code,handler,job,method)
按照code,handler,job,method
这几个维度聚合count_values
,每一个唯一的样本值出现的次数,类似mysql的group与count,如:count_values("count", http_requests_total)
,计算count
值的个数topk
和bottomk
,用于对样本值进行排序,返回当前样本值前n位,或者后n位的时间序列,如:topk(5, http_requests_total)
,计算HTTP请求数前5位的时序样本数据quantile
用于计算当前样本数据值的分布情况,如:quantile(0.5, http_requests_total)
5.3 PromQL聚合操作
- sum(求和):
sum(http_requests_total)
,整个应用的HTTP请求总量 - min(最小值):
min(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)
,返回 CPU 使用率最低的实例的值 - max(最大值):
max(http_request_duration_seconds) by (instance)
,返回 HTTP 请求响应时间最长的实例的值 - avg(平均值):
avg(rate(node_cpu_seconds_total{mode="user"}[5m])) without (cpu)
,返回所有节点的 CPU 使用率平均值 - stddev(标准差):
stddev(http_request_duration_seconds)
,返回 HTTP 请求响应时间的标准差 - stdvar(标准方差):
stdvar(http_request_duration_seconds)
,返回 HTTP 请求响应时间的方差 - count(计数):
count(node_cpu_seconds_total) without (cpu, mode)
,返回所有节点的数量
5.4 PromQL内置函数
由于Prometheus的内置函数太多了,这里列举几个常用的:
- ceil():将 v 中所有元素的样本值向上四舍五入到最接近的整数,如:
node_load5{instance="192.168.1.75:9100"} # 结果为 2.79
- rate():可以直接计算区间向量 v 在时间窗口内平均增长速率,它会在单调性发生变化时(如由于采样目标重启引起的计数器复位)自动中断,如:
rate(http_requests_total[5m])
返回区间向量中每个时间序列过去 5 分钟内 HTTP 请求数的每秒增长率 - label_join():可以将时间序列 v 中多个标签 src_label 的值,通过 separator 作为连接符写入到一个新的标签 dst_label 中
- label_replace():为了能够让客户端的图标更具有可读性,可以通过 label_replace 函数为时间序列添加额外的标签
其他更多的文档可以参考:官方文档或者 https://prometheus.fuckcloudnative.io/di-san-zhang-prometheus/di-4-jie-cha-xun/functions
6、Alertmanager告警
6.1 告警架构
Alertmanager
Alertmanager
通常是接收Prometheus的数据,然后经过去重,分组,最后调用到对应的webhook,通知告警接收人。
6.2 Prometheus与Alertmanager如何关联
通常Prometheus告警流程如图:
Alertmanager
在告警中Prometheus做什么?
- Prometheus需要先定义告警规则,其中规则样例如下:
groups:
- name: example # 分组名称
rules:
- alert: alertname # 告警规则1的名称
expr: job:request_latency_seconds:mean5m{job="xxx"} > 0.5 # 基于PromQL表达式告警触发条件,用于计算是否有时间序列满足该条件
for: 10m # 评估等待时间,可选参数,用于表示只有当触发条件持续一段时间后才发送告警,在等待期间新产生告警的状态为pending
labels: # 自定义标签,允许用户指定要附加到告警上的一组附加标签
severity: page
annotations: # 用于指定一组附加信息,比如用于描述告警详细信息的文字等
summary: High request latency
description: description info
也可以定义一些批量的文件,然后再 prometheus.yml 设置 rule_files
,比如:
rule_files:
- /etc/prometheus/rules/*.rules
- 定义告警周期(也就是计算周期):
global:
[ evaluation_interval: <duration> | default = 1m ]
在告警中Alertmanager做什么?
- 定义配置文件,如下:
global:
resolve_timeout: 5m
route: # 所有的告警信息都会从配置中的顶级路由(route)进入路由树,根据路由规则将告警信息发送给相应的接收器
group_by: ['alertname'] # 分组参数
group_wait: 10s # 最初发送通知之前等待缓冲同一组告警的时间
group_interval: 10s # 发送添加到已有的告警通知组之前需要等待的时间
repeat_interval: 1h # 每隔repeat_interval收到一条告警信息
receiver: 'web.hook' # 定义发给哪一个receiver
receivers: # 定义一组接收器,比如可以按照角色(比如系统运维,数据库管理员)来划分多个接收器。接收器可以关联邮件,Slack以及其它方式接收告警信息
- name: 'web.hook'
webhook_configs:
- url: 'http://127.0.0.1:5001/'
inhibit_rules: # 设置抑制规则可以减少垃圾告警的产生
- target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
- 将Alertmanager的地址配置到
prometheus.yml
中:
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
7、Prometheus进阶知识
7.1 存储原理
Prometheus 1.x版本的TSDB(V2存储引擎)基于LevelDB,并且使用了和Facebook Gorilla一样的压缩算法,能够将16个字节的数据点压缩到平均1.37个字节。Prometheus 2.x版本引入了全新的V3存储引擎,提供了更高的写入和查询性能,Prometheus按2小时一个block进行存储,每个block由一个目录组成,目录里包含:
- 一个或者多个chunk文件(保存timeseries数据)、一个metadata文件、一个index文件(通过metric name和labels查找timeseries数据在chunk文件的位置)
- 最新写入的数据保存在内存block中,达到2小时后写入磁盘,为了防止程序崩溃导致数据丢失,实现了WAL(write-ahead-log)机制,启动时会以写入日志(WAL)的方式来实现重播,从而恢复数据
- 删除数据时,删除条目会记录在独立的tombstone文件中,而不是立即从chunk文件删除
- 2小时的block会在后台压缩成更大的block,数据压缩合并成更高level的block文件后删除低level的block文件,这个和leveldb、rocksdb等LSM树的思路一致
以上设计和Gorilla的设计高度相似,所以Prometheus几乎就是等于一个缓存TSDB,它本地存储的特点决定了它不能用于long-term数据存储,只能用于短期窗口的timeseries数据保存和查询,并且不具有高可用性,其中以下是我在线上部署Prometheus的数据目录:
存储
Prometheus如果只是存储在单机上,在面临大规模集群情况下肯定磁盘不够,所以提供了远程存储能力,可以在 prometheus.yml
配置:
remote_write:
- url: "http://localhost:9201/write"
remote_read:
- url: "http://localhost:9201/read"
7.2 Prometheus高可用架构
Prometheus官方的高可用有几种方案:
- HA:即两套 Prometheus 采集完全一样的数据,外边挂负载均衡
- HA + 远程存储:除了基础的多副本 Prometheus,还通过 Remote write 写入到远程存储,解决存储持久化问题
- 联邦集群:即 Federation,按照功能进行分区,不同的 Shard 采集不同的数据,由 Global 节点来统一存放,解决监控数据规模的问题
HA方案:
HA
多个Prometheus同时采集数据,数据会在各个机器上存储一份,如果查询发现某台机器挂了,可以通过LVS/nginx自动切换,但是存储缺点是数据不同步,无法横向扩容。
HA + 远程存储:
HA存储
多个Prometheus同时采集数据,数据会存储到远端存储上,如果查询发现某台机器挂了,可以通过LVS/nginx自动切换,只要远端存储数据不挂或者容量不大情况,这种方案架构简单,建议采用。
联邦集群:
联邦集群
联邦集群主要是针对大规模集群,Prometheus计算存储等分离方式,某几个Prometheus管理集群A数据,某几个Prometheus管理集群B数据...,然后通过多层逐步缩小集群节点,最后汇总到某个Prometheus查询大盘数据等。
7.3 Alertmanager高可用架构
解决了Prometheus的单节点问题,而Alertmanager同样面临这种情况,而Alertmanager也提供官方的解决方案:
Alertmanager高可用架构
Prometheus 可以发送告警信息到多个 Alertmanager 节点上,但是 Alertmanager 收到以后没法收敛,于是 Alertmanager 引入了Gossip机制,Gossip机制为多个 Alertmanager 之间提供了信息传递的机制,确保及时在多个Alertmanager 分别接收到相同告警信息的情况下,也只有一个告警通知被发送给 Receiver。
7.4 Thanos架构
为什么需要Thanos?
随着集群规模越来越大,监控数据的种类和数量也越来越多:如Master/Node 机器监控、进程监控、4 大核心组件的性能监控,POD 资源监控、kube-stats-metrics、K8S events监控、插件监控等等。
除了解决上面的高可用问题,更多希望基于 Prometheus 构建全局视图,主要需求有:
- 长期存储:集群很大的情况下,一般 Prometheus 一天可能产生几十G到几百G的数据不等,如果一个月就是T级别以上的数据
- 无限拓展:集群节点在大厂到数万到数十万比较正常,而且其中服务的上报随着服务增多,这个量级也是逐步增大
解决上述问题开源社区有一些解决方案,比如:Cortex/Thanos/Victoria/StackDriver等,但是Thanos相对的优势是侵入性比较小,可以快速与Prometheus结合使用,其官方架构如下:
Thanos
限于篇幅,有兴趣了解Thanos架构和源码的,可以去 github(github.com/thanos-io/t…
8、Prometheus使用过程中的问题汇总
8.1 大内存问题
随着规模变大,prometheus需要的cpu和内存都会升高,内存一般先达到瓶颈,这个时候要么加内存,要么集群分片减少单机指标,解决方案:
- sample 数量超过了 200 万,就不要单实例了,做下分片,然后通过 victoriametrics,thanos,trickster等方案合并数据
- 评估 metric 和 label 占用较多的指标,去掉没用的指标
- 查询时尽量避免大范围查询,注意时间范围和 step 的比例,慎用 group
- 如果需要关联查询,先想想能不能通过 relabel 的方式给原始数据多加个 label
8.2 慢查询问题
评估 prometheus 的整体响应时间,可以用这个默认指标:
prometheus_engine_query_duration_seconds{}
对于 prometheus 查询返回比较慢解决方案:
- 如果比较复杂且耗时的sql,可以使用record rule(预聚合)减少指标数量,并使查询效率更高
- 范围查询时,大的时间范围,step 值却很小,导致查询到的数量量过大,减少查询范围
- 通过联邦架构,将不需要的instance的数据聚合
- 去出高基数label,比如用户的userid或者来源IP等,这种可以TSDB Status中查到,命令
./tsdb analyze ../data/prometheus/
8.3 找到最大的 metric 或 job
有时候需要分析 metric 的数量,确定哪些 metric 中的 label 太多,可以通过 topk
查看:
topk(10, count by (__name__)({__name__=~".+"}))
参考
(1)yunlzheng.gitbook.io/prometheus-…
(2)github.com/prometheus-…
(3)github.com/thanos-io/t…
(4)yasongxu.gitbook.io/container-m…