Prometheus 与 nodata 告警

Prometheus 与 nodata 告警

背景

随着云原生和高动态服务端的发展,在运维领域,以 Prometheus 为代表的现代时间序列存储正在加速替代以 Zabbix 为代表的传统监控系统。运维领域在享受时间序列技术发展红利的同时,也面临时间序列管理思路上的转变和监控系统实际应用的上一些难点 —— nodata 告警便是其中之一。nodata 告警是传统监控系统的必备功能,但却缺席了几乎所有现代时间序列存储实践,这给运维监控带了诸多缺陷。本文尝试分析其中原因,并给出一些可能的解决方法。

nodata 告警触发器的特殊性与必要性

nodata 告警触发器(Trigger)与普通告警触发器相比具有原生的特殊性。普通告警触发器的作用是对一组监控指标(Metric)的过滤,通常是基于数值大小的过滤。

如果存在如下表示的监控数值集合 {M}
{M={ \left\{ {m\mathop{{}}\nolimits_{{1}},m\mathop{{}}\nolimits_{{2}},...,m\mathop{{}}\nolimits_{{n}}} \right\} }}
告警触发器 {T\mathop{{}}\nolimits_{{gt100}}} 可对集合 {M} 中的元素做『大于 100』的过滤
{T_{{gt100}}{ \left( {M} \right) }={ \left\{ {x \left| x \in M,x > 100\right. } \right\} }}
普通告警触发器触发的告警集合 {A}
A=T_{gt100} (M)
某个普通告警触发器作用于某组监控数值后,产生普通告警集合的过程如上文所述。nodata 告警触发器的工作需要引入额外的全集 {U}
U=\{m_{1},m_{2},...,m_{n^ \prime }\}
nodata 告警触发器 T_{nodata} 对集合 {M} 求绝对补集
T_{nodata}(M)=\left\{ x|x\in U,x\notin M \right\}
nodata 告警触发器触发的告警集合 A_{nodata}
A_{nodata}=T_{nodata}(M)=\bar{M}
一般来说,运维监控场景下我们希望得到的完整告警集合 A_{all}
A_{all}=A\cup A_{nodata}
从上文看出,nodata 告警触发器与普通告警触发器最大的区别是前者需要引入『全集』U ,全集应当从监控系统之外获得,以保证监控系统本身的有效性。

运维监控场景下,发生 nodata 告警最大的可能性是监控系统本身的失效,比如采集点失效或采集对象失效,在我们的实践中,服务器意外下线、磁盘故障、服务崩溃等都会导致 nodata 告警;另外还有一类监控指标,这类指标以 nodata 为『正常状态』,如 5xx code 产生的速率,在没有 5xx code 产生时,虽然我们希望指标的数值为 0 (而不是 nodata) ,但在实践中往往很难保证,对于这类指标有效性的保证,我们会在其他文章中详细说明。

nodata 告警触发器的难点之一在于全集 U 的获取。在高动态的服务端环境中,往往很难得到『全部服务器集合』、『全部 IP 地址集合』、『全部 Pod 集合』、『某服务全部运行实例集合』这样的全集。所以,在数值型的监控采集之外,必须建设更加结构化的信息组织方式,并配以自动、半自动与人工相结合的信息维护方法。假如在结构化信息中很难方便准确地获取『全部某某集合』这样的信息,就无法制作真正有效地 nodata 告警触发器。

nodata 告警触发器的另一个难点是计算的开销大。普通告警触发器对指标的数值过滤,可以通过『带条件的查询』做到,这本质上是将告警计算的开销一次性卸载到时间序列存储系统中,而现代的时间序列存储系统一般都支持这样做。由上文对 nodata 告警触发器的定义可以得到,nodata 的计算必须在数值过滤之前,也就是说 nodata 告警计算的计算对象是全量的监控指标,对全量监控指标求补集本身是一个开销巨大的计算。另外全集 U 并不存在于时间序列存储中(否则 nodata 告警就失去了客观性),把全集 U 带入 nodata 告警计算可会给时间序列存储带来额外的传输与计算压力。

虽然有诸多困难,但 nodata 告警的重要性不言而喻。如果没有 nodata 告警,监控指标的失效是静默的,监控系统本身的有效性无法得到保证。对于云原生的服务端环境,监控对象的动态化程度更高,虽然可以制作更加宏观的监控指标(如某类 Pod 的总实例数),但 nodata 告警可以帮助我们获悉更加微观的服务端运行工况。

