云原生二十篇|详解Prometheus

2023年 12月 25日 110.3k 0

监控一直是我们在开发过程中老生常谈的话题,大多数开发认为监控是运维的工作,我们只需要关注业务逻辑,打印对应的日志,增加上报数据,或者是用一些轻量的开源监控即可,是这样么?
其实随着云原生的架构演进,微服务化越来越常见,同时底层框架越来越复杂,开发是越来越简单,但是监控却越来越复杂。

(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/…

图片

时序数据库

可以看到 InfluxDBPrometheus等都是比较常见时序数据库,其用于监控领域比较常见。

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…

相关文章

KubeSphere 部署向量数据库 Milvus 实战指南
探索 Kubernetes 持久化存储之 Longhorn 初窥门径
征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
那些年在 Terraform 上吃到的糖和踩过的坑
无需 Kubernetes 测试 Kubernetes 网络实现
Kubernetes v1.31 中的移除和主要变更

发布评论