暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Prometheus 的九个坑【评注版】

工匠小猪猪的技术世界 2019-07-11
1380

一个执着于技术的公众号


最近我在研究Prometheus,在研究吴叶磊的文章以后,对于Prometheus存在的问题做了一次再梳理的评注,如下所示:

Prometheus本身已经成为了云原生中指标监控的事实标准,几乎所有 Kubernetes 的核心组件以及其它云原生系统都以 Prometheus 的指标格式输出自己的运行时监控信息。
甚至绝大多数中间件(比如 MySQL、Kafka、Redis、ES)也都有社区提供的Prometheus Exporter。

很多人都从Prometheus的优点开始说起,比如概念、安装、搭建、使用,那我反其道而行之,先从Prometheus的潜在问题开始说起。

Prometheus does one thing, and it does it well

Prometheus最核心的是监控指标,但是如果考虑动态伸缩、远端存储、平台化就需要做一些额外的扩展和整合工作。

2019年了,Prometheus除了仍然还不支持分布式、不支持数据导入/导出、不支持通过 api 修改监控目标和报警规则,还有着如下的一些问题,在使用之初就需要了解。

参考资料:https://aleiwu.com/post/prometheus-bp/

风险规避

一、自监控

Prometheus在云原生上可以用来监控其他一系列系统,那么这里问一个尖锐的问题,Prometheus的自监控谁来做?我们都知道Prometheus也可以提供自身的监控指标项,但是如果Prometheus本身就宕机了,那么Prometheus本身如何做告警?

因此,强烈建议在上生产环境之前,一定要确保至少有两个独立的 Prometheus 实例互相做交叉监控。 交叉监控的配置也很简单,每台 Prometheus 都拉取其余所有 Prometheus 的指标即可。

二、Alertmanager警报系统挂掉了怎么办

如果Alertmanager挂掉了,警报就发不出来了,对于开发、运维人员来说,Prometheus就成了一个哑巴,我们该如何处理?

因此,对于Prometheus来说,要做HA集群,包括警报系统Alertmanager。

除此之外还有一个经典的兜底措施叫做 “Dead man’s switch”: 定义一条永远会触发的告警,不断通知,假如哪天这条通知停了,那么说明报警链路出问题了。

三、准确性与运维简易性的权衡

和ELK等其他日志架构相比,Prometheus的运维相对比较简单。

在使用Prometheus之初必须明确清楚,Prometheus放弃了一部分数据准确性,无法和日志系统一样做到100%准确,但是可以获取高纯度的监控趋势

  • 比如在两次采样的间隔中,内存用量有一个瞬时小尖峰,那么这次小尖峰我们是观察不到的;

  • 再比如 QPS、RT、P95、P99 这些值都只能估算,无法和日志系统一样做到 100% 准确

四、任何版本的Prometheus都不支持NFS

参见 https://github.com/prometheus/prometheus/issues/3534

这个案例还有一些实际生产案例都告诉我们,Prometheus存储文件如果使用NFS上有发生损坏丢失历史数据的可能性

五、尽早干掉维度过高的坏指标

如果Prometheus 里有 50% 以上的存储空间和 80% 以上的计算资源(CPU、内存)都被两三个高维度指标占据,这里就需要考虑是否需要治理。对于这些高指标,可以换其他的方式,比如数仓或者日志流等等去处理。

并不是业务方所有的指标都可以直接不管不顾得往Label里塞

解决方法:用警报规则找出维度过高的坏指标,然后在 Scrape 配置里 Drop 掉导致维度过高的 label。

# 统计每个指标的时间序列数,超出 10000 的报警
count by (__name__)({__name__=~".+"}) > 10000

“坏指标”报警出来之后,就可以用 metric_relabel_config 的 drop 操作删掉有问题的 label(比如 userId、email 这些一看就是问题户)

六、PromQL要先 rate() 再 sum(),不能 sum() 完再 rate()

这背后与 rate() 的实现方式有关,rate() 在设计上假定对应的指标是一个 Counter,也就是只有 incr(增加) 和 reset(归0) 两种行为。而做了 sum() 或其他聚合之后,得到的就不再是一个 Counter 了,举个例子,比如 sum() 的计算对象中有一个归0了,那整体的和会下降,而不是归零,这会影响 rate() 中判断 reset(归0) 的逻辑,从而导致错误的结果。写 PromQL 时这个坑容易避免,但碰到 Recording Rule 就不那么容易了,因为不去看配置的话大家也想不到 new_metric 是怎么来的。

Recording Rule规则:一步到位,直接算出需要的值,避免算出一个中间结果再拿去做聚合。