在 OpsMind 的实践中,我们使用 CMDB 和经典的 CMDB 方法来获得全集 U ,并改造 Prometheus ,将 nodata 计算卸载到存储层。下文结合我们的实践,并尽可能剥离我们特殊的业务场景,以 Prometheus 为例,介绍几个相对通用的 nodata 告警触发器的实现思路。

单一维度的 nodata

单一维度的 nodata 是最常见的 nodata 告警触发器,Zabbix 等传统监控系统提供的也是这类 nodata 功能。以服务器 Load 监控为例

存在服务器集合 H
{H={ \left\{ {h\mathop{{}}\nolimits_{{1}},h\mathop{{}}\nolimits_{{2}},...,h\mathop{{}}\nolimits_{{n}}} \right\} }}
存在监控点 load
load(h)=cpu\ load5\ of\ h
便可以得到 Load 监控指标 L
{L={ \left\{ l_{1}, l_{2}, ...,l_{i},...l_{n}|l_{i}=load(h_{i}) \right\} }}
当监控指标 L 中存在失效的监控点时,L 变为不完整的指标 L^ \prime
L^ \prime= { \left\{ l_{1}, l_{2}, ...,l_{i},...l_{n^ \prime}|l_{i}=load(h_{i}) \right\} }

L^ \prime \subseteq L

如果集合 H 在时间序列存储之外(例如,存储于 CMDB 中),就可以将 H 认定为 nodata 的全集 U,而 nodata 的告警集合 A_{nodata}L^ \prime 的绝对补集
A_{nodata}=\bar{L^ \prime}
在我们的实践中,为了将 nodata 的补集运算卸载到 Prometheus,我们将 CMDB 作为一个监控点,由 Prometheus 向 CMDB 拉取全集 H ,具体的指标类似于

nodata_hosts{host="h1", nodata="True"} 1
nodata_hosts{host="h2", nodata="True"} 1
nodata_hosts{host="h3", nodata="True"} 1
...

同时,假设 Load 监控指标 L 类似于

host_cpu_load5{host="h1"} 42
host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
...

我们针对 L 生成如下告警触发器

host_cpu_load5{host=~"h.*"} > 42

则卸载 nodata 之后的运算可表示为

host_cpu_load5{host=~"h.*"} or on(host) nodata_hosts{host=~"h.*"} * 1/0 > 42

此告警触发器可以生成如下的告警信息

host_cpu_load5{host="h2"} 43
host_cpu_load5{host="h3"} 44
host_cpu_load5{host="hx", nodata="True"} +inf

这里有如下几个关键点

  1. 将 CMDB 中的结构化信息转储到 Prometheus
  2. 使用 or 运算符做补集运算
  3. on() 的 label 为 nodata 的单一维度
  4. 普通指标与 nodata 指标在 nodata 维度上的查询条件一致
  5. or 之后的表达式通过 * 1/0 转为 +inf 以保证数值条件成立

我们通过将全集 H 转为监控指标,并通过 or 运算符做补集运算实现了单一维度的 nodata 告警触发器。

多维度正交的 nodata

多维度正交 nodata 也是运维监控场景中的常见需求。假设有 n 台服务,每台服务器上都运行相同的一组 m 个服务实例,那么对于服务的监控指标,就需要在服务器和服务两个维度上做 nodata 计算。问题描述如下

服务器集合
H={ \left\{ h_1,h_2,...h_n \right\} }
服务实例集合
S={\left\{s_1,s_2,...s_m \right\}}
监控点 qps 用来获取服务实例的每秒访问次数
qps(h_i, s_j) = qps\ of\ service\ s_j\ on\ server\ h_i
得到 Qps 监控指标 Q
Q={\left\{q_{1,1},q_{1,2},q_{2,1},... ,q_{i,j}, ..., q_{n,m}|q_{i,j}=qps(h_i, s_j) \right\}}
当监控指标 Q 存在监控点失效时,Q 变为不完整的监控指标 Q^ \prime
Q={\left\{q_{1,1},q_{1,2},q_{2,1},... ,q_{i,j}, ..., q_{n^ \prime,m^ \prime}|q_{i,j}=qps(h_i, s_j) \right\}}

