首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

高可用 Prometheus 的常见问题

监控系统的历史悠久,是一个很成熟的方向,而 Prometheus 作为新生代的开源监控系统,慢慢成为了云原生体系的事实标准,也证明了其设计很受欢迎。本文主要分享在 prometheus 实践中遇到的一些问题和思考

几点原则

监控是基础设施,目的是为了解决问题,不要只朝着大而全去做,尤其是不必要的指标采集,浪费人力和存储资源(To B 商业产品例外)

需要处理的告警才发出来,发出来的告警必须得到处理

简单的架构就是最好的架构,业务系统都挂了,监控也不能挂,Google SRE 里面也说避免使用 magic 系统,例如机器学习报警阈值、自动修复之类。这一点见仁见智吧,感觉很多公司都在搞智能 AI 运维

prometheus 的局限

prometheus 是基于 metric 的监控,不适用于日志(logs)、事件(event)、调用链(tracing)

prometheus 默认是 pull 模型,合理规划你的网络,尽量不用 pushgateway 转发

对于集群化、水平扩展,官方和社区都没有银弹,合理选择 federate、cortex、thanos

监控系统一般 可用性>一致性,这个后面说 thanos 的时候会提到

合理选择黄金指标

我们应该关注哪些指标?Google 在“SRE Handbook”中提出了“四个黄金信号”:延迟、流量、错误数、饱和度。实际操作中可以使用 USE 或 RED 方法作为指导,USE 用于资源,RED 用于服务

USE 方法:Utilization、Saturation、Errors

RED 方法:Rate、Errors、Duration

对 USE 和 RED 的阐述可以参考容器监控实践—K8S 常用指标分析[1]这篇文章

采集组件 all in one

prometheus 体系中 exporter 都是独立的,每个组件各司其职,如机器资源用 node-exporter,gpu 有 NVIDIA exporter 等等,但是 exporter 越多,运维压力越大,尤其是对 agent 做资源控制、版本升级。我们尝试对一些 exporter 进行组合,方案有二:

通过主进程拉起 n 个 exporter 进程,仍然可以跟着社区版本更新

用 telegraf 来支持各种类型的 input,n 合 1

另外,node-exporter 不支持进程监控,可以加一个 process-exporter,也可以用上边提到的 telegraf。

k8s 1.16 中 cadvisor 的指标兼容问题

在 k8s 1.16 版本,cadvisor 的指标去掉了 pod_name 和 container_name 的 label,替换为了 pod 和 container。如果你之前用这两个 label 做查询或者 grafana 绘图,得更改下 sql 了。因为我们一直支持多个 k8s 版本,就通过 relabel 配置继续保留了原来的**_name

注意要用 metric_relabel_configs,不是 relabel_configs,采集后做的 replace。

prometheus 集群内与集群外部署

prometheus 如果部署在 k8s 集群内采集是很方便的,用官方给的 yaml 就可以,但我们因为权限和网络需要部署在集群外,二进制运行,专门划了几台高配服务器运行监控组件。

以 pod 方式运行在集群内是不需要证书的(in-cluster 模式),但集群外需要声明 token 之类的证书,并替换address。例如:

上面是通过默认配置中通过 apiserver proxy 到 let,如果网络能通,其实也可以直接把 kubelet 的 10255 作为 target,规模大的时候还减轻了 apiserver 的压力,不过这种方式就要写服务发现来更新 node 列表了。

gpu 指标的获取

nvidia-smi 可以查看机器上的 gpu 资源,而 cadvisor 其实暴露了 metric 来表示 容器使用 gpu 情况,

如果要更详细的 gpu 数据,可以安装dcgm exporter[2],不过 k8s 1.13 才能支持

更改 prometheus 的显示时区

prometheus 为避免时区混乱,在所有组件中专门使用 Unix time 和 UTC 进行显示。不支持在配置文件中设置时区,也不能读取本机/etc/timezone 时区。

其实这个限制是不影响使用的:

如果做可视化,grafana 是可以做时区转换的

如果是调接口,拿到了数据中的时间戳,你想怎么处理都可以

