Alertmanager发送告警原理
Author: Lijb
为什么要 Alertmanager?
我们先介绍一点背景知识,Prometheus 生态中的警报是在 Prometheus Server 中计算警报规则(Alert Rule)并产生的,而所谓计算警报规则,其实就是周期性地执行一段 PromQL,得到的查询结果就是警报,比如:
node_load5 > 20
这个 PromQL 会查出所有”在最近一次采样中,5分钟平均 Load 大于 20”的时间序列。这些序列带上它们的标签就被转化为警报。
只是,当 Prometheus Server 计算出一些警报后,它自己并没有能力将这些警报通知出去,只能将警报推给 Alertmanager,由 Alertmanager 进行发送。
这个切分,一方面是出于单一职责的考虑,让 Prometheus “do one thing and do it well”, 另一方面则是因为警报发送确实不是一件”简单”的事,需要一个专门的系统来做好它。可以这么说,Alertmanager 的目标不是简单地”发出警报”,而是”发出高质量的警报”。它提供的高级功能包括但不限于:
- Go Template 渲染警报内容;
- 管理警报的重复提醒时机与消除后消除通知的发送;
- 根据标签定义警报路由,实现警报的优先级、接收人划分,并针对不同的优先级和接收人定制不同的发送策略;
- 将同类型警报打包成一条通知发送出去,降低警报通知的频率;
- 支持静默规则: 用户可以定义一条静默规则,在一段时间内停止发送部分特定的警报,比如已经确认是搜索集群问题,在修复搜索集群时,先静默掉搜索集群相关警报;
- 支持”抑制”规则(Inhibition Rule): 用户可以定义一条”抑制”规则,规定在某种警报发生时,不发送另一种警报,比如在”A 机房网络故障”这条警报发生时,不发送所有”A 机房中的警报”;
假如你很忙,那么读到这里就完全 OK 了,反正这类文章最大的作用就是让我们”知道有 X 这回事,大概了解有啥特性,当有需求匹配时,能想到试试看 X 合不合适“,其中 X = Alertmanager。当然,假如你是个好奇宝宝,那么还可以看看下面的解析。
Alertmanager 内部架构
先看官方文档中的架构图:
从左上开始,Prometheus 发送的警报到 Alertmanager;
- 警报会被存储到 AlertProvider 中,Alertmanager 的内置实现就是包了一个 map,也就是存放在本机内存中,这里可以很容易地扩展其它 Provider;
- Dispatcher 是一个单独的 goroutine,它会不断到 AlertProvider 拉新的警报,并且根据 YAML 配置的 Routing Tree 将警报路由到一个分组中;
- 分组会定时进行 flush (间隔为配置参数中的 group_interval), flush 后这组警报会走一个 Notification Pipeline 链式处理;
- Notification Pipeline 为这组警报确定发送目标,并执行抑制逻辑,静默逻辑,去重逻辑,发送与重试逻辑,实现警报的最终投递;
下面就分开讲一讲核心的两块:
- Dispatcher 中的 Routing Tree 的实现与设计意图
- Notification Pipeline 的实现与设计意图
Routing Tree
Routing Tree 的是一颗多叉树,节点的数据结构定义如下:
// 节点包含警报的路由逻辑
type Route struct {
// 父节点
parent *Route
// 节点的配置,下文详解
RouteOpts RouteOpts
// Matchers 是一组匹配规则,用于判断 Alert 与当前节点是否匹配
Matchers types.Matchers
// 假如为 true, 那么 Alert 在匹配到一个节点后,还会继续往下匹配
Continue bool
// 子节点
Routes []*Route}
具体的处理代码很简单,深度优先搜索:警报从 root 开始匹配(root 默认匹配所有警报),然后根据节点中定义的 Matchers 检测警报与节点是否匹配,匹配则继续往下搜索,默认情况下第一个”最深”的 match (也就是 DFS 回溯之前的最后一个节点)会被返回。特殊情况就是节点配置了 Continue=true,这时假如这个节点匹配上了,那不会