Prometheus 不完全避坑指南
Prometheus 是一个开源监控系统,它本身已经成为了云原生中指标监控的事实标准,几乎所有 Kubernetes 的核心组件以及其它云原生系统都以 Prometheus 的指标格式输出自己的运行时监控信息。我在工作中也比较深入地使用过 Prometheus,最大的感受就是它非常容易维护,突出一个简单省心成本低。当然,这当中也免不了踩过一些坑,下面就总结一下。
假如你没有用过 Prometheus,建议先看一遍官方文档:https://prometheus.io/docs/introduction/overview/。
首先做好自监控
答案是”另一个监控系统”,而这个监控系统可以是另一个 Prometheus。按照官方的 quickstart 或 Helm 部署的 Prometheus 单实例自己监控自己的,我们当然不能指望一个系统挂掉之后自己发现自己挂了。因此我强烈建议在上生产环境之前,一定要确保至少有两个独立的 Prometheus 实例互相做交叉监控。交叉监控的配置也很简单,每台 Prometheus 都拉取其余所有 Prometheus 的指标即可。
还有一个点是警报系统(Alertmanager),我们再考虑一下警报系统挂掉的情况:这时候 Prometheus 可以监控到警报系统挂了,但是因为警报挂掉了,所以警报自然就发不出来,这也是应用 Prometheus 之前必须搞定的问题。这个问题可以通过给警报系统做 HA 来应对。除此之外还有一个经典的兜底措施叫做 “Dead man’s switch(https://en.wikipedia.org/wiki/Deadman%27sswitch)”:定义一条永远会触发的告警,不断通知,假如哪天这条通知停了,那么说明报警链路出问题了。
不要使用 NFS 做存储
尽早干掉维度(Cardinality)过高的指标
首先要明确这类指标是对 Prometheus 的滥用,类似需求完全应该放到日志流或数仓里去算。但是指标的接入方关注的往往是业务上够不够方便,假如足够方便的话什么都可以往 label 里塞。这就需要我们防患于未然,一个有效的办法是用警报规则找出维度过高的坏指标,然后在 Scrape 配置里 Drop 掉导致维度过高的 label。
警报规则的例子:
“坏指标”报警出来之后,就可以用 metricrelabelconfig 的 drop 操作删掉有问题的 label(比如 userId、email 这些一看就是问题户),这里的配置方式可以查阅文档。
对了,这条的关键词是尽早,最好就是部署完就搞上这条规则,否则等哪天 Prometheus 容量满了再去找业务方说要删 label,那业务方可能就要忍不住扇你了……
Rate 类函数 + Recording Rule 的坑
当时,我们已经有了一个维度很高的指标(只能继续维护了,因为没有尽早干掉),为了让大家查询得更快一点,我们设计了一个 Recording Rule,用 sum() 来去掉维度过高的 badlabel,得到一个新指标。那么只要不涉及到 badlabel,大家就可以用新指标进行查询,Recording Rule 如下:
用了一段时间后,大家发现 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
这个其实不是问题,碰到时将趋势图的采样间隔拉到最小,仔细比对一下,就能验证警报的准确性。而对于聚合很复杂的警报,可以先写一条 Recording Rule,再针对 Recording Rule 产生的新指标来建警报。这种方式也能帮助我们更高效地去建分级警报(超过不同阈值对应不同的紧急程度)。
Alertmanager 的 group_interval 会影响 resolved 通知
这个设计让“警报消除就立马发送消除通知”变得几乎不可能,因为假如把 group_interval 变得很小的话,警报通知就会过于频繁,而调大的话,就会拖累到消除通知。
这个问题修改一点源码即可解决,不过无伤大雅,不修也完全没问题。
最后一条:不要忘记因何而来
曾经有一段时间,我们追求“监控的覆盖率”,所有系统所有层面,一定要有指标,而且具体信息 label 分得越细越好,最后搞出几千个监控项,不仅搞得眼花缭乱还让 Prometheus 变慢了;
还有一段时间,我们追求“警报的覆盖率”,事无巨细必须要有警报,人人有责全体收警报(有些警报会发送给几十个人)。最后当然你也能预想到了,告警风暴让大家都对警报疲劳了;
这些事情乍看起来都是在努力工作,但其实一开始的方向就错了,监控的目标绝对不是为了达到 xxx 个指标,xxx 条警报规则,这些东西有什么意义?依我看,负责监控的开发就算不是 SRE 也要有 SRE 的心态和视野,不要为监控系统的功能或覆盖面负责(这样很可让导致开发在监控里堆砌功能和内容,变得越来越臃肿越来越不可靠),而要为整个业务的稳定性负责,同时站在稳定性的投入产出比角度去考虑每件事情的性质和意义,不要忘记我们因何而来。
原文链接:https://aleiwu.com/post/prometheus-bp/
假如你没有用过 Prometheus,建议先看一遍官方文档:https://prometheus.io/docs/introduction/overview/。
比如在两次采样的间隔中,内存用量有一个瞬时小尖峰,那么这次小尖峰我们是观察不到的;
再比如 QPS、RT、P95、P99 这些值都只能估算,无法和日志系统一样做到 100% 准确,下面也会讲一个相关的坑。
首先做好自监控
答案是”另一个监控系统”,而这个监控系统可以是另一个 Prometheus。按照官方的 quickstart 或 Helm 部署的 Prometheus 单实例自己监控自己的,我们当然不能指望一个系统挂掉之后自己发现自己挂了。因此我强烈建议在上生产环境之前,一定要确保至少有两个独立的 Prometheus 实例互相做交叉监控。交叉监控的配置也很简单,每台 Prometheus 都拉取其余所有 Prometheus 的指标即可。
还有一个点是警报系统(Alertmanager),我们再考虑一下警报系统挂掉的情况:这时候 Prometheus 可以监控到警报系统挂了,但是因为警报挂掉了,所以警报自然就发不出来,这也是应用 Prometheus 之前必须搞定的问题。这个问题可以通过给警报系统做 HA 来应对。除此之外还有一个经典的兜底措施叫做 “Dead man’s switch(https://en.wikipedia.org/wiki/Deadman%27sswitch)”:定义一条永远会触发的告警,不断通知,假如哪天这条通知停了,那么说明报警链路出问题了。
不要使用 NFS 做存储
尽早干掉维度(Cardinality)过高的指标
首先要明确这类指标是对 Prometheus 的滥用,类似需求完全应该放到日志流或数仓里去算。但是指标的接入方关注的往往是业务上够不够方便,假如足够方便的话什么都可以往 label 里塞。这就需要我们防患于未然,一个有效的办法是用警报规则找出维度过高的坏指标,然后在 Scrape 配置里 Drop 掉导致维度过高的 label。
警报规则的例子:
# 统计每个指标的时间序列数,超出 10000 的报警
count by (__name__)({__name__=~".+"}) > 10000
“坏指标”报警出来之后,就可以用 metricrelabelconfig 的 drop 操作删掉有问题的 label(比如 userId、email 这些一看就是问题户),这里的配置方式可以查阅文档。
对了,这条的关键词是尽早,最好就是部署完就搞上这条规则,否则等哪天 Prometheus 容量满了再去找业务方说要删 label,那业务方可能就要忍不住扇你了……
Rate 类函数 + Recording Rule 的坑
当时,我们已经有了一个维度很高的指标(只能继续维护了,因为没有尽早干掉),为了让大家查询得更快一点,我们设计了一个 Recording Rule,用 sum() 来去掉维度过高的 badlabel,得到一个新指标。那么只要不涉及到 badlabel,大家就可以用新指标进行查询,Recording Rule 如下:
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
我的历史趋势图看上去超过水位线了,警报为什么没报?
我的历史趋势图看上去挺正常的,警报为什么报了?
这个其实不是问题,碰到时将趋势图的采样间隔拉到最小,仔细比对一下,就能验证警报的准确性。而对于聚合很复杂的警报,可以先写一条 Recording Rule,再针对 Recording Rule 产生的新指标来建警报。这种方式也能帮助我们更高效地去建分级警报(超过不同阈值对应不同的紧急程度)。
Alertmanager 的 group_interval 会影响 resolved 通知
这个设计让“警报消除就立马发送消除通知”变得几乎不可能,因为假如把 group_interval 变得很小的话,警报通知就会过于频繁,而调大的话,就会拖累到消除通知。
这个问题修改一点源码即可解决,不过无伤大雅,不修也完全没问题。
最后一条:不要忘记因何而来
曾经有一段时间,我们追求“监控的覆盖率”,所有系统所有层面,一定要有指标,而且具体信息 label 分得越细越好,最后搞出几千个监控项,不仅搞得眼花缭乱还让 Prometheus 变慢了;
还有一段时间,我们追求“警报的覆盖率”,事无巨细必须要有警报,人人有责全体收警报(有些警报会发送给几十个人)。最后当然你也能预想到了,告警风暴让大家都对警报疲劳了;
这些事情乍看起来都是在努力工作,但其实一开始的方向就错了,监控的目标绝对不是为了达到 xxx 个指标,xxx 条警报规则,这些东西有什么意义?依我看,负责监控的开发就算不是 SRE 也要有 SRE 的心态和视野,不要为监控系统的功能或覆盖面负责(这样很可让导致开发在监控里堆砌功能和内容,变得越来越臃肿越来越不可靠),而要为整个业务的稳定性负责,同时站在稳定性的投入产出比角度去考虑每件事情的性质和意义,不要忘记我们因何而来。
原文链接:https://aleiwu.com/post/prometheus-bp/