如果因为 prometheus 自带的 ui 不是本地时间,看着不舒服, 2.16 版本[3]的新版 webui 已经引入了 local timezone 的选项。区别见下图

如果你仍然想改 prometheus 代码来适应自己的时区,可以参考这篇文章[4]

关于 timezone 的讨论,可以看这个issue[5]

如何采集 lb 后面的 rs 的 metric

假如你有一个负载均衡 lb,但网络上 prometheus 只能访问到 lb 本身,访问不到后面的 rs,应该如何采集 rs 暴露的 metric?

rs 的服务加 sidecar proxy,或者本机增加 proxy 组件,保证 prometheus 能访问到

lb 增加/ backend1 和/ backend2 请求转发到两个单独的后端,再由 prometheus 访问 lb 采集

版本

prometheus 当前最新版本为 2.16,prometheus 还在不断迭代,因此尽量用最新版,1.x 版本就不用考虑了。

2.16 版本上有一套实验 UI,可以查看 TSDB 的状态,包括 top 10 的 label、metric

prometheus 大内存问题

随着规模变大,prometheus 需要的 cpu 和内存都会升高,内存一般先达到瓶颈,这个时候要么加内存,要么集群分片减少单机指标。这里我们先讨论单机版 prometheus 的内存问题

原因:

prometheus 的内存消耗主要是因为每隔 2 小时做一个 block 数据落盘,落盘之前所有数据都在内存里面,因此和采集量有关。

加载历史数据时,是从磁盘到内存的,查询范围越大,内存越大。这里面有一定的优化空间

一些不合理的查询条件也会加大内存,如 group、大范围 rate

我的指标需要多少内存:

作者给了一个计算器,设置指标量、采集间隔之类的,计算 prometheus 需要的理论内存值:https://www.robustperception.io/how-much-ram-does-prometheus-2-x-need-for-cardinality-and-ingestion

以我们的一个 promserver 为例,本地只保留 2 小时数据,95 万 series,大概占用的内存如下:

有什么优化方案:

sample 数量超过了 200 万,就不要单实例了,做下分片,然后通过 victoriametrics,thanos,trickster 等方案合并数据

评估哪些 metric 和 label 占用较多,去掉没用的指标。2.14 以上可以看 tsdb 状态[6]

查询时尽量避免大范围查询,注意时间范围和 step 的比例,慎用 group

如果需要关联查询,先想想能不能通过 relabel 的方式给原始数据多加个 label,一条 sql 能查出来的何必用 join,时序数据库不是关系数据库。

prometheus 内存占用分析:

通过 pprof 分析:https://www.robustperception.io/optimising-prometheus-2-6-0-memory-usage-with-pprof

1.x 版本的内存:https://www.robustperception.io/how-much-ram-does-my-prometheus-need-for-ingestion

相关 issue:

https://groups.google.com/forum/#!searchin/prometheus-users/memory%7Csort:date/prometheus-users/q4oiVGU6Bxo/uifpXVw3CwAJ

https://github.com/prometheus/prometheus/issues/5723

https://github.com/prometheus/prometheus/issues/1881

prometheus 容量规划

容量规划除了上边说的内存,还有磁盘存储规划,这和你的 prometheus 的架构方案有关

如果是单机 prometheus,计算本地磁盘使用量

如果是 remote-write,和已有的 tsdb 共用即可。

如果是 thanos 方案,本地磁盘可以忽略(2h),计算对象存储的大小就行。

Prometheus 每 2 小时将已缓冲在内存中的数据压缩到磁盘上的块中。包括 chunks, indexes, tombstones 和 metadata,这些占用了一部分存储空间。一般情况下,Prometheus 中存储的每一个样本大概占用 1-2 字节大小(1.7byte)。可以通过 promql 来查看每个样本平均占用多少空间:

如果大致估算本地磁盘大小,可以通过以下公式:

保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。

查看当前每秒获取的样本数:

有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到 Prometheus 会对时间序列进行压缩,因此减少时间序列的数量效果更明显.

举例说明:

采集频率 30s,机器数量 1000,metric 种类 6000,1000600026024 约 200 亿,30G 左右磁盘

只采集需要的指标,如 match[], 或者统计下最常使用的指标,性能最差的指标

以上磁盘容量并没有把 WAL 文件算进去,WAL 文件(raw data)Prometheus 官方文档中说明至少会保存 3 个 write-ahead log files,每一个最大为 128M(实际运行发现数量会更多)

因为我们使用了 thanos 的方案,所以本地磁盘只保留 2h 热数据。WAL 每 2 小时生成一份 block 文件,block 文件每 2 小时上传对象存储,本地磁盘基本没有压力。

关于 prometheus 存储机制,可以看这篇[7]

对 apiserver 的 性能影响

如果你的 prometheus 使用了 kubernetes_sd_config 做服务发现,请求一般会经过集群的 apiserver,随着规模的变大,需要评估下对 apiserver 性能的影响,尤其是 proxy 失败的时候,会导致 cpu 升高。当然了,如果单 k8s 集群规模太大,一般都是拆分集群,不过随时监测下 apiserver 的进程变化还是有必要的。

在监控 cadvisor、docker、kube-proxy 的 metric 时,我们一开始选择从 apiserver proxy 到节点的对应端口,统一设置比较方便,但后来还是改为了直接拉取节点,apiserver 仅做服务发现。

rate 的计算逻辑

prometheus 中的 counter 类型主要是为了 rate 而存在的,即计算速率,单纯的 counter 计数意义不大,因为 counter 一旦重置,总计数就没有意义了。

rate 会自动处理 counter 重置的问题,counter 一般都是一直变大的,例如一个 exporter 启动,然后崩溃了。本来以每秒大约 10 的速率递增,但仅运行了半个小时,则速率(x_total [1h])将返回大约每秒 5 的结果。另外,counter 的任何减少也会被视为 counter 重置。例如,如果时间序列的值为[5,10,4,6],则将其视为[5,10,14,16]。

rate 值很少是精确的。由于针对不同目标的抓取发生在不同的时间,因此随着时间的流逝会发生抖动,query_range 计算时很少会与抓取时间完美匹配,并且抓取有可能失败。面对这样的挑战,rate 的设计必须是健壮的。

rate 并非想要捕获每个增量,因为有时候增量会丢失,例如实例在抓取间隔中挂掉。如果 counter 的变化速度很慢,例如每小时仅增加几次,则可能会导致【假象】。比如出现一个 counter 时间序列,值为 100,rate 就不知道这些增量是现在的值,还是目标已经运行了好几年并且才刚刚开始返回。

建议将 rate 计算的范围向量的时间至少设为抓取间隔的四倍。这将确保即使抓取速度缓慢,且发生了一次抓取故障,您也始终可以使用两个样本。此类问题在实践中经常出现,因此保持这种弹性非常重要。例如,对于 1 分钟的抓取间隔,您可以使用 4 分钟的 rate 计算,但是通常将其四舍五入为 5 分钟。

如果 rate 的时间区间内有数据缺失,他会基于趋势进行推测,比如:

详细的内容可以看下这个视频[8]

反直觉的 p95 统计

histogram_quantile 是 Prometheus 常用的一个函数,比如经常把某个服务的 P95 响应时间来衡量服务质量。不过它到底是什么意思很难解释得清,特别是面向非技术的同学,会遇到很多“灵魂拷问”。

我们常说 P95(p99,p90 都可以) 响应延迟是 100ms,实际上是指对于收集到的所有响应延迟,有 5% 的请求大于 100ms,95% 的请求小于 100ms。Prometheus 里面的 histogram_quantile 函数接收的是 0-1 之间的小数,将这个小数乘以 100 就能很容易得到对应的百分位数,比如 0.95 就对应着 P95,而且还可以高于百分位数的精度,比如 0.9999。

当你用 histogram_quantile 画出响应时间的趋势图时,可能会被问:为什么 p95 大于或小于我的平均值?