七、警报和历史趋势图未必 Match

最近半年常常被问两个问题:

  • 我的历史趋势图看上去超过水位线了,警报为什么没报?

  • 我的历史趋势图看上去挺正常的,警报为什么报了?

这其中有一个原因是:趋势图上每个采样点的采样时间和警报规则每次的计算时间不是严格一致的。当时间区间拉得比较大的时候,采样点非常稀疏,不如警报计算的间隔来得密集,这个现象尤为明显,比如时序图采样了 0秒,60秒,120秒三个点。而警报在15秒,30秒,45秒连续计算出了异常,那在图上就看不出来。另外,经过越多的聚合以及函数操作,不同时间点的数据差异会来得越明显,有时确实容易混淆。

这个其实不是问题,碰到时将趋势图的采样间隔拉到最小,仔细比对一下,就能验证警报的准确性。而对于聚合很复杂的警报,可以先写一条 Recording Rule, 再针对 Recording Rule 产生的新指标来建警报。这种范式也能帮助我们更高效地去建分级警报(超过不同阈值对应不同的紧急程度)

八、Alertmanager 的 group_interval 会影响 resolved 通知

Alertmanager 里有一个叫 group_interval 的配置,用于控制同一个 group 内的警报最快多久通知一次。这里有一个问题是 firing(激活) 和 resolved(已消除) 的警报通知是共享同一个 group 的。也就是说,假设我们的 group_interval 是默认的 5 分钟,那么一条警报激活十几秒后立马就消除了,它的消除通知会在报警通知的 5 分钟之后才到,因为在发完报警通知之后,这个 Group 需要等待 5 分钟的 group_interval 才能进行下一次通知。

这个设计让”警报消除就立马发送消除通知”变得几乎不可能,因为假如把 group_interval 变得很小的话,警报通知就会过于频繁,而调大的话,就会拖累到消除通知。

这个问题修改一点源码即可解决,不过无伤大雅,不修也完全没问题。

九、Prometheus 本身没有提供管理配置的 API 接口

Prometheus Operator Make Prometheus as a Service By k8s

Prometheus 本身没有提供管理配置的 API 接口,尤其是管理监控目标和管理警报规则,没有提供好用的多实例管理手段。除了写脚本以外,可以了解一下 Prometheus Operator

什么是 Operator?Operator = Controller + CRD。假如你不了解什么是 Controller 和 CRD,可以看一个 Kubernetes 本身的例子:我们提交一个 Deployment 对象来声明期望状态,比如 3 个副本;而 Kubernetes 的 Controller 会不断地干活(跑控制循环)来达成期望状态,比如看到只有 2 个副本就创建一个,看到有 4 个副本了就删除一个。在这里,Deployment 是 Kubernetes 本身的 API 对象。那假如我们想自己设计一些 API 对象来完成需求呢?Kubernetes 本身提供了 CRD(Custom Resource Definition),允许我们定义新的 API 对象。但在定义完之后,Kubernetes 本身当然不可能知道这些 API 对象的期望状态该如何到达。这时,我们就要写对应的 Controller 去实现这个逻辑。而这种自定义 API 对象 + 自己写 Controller 去解决问题的模式,就是 Operator Pattern。
https://aleiwu.com/post/prometheus-operator/



以下是原文链接:https://aleiwu.com/post/prometheus-bp/

Prometheus 是一个开源监控系统,它本身已经成为了云原生中指标监控的事实标准,几乎所有 Kubernetes 的核心组件以及其它云原生系统都以 Prometheus 的指标格式输出自己的运行时监控信息。我在工作中也比较深入地使用过 Prometheus,最大的感受就是它非常容易维护,突出一个简单省心成本低。当然,这当中也免不了踩过一些坑,下面就总结一下。

假如你没有用过 Prometheus,建议先看一遍官方文档:https://prometheus.io/docs/introduction/overview/。


接受准确性与可靠性的权衡


Prometheus 作为一个基于指标(Metric)的监控系统,在设计上就放弃了一部分数据准确性:

  • 比如在两次采样的间隔中,内存用量有一个瞬时小尖峰,那么这次小尖峰我们是观察不到的;

  • 再比如 QPS、RT、P95、P99 这些值都只能估算,无法和日志系统一样做到 100% 准确,下面也会讲一个相关的坑。


放弃一点准确性得到的是更高的可靠性,这里的可靠性体现为架构简单、数据简单、运维简单。假如你维护过 ELK 或其它日志架构的话,就会发现相比于指标,日志系统想要稳定地跑下去需要付出几十倍的机器成本与人力成本。既然是权衡,那就没有好或不好,只有适合不适合,我推荐在应用 Prometheus 之初就要先考虑清楚这个问题,并且将这个权衡明确地告诉使用方。