Q \subseteq Q^ \prime

与单维度类似,我们转储服务的全集指标

nodata_services{service="s1", nodata="True"} 1
nodata_services{service="s2", nodata="True"} 1
nodata_services{service="s3", nodata="True"} 1
...

假设监控指标 Q 类似于

service_qps{host="h1", service="s1"} 42
service_qps{host="h1", service="s2"} 43
service_qps{host="h2", service="s1"} 44
...

则对于监控指标 Q 的一个告警触发器类似于

service_qps{host=~"h.*", service=~"s.*"} > 42

卸载 nodata 计算之后的告警触发器

service_qps{host=~"h.*", service=~"s.*"} or on(host, service) absent(nodata_hosts{host=~"h.*"}, nodata_services{service=~"s.*"}) * 1/0 > 42

此告警触发器可以生成如下告警信息

service_qps{host="h1", service="s2"} 43
service_qps{host="h2", service="s1"} 44
service_qps{host="hx", service="s1", nodata="True"} +inf
service_qps{host="h1", service="sx", nodata="True"} +inf
...

除了与单一维度 nodata 类似的关键点之外,这里还有如下几个关键点:

  1. 重写 Prometheus 的 absent 函数支持多 vector 的正交计算
  2. on() 的 label 为多个 nodata 的计算维度

我们通过与单一维度 nodata 类似的手法实现了支持多维度正交的 nodata 告警触发器。

更一般的多维度 nodata

多维度 nodata 更一般的表述是多维度之间无法形成正交关系的情况。这些情况较难处理,需要 CMDB 与时间序列存储建立较密切的联系(而非简单的数据转储),但如果可以处理得当,可以大大增强监控系统的能力。

假设有服务器集合
H={ \left\{ h_1,h_2,...h_n \right\} }
服务器 h_i 上的服务实例集合
S_i={\left\{ s_{i,1},s_{i,2}, ..., s_{i,j}, ..., s_{i,m}|i \in H \right\}}
监控指标 Q
Q={\left\{ q_{i,j}|i \in H, j \in S_i \right\}}
针对这个监控指标的一个告警触发器

service_qps{host=-"h1,h2,h3"service=-"s1,s2"} > 42

注意这里我们扩展了 PromQL 的语法,支持『列表匹配』=-,关于这个语法带来的功能和性能的优化本文暂不赘述。

为了对 Q 具有这类复杂多维度关系的指标,我们需要在 CMDB 中建立服务器与服务实例的关系表

ID 服务器 服务实例
0 h1 s1
1 h1 s2
2 h2 s1
3 h3 s1

通过 CMDB 中的关系表,将 nodata 卸载后的告警触发器为

service_qps{host=-"h1[0,1],h2[2],h3[3]"service=-"s1[0,2,3],s2[1,3]"} > 42

这里我们扩展了列表匹配的语法,支持 "nodata key" [0,1],表示某个列表项在全集中的 ID。Promethues 查询时会针对每个 label 计录缺失列表项的 nodata key,并将多个 label 记录下来的 nodata key 求并集。

对本例来说,假设 h1 上的 s2 和 h2 上的 s1 监控失效,则 host label 缺失的列表项为 h2(并不包含 h1,因为 h1 下的 s1 有数值),对应的 nodata key 为
{\left\{ 2 \right\}}
service label 上缺失的列表项为 s2(并不包含 s1,因为 h1 下的 s1 有数值),对应的 nodata key 为
{\left\{ 1 \right\}}
并集的结果为
A_{nodata} = {\left\{ 2 \right\}} \cup {\left\{ 1\right\}} = {\left\{ 1,2\right\}}
这表示全集中 ID 为 12 的条目在时间序列中没有数值,需要做 nodata 告警,与我们的假设相符。

结语

本文分析了现代时间序列管理方案中 nodata 的特殊性与必要性,并以 Prometheus 为例尝试给出几个解决方案。可以看到,现代时间序列管理方案中的 nodata 处理是十分复杂的,我们认为这和云原生环境下其他的监控难题一样具有原生复杂性,这只是云原生给运维带来的诸多根本性挑战的外在表现形式,这些挑战需要系统性地分析和解决。OpsMind 为应对云原生的挑战做了大量的技术研判和产品包装,我们将在其他文章中与大家陆续分享。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容