正如中位数可能比平均数大也可能比平均数小,P99 比平均值小也是完全有可能的。通常情况下 P99 几乎总是比平均值要大的,但是如果数据分布比较极端,最大的 1% 可能大得离谱从而拉高了平均值。一种可能的例子:

服务 X 由顺序的 A,B 两个步骤完成,其中 X 的 P99 耗时 100ms,A 过程 P99 耗时 50ms,那么推测 B 过程的 P99 耗时情况是?

直觉上来看,因为有 X=A+B,所以答案可能是 50ms,或者至少应该要小于 50ms。实际上 B 是可以大于 50ms 的,只要 A 和 B 最大的 1% 不恰好遇到,B 完全可以有很大的 P99:

所以我们从题目唯一能确定的只有 B 的 P99 应该不能超过 100ms,A 的 P99 耗时 50ms 这个条件其实没啥用。

类似的疑问很多,因此对于 histogram_quantile 函数,可能会产生反直觉的一些结果,最好的处理办法是不断试验调整你的 bucket 的值,保证更多的请求时间落在更细致的区间内,这样的请求时间才有统计意义。

慢查询问题

promql 的基础知识看这篇文章[9]

prometheus 提供了自定义的 promql 作为查询语句,在 graph 上调试的时候,会告诉你这条 sql 的返回时间,如果太慢你就要注意了,可能是你的用法出现了问题。

评估 prometheus 的整体响应时间,可以用这个默认指标:

一般情况下响应过慢都是 promql 使用不当导致,或者指标规划有问题,如:

大量使用 join 来组合指标或者增加 label,如将 kube-state-metric 中的一些 meta label 和 node-exporter 中的节点属性 label 加入到 cadvisor 容器 数据里,像统计 pod 内存使用率并按照所属节点的机器类型分类,或按照所属 rs 归类。

范围查询时,大的时间范围,step 值却很小,导致查询到的数量量过大。

rate 会自动处理 counter 重置的问题,最好由 promql 完成,不要自己拿出来全部元数据在程序中自己做 rate 计算。

在使用 rate 时,range duration 要大于等于step[10],否则会丢失部分数据[11]

prometheus 是有基本预测功能的,如和(更准确)可以根据已有数据预测未来趋势

如果比较复杂且耗时的 sql,可以使用 record rule 减少指标数量,并使查询效率更高,但不要什么指标都加 record,一半以上的 metric 其实不太会查询到。同时 label 中的值不要加到 record rule 的 name 中。

高基数问题 Cardinality

高基数是数据库避不开的一个话题,对于 mysql 这种 db 来讲,基数是指特定列或字段中包含的唯一值的数量。基数越低,列中重复的元素越多。对于时序数据库而言,就是 tags、label 这种标签值的数量多少。

比如 prometheus 中如果有一个指标 表示访问量,method 表示请求方法,originIP 是客户端 IP,method 的枚举值是有限的,但 originIP 却是无限的,加上其他 label 的排列组合就无穷大了,也没有任何关联特征,因此这种高基数不适合作为 metric 的 label,真要的提取 originIP,应该用日志的方式,而不是 metric 监控

时序数据库会为这些 label 建立索引,以提高查询性能,以便您可以快速找到与所有指定标签匹配的值。如果值的数量过多,索引是没有意义的,尤其是做 p95 等计算的时候,要扫描大量 series 数据

官方文档中对于 label 的建议

如何查看当前的 label 分布情况呢,可以使用 prometheus 提供的 tsdb 工具。可以使用命令行查看,也可以在 2.16 版本以上的 prometheus graph 查看

top10 高基数的 metric

高基数的 label

找到最大的 metric 或 job

top10 的 metric 数量:按 metric 名字分

top10 的 metric 数量:按 job 名字分

k8s 组件性能指标

除了基础的 cadvisor 资源监控,还应该对核心组件的 metric 进行采集,包括:

10250:kubelet 监听端口,包括/stats/summary、metrics、metrics/cadvisor。10250 为认证端口,非认证端口用 10255

10251:kube-scheduler 的 metric 端口,本地 127 访问不需要认证,如调度延迟,

10252:kube-controller 的 metric 端口,本地 127 访问不需要认证