首先做好自监控


不知道你有没有考虑过一个问题,其它系统都用 Prometheus 监控起来了,报警规则也设置好了,那 Prometheus 本身由谁来监控?

答案是”另一个监控系统”,而这个监控系统可以是另一个 Prometheus。按照官方的 quickstart 或 Helm 部署的 Prometheus 单实例自己监控自己的,我们当然不能指望一个系统挂掉之后自己发现自己挂了。因此我强烈建议在上生产环境之前,一定要确保至少有两个独立的 Prometheus 实例互相做交叉监控。交叉监控的配置也很简单,每台 Prometheus 都拉取其余所有 Prometheus 的指标即可。

还有一个点是警报系统(Alertmanager),我们再考虑一下警报系统挂掉的情况:这时候 Prometheus 可以监控到警报系统挂了,但是因为警报挂掉了,所以警报自然就发不出来,这也是应用 Prometheus 之前必须搞定的问题。这个问题可以通过给警报系统做 HA 来应对。除此之外还有一个经典的兜底措施叫做 “Dead man’s switch(https://en.wikipedia.org/wiki/Deadman%27sswitch)”:定义一条永远会触发的告警,不断通知,假如哪天这条通知停了,那么说明报警链路出问题了。

不要使用 NFS 做存储


如题,Prometheus 维护者也在 issue(https://github.com/prometheus/prometheus/issues/3534)中表示过不支持 NFS。这点我们有血泪教训(我们曾经有一台 Prometheus 存储文件发生损坏丢失了历史数据)。

尽早干掉维度(Cardinality)过高的指标


根据我们的经验,Prometheus 里有 50% 以上的存储空间和 80% 以上的计算资源(CPU、内存)都是被那么两三个维度超高的指标用掉的。而且这类维度超高的指标由于数据量很大,稍微查得野一点就会 OOM 搞死 Prometheus 实例。

首先要明确这类指标是对 Prometheus 的滥用,类似需求完全应该放到日志流或数仓里去算。但是指标的接入方关注的往往是业务上够不够方便,假如足够方便的话什么都可以往 label 里塞。这就需要我们防患于未然,一个有效的办法是用警报规则找出维度过高的坏指标,然后在 Scrape 配置里 Drop 掉导致维度过高的 label。

警报规则的例子:

  1. # 统计每个指标的时间序列数,超出 10000 的报警

  2. count by (__name__)({__name__=~".+"}) > 10000


“坏指标”报警出来之后,就可以用 metricrelabelconfig 的 drop 操作删掉有问题的 label(比如 userId、email 这些一看就是问题户),这里的配置方式可以查阅文档。

对了,这条的关键词是尽早,最好就是部署完就搞上这条规则,否则等哪天 Prometheus 容量满了再去找业务方说要删 label,那业务方可能就要忍不住扇你了……

Rate 类函数 + Recording Rule 的坑


可能你已经知道了 PromQL 里要先 rate() 再 sum(),不能 sum() 完再 rate()(不知道也没事,马上讲)。但当 rate() 已经同类型的函数如 increase() 和 recording rule 碰到一起时,可能就会不小心掉到坑里去。

当时,我们已经有了一个维度很高的指标(只能继续维护了,因为没有尽早干掉),为了让大家查询得更快一点,我们设计了一个 Recording Rule,用 sum() 来去掉维度过高的 badlabel,得到一个新指标。那么只要不涉及到 badlabel,大家就可以用新指标进行查询,Recording Rule 如下:

  1. sum(old_metric) without (bad_label)


用了一段时间后,大家发现 newmetric 做 rate() 得到的 QPS 趋势图里经常有奇怪的尖峰,但 oldmetric 就不会出现。这时我们恍然大悟:绕了个弯踩进了 rate() 的坑里。

这背后与 rate() 的实现方式有关,rate() 在设计上假定对应的指标是一个 Counter,也就是只有 incr(增加)和 reset(归 0)两种行为。而做了 sum() 或其他聚合之后,得到的就不再是一个 Counter 了,举个例子,比如 sum() 的计算对象中有一个归 0 了,那整体的和会下降,而不是归零,这会影响 rate() 中判断 reset(归 0)的逻辑,从而导致错误的结果。写 PromQL 时这个坑容易避免,但碰到 Recording Rule 就不那么容易了,因为不去看配置的话大家也想不到 new_metric 是怎么来的。

要完全规避这个坑,可以遵守一个原则:Recording Rule 一步到位,直接算出需要的值,避免算出一个中间结果再拿去做聚合。

警报和历史趋势图未必 Match


最近半年常常被问两个问题所困惑:

  • 我的历史趋势图看上去超过水位线了,警报为什么没报?

  • 我的历史趋势图看上去挺正常的,警报为什么报了?


这其中有一个原因是:趋势图上每个采样点的采样时间和警报规则每次的计算时间不是严格一致的。当时间区间拉得比较大的时候,采样点非常稀疏,不如警报计算的间隔来得密集,这个现象尤为明显,比如时序图采样了 0 秒,60 秒,120 秒三个点。而警报在 15 秒,30 秒,45 秒连续计算出了异常,那在图上就看不出来。另外,经过越多的聚合以及函数操作,不同时间点的数据差异会来得越明显,有时确实容易混淆。

这个其实不是问题,碰到时将趋势图的采样间隔拉到最小,仔细比对一下,就能验证警报的准确性。而对于聚合很复杂的警报,可以先写一条 Recording Rule,再针对 Recording Rule 产生的新指标来建警报。这种方式也能帮助我们更高效地去建分级警报(超过不同阈值对应不同的紧急程度)。

Alertmanager 的 group_interval 会影响 resolved 通知


Alertmanager 里有一个叫 groupinterval 的配置,用于控制同一个 group 内的警报最快多久通知一次。这里有一个问题是 firing(激活)和 resolved(已消除)的警报通知是共享同一个 group 的。也就是说,假设我们的 groupinterval 是默认的 5 分钟,那么一条警报激活十几秒后立马就消除了,它的消除通知会在报警通知的 5 分钟之后才到,因为在发完报警通知之后,这个 Group 需要等待 5 分钟的 group_interval 才能进行下一次通知。

这个设计让“警报消除就立马发送消除通知”变得几乎不可能,因为假如把 group_interval 变得很小的话,警报通知就会过于频繁,而调大的话,就会拖累到消除通知。

这个问题修改一点源码即可解决,不过无伤大雅,不修也完全没问题。

最后一条:不要忘记因何而来


最后一条撒点鸡汤:监控的核心目标还是护航业务稳定,保障业务的快速迭代,永远不要忘记因何而来。

曾经有一段时间,我们追求“监控的覆盖率”,所有系统所有层面,一定要有指标,而且具体信息 label 分得越细越好,最后搞出几千个监控项,不仅搞得眼花缭乱还让 Prometheus 变慢了;

还有一段时间,我们追求“警报的覆盖率”,事无巨细必须要有警报,人人有责全体收警报(有些警报会发送给几十个人)。最后当然你也能预想到了,告警风暴让大家都对警报疲劳了;

这些事情乍看起来都是在努力工作,但其实一开始的方向就错了,监控的目标绝对不是为了达到 xxx 个指标,xxx 条警报规则,这些东西有什么意义?依我看,负责监控的开发就算不是 SRE 也要有 SRE 的心态和视野,不要为监控系统的功能或覆盖面负责(这样很可让导致开发在监控里堆砌功能和内容,变得越来越臃肿越来越不可靠),而要为整个业务的稳定性负责,同时站在稳定性的投入产出比角度去考虑每件事情的性质和意义,不要忘记我们因何而来。


欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢



知识星球是 公众号 工匠人生 忠实读者私密进阶学习圈。这里会有很多超越公众号技术深度的架构原创实战经验,也有私密微信群,分享行业深度洞见,交流碰撞,沉淀优质内容。


       漫画:程序员小赵的架构师之路

       那些年,阿里教会我的事

     【感恩】当年,那个领我入门的项目老大......

       重新定义淘宝面试中遇到的架构问题

       Java5~11各个版本新特性史上最全总结     

       史上最全JUC脑图文字大纲珍藏版

     【我和世界不一样】复盘猪猪的知识星球

      Java与Netty实现高性能高并发

      Java并发编程73道面试题及答案 —— 面试稳了

     【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?

     【面试专题】昨晚面试过程中关于Maven依赖排除的问题和答案

     【面试题及答案】百度亿万级数据处理及面试现场系列合辑

       大数据十道经典海量数据处理面试题与十个方法大总结

     【猪猪原创时间】车的哲学故事——远光灯和近光灯,当蛮横遇上谦和

       微服务Dubbo全链路追踪,使用MDC和SPI生成全局TraceID

       RSocket思潮

     【朝花夕拾】Maven拾遗

        IDEA构建SpringBoot生成的maven-wrapper是什么?

        UUID会重复吗?

        浅谈项目管

文章转载自工匠小猪猪的技术世界,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论