6443: apiserver,需要证书认证,直接 curl 命令为

2379: etcd 的 metric 端口,直接 curl 命令为:

docker 指标暴露:

如果要开放 docker 进程指标,需要开启实验特性,文件/etc/docker/daemon.json

kube-proxy 指标:

端口为 10249,默认 127 开放,可以修改为 hostname 开放,--metrics-bind-address=机器 ip

示例图:

prometheus 重启慢

prometheus 重启的时候需要把 wal 中的内容 load 到内存里,保留时间越久、wal 文件越大,重启的实际越长,这个是 prometheus 的机制,没得办法,因此能 reload 的,就不要重启,重启一定会导致短时间的不可用,而这个时候 prometheus 高可用就很重要了。

但 prometheus 也曾经对启动时间做过优化,在 2.6 版本中对于 WAL 的 load 速度就做过速度的优化,希望重启的时间不超过 1 分钟[12]

你的应用应该暴露多少指标

当你开发自己的服务的时候,你可能会把一些数据暴露 metric 出去,比如特定请求数、goroutine 数等,指标数量多少合适呢?

虽然指标数量和你的应用规模相关,但也有一些建议(Brian Brazil)[13],

比如简单的服务如缓存等,类似 pushgateway,大约 120 个指标,prometheus 本身暴露了 700 左右的指标,如果你的应用很大,也尽量不要超过 10000 个指标,需要合理控制你的 label。

relabel_configs 与 metric_relabel_configs

relabel_config 发生在采集之前,metric_relabel_configs 发生在采集之后,合理搭配可以满足场景的配置

Prometheus 的预测能力

场景 1:你的磁盘剩余空间一直在减少,并且降低的速度比较均匀,你希望知道大概多久之后达到阈值,并希望在某一个时刻报警出来。

场景 2:你的 pod 内存使用率一直升高,你希望知道大概多久之后会到达 limit 值,并在一定时刻报警出来,在被杀掉之前上去排查。

prometheus 的 deriv 和 predict_linear 方法可以满足这类需求, promtheus 提供了基础的预测能力,基于当前的变化速度,推测一段时间后的值。

以 mem_free 为例,最近一小时的 free 值一直在下降。

deriv 函数可以显示指标在一段时间的变化速度

predict_linear 方法是预测基于这种速度,最后可以达到的值

你可以基于设置合理的报警规则,如小于 10 时报警

predict_linear 与 deriv 的关系,含义上约等于,predict_linear 稍微准确一些。

如果你要基于 metric 做模型预测,可以参考下forecast-prometheus[14]

错误的高可用设计

有些人提出过这种类型的方案,想提高其扩展性和可用性。

应用程序将 metric 推到到消息队列如 kafaka,然后经过 exposer 消费中转,再被 prometheus 拉取。产生这种方案的原因一般是有历史包袱、复用现有组件、想通过 mq 来提高扩展性。

这种方案有几个问题:

增加了 queue 组件,多了一层依赖,如果 app 与 queue 之间连接失败,难道要在 app 本地缓存监控数据?

抓取时间可能会不同步,延迟的数据将会被标记为陈旧数据,当然你可以通过添加时间戳来标识,但就失去了对陈旧数据的处理逻辑[15]

扩展性问题:prometheus 适合大量小目标,而不是一个大目标,如果你把所有数据都放在了 exposer 中,那么 prometheus 的单个 job 拉取就会成为 cpu 瓶颈。这个和 pushgateway 有些类似,没有特别必要的场景,都不是官方建议的方式。

缺少了服务发现和拉取控制,prom 只知道一个 exposer,不知道具体是哪些 target,不知道他们的 up 时间,无法使用 scrape_*等指标做查询,也无法用scrape_limit[16]做限制。

如果你的架构和 prometheus 的设计理念相悖,可能要重新设计一下方案了,否则扩展性和可靠性反而会降低。

高可用方案

prometheus 高可用有几种方案:

基本 HA:即两套 prometheus 采集完全一样的数据,外边挂负载均衡

HA + 远程存储:除了基础的多副本 prometheus,还通过 Remote write 写入到远程存储,解决存储持久化问题

联邦集群:即 federation,按照功能进行分区,不同的 shard 采集不同的数据,由 Global 节点来统一存放,解决监控数据规模的问题。

使用 thanos 或者 victoriametrics,来解决全局查询、多副本数据 join 问题。

就算使用官方建议的多副本 + 联邦,仍然会遇到一些问题:

本质原因是,prometheus 的本地存储没有数据同步能力,要在保证可用性的前提下,再保持数据一致性是比较困难的,基础的 HA proxy 满足不了要求,比如:

集群的后端有 A 和 B 两个实例,A 和 B 之间没有数据同步。A 宕机一段时间,丢失了一部分数据,如果负载均衡正常轮询,请求打到 A 上时,数据就会异常。

如果 A 和 B 的启动时间不同,时钟不同,那么采集同样的数据时间戳也不同,就不是多副本同样数据的概念了

就算用了远程存储,A 和 B 不能推送到同一个 tsdb,如果每人推送自己的 tsdb,数据查询走哪边就是问题了。

因此解决方案是在存储、查询两个角度上保证数据的一致:

存储角度:如果使用 remote write 远程存储, A 和 B 后面可以都加一个 adapter,adapter 做选主逻辑,只有一份数据能推送到 tsdb,这样可以保证一个异常,另一个也能推送成功,数据不丢,同时远程存储只有一份,是共享数据。方案可以参考这篇文章[17]

查询角度:上边的方案实现很复杂且有一定风险,因此现在的大多数方案在查询层面做文章,比如 thanos 或者 victoriametrics,仍然是两份数据,但是查询时做数据去重和 join。只是 thanos 是通过 sidecar 把数据放在对象存储,victoriametrics 是把数据 remote write 到自己的 server 实例,但查询层 thanos-query 和 victor 的 promxy 的逻辑基本一致。

我们采用了 thanos 来支持多地域监控数据,具体方案可以看这篇文章[18]

关于日志

k8s 中的日志一般指是容器标准输出 + 容器内日志,方案基本是采用 fluentd/fluent-bit/filebeat 等采集推送到 es,但还有一种是日志转 metric,如解析特定字符串出现次数,nginx 日志得到 qps 指标 等,这里可以采用 grok 或者 mtail,以 exporter 的形式提供 metric 给 prometheus

参考资料[2]

dcgm exporter:https://github.com/NVIDIA/gpu-monitoring-tools/tree/master/exporters/prometheus-dcgm

[3]

2.16 版本:https://github.com/prometheus/prometheus/commit/d996ba20ec9c7f1808823a047ed9d5ce96be3d8f

[4]

这篇文章:https://zhangguanzhang.github.io/2019/09/05/prometheus-change-timezone/

[5]

issue:https://github.com/prometheus/prometheus/issues/500

[6]

tsdb 状态:https://www.google.com/url?q=https%3A%2F%2Fprometheus.io%2Fdocs%2Fprometheus%2Flatest%2Fquerying%2Fapi%2F%23tsdb-stats&sa=D&sntz=1&usg=AFQjCNFE5AzQxyhzt8SqQLHPUySZl3lNNw

[8]

视频:https://www.youtube.com/watch?reload=9&v=67Ulrq6DxwA

[10]

step:https://www.robustperception.io/step-and-query_range

[11]

部分数据:https://chanjarster.github.io/post/p8s-step-param/

[12]

1 分钟:https://www.robustperception.io/optimising-startup-time-of-prometheus-2-6-0-with-pprof

[13]

建议(Brian Brazil):https://www.robustperception.io/how-many-metrics-should-an-application-return

[14]

forecast-prometheus:https://github.com/nfrumkin/forecast-prometheus

[15]

处理逻辑:https://www.robustperception.io/staleness-and-promql

[16]

scrape_limit:https://www.robustperception.io/using-sample_limit-to-avoid-overload

[17]

这篇文章:https://blog.timescale.com/blog/prometheus-ha-postgresql-8de68d19b6f5

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201024A09AWP00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券