🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
alertmanager集群莫名发送resolve消息的问题探究
术语
- 告警消息:指一条告警
- 告警恢复消息:指一条告警恢复
- 告警信息:指告警相关的内容,包括告警消息和告警恢复消息
问题描述
最近遇到了一个alertmanager HA集群莫名发送告警恢复消息的问题。简单来说就是线上配置了一个一直会产生告警的规则,但却会收到alertmanager发来的告警恢复消息,与预期不符。
所使用的告警架构如下,vmalert产生的告警会通过LB发送到某个后端alertmanager实例。原本以为,接收到该告警的alertmanager会将告警信息同步到其他实例,当vmalert产生下一个相同的告警后,则alertmanager实例中的第二个告警会刷新第一个告警,后续通过告警同步将最新的告警发送到各个alertmanager实例,从而达到抑制告警和抑制告警恢复的效果(。
但在实际中发现,alertmanager对一直产生的告警发出了告警恢复消息。
问题解决
问题解决办法很简单:让告警直接发送到alertmanager HA集群的每个实例即可。
在Question regarding Loadbalanced Alertmanager Clusters和Alerting issues with Alertmanage这两篇文档中描述了使用LB导致alertmanager HA集群发生告警混乱的问题。此外在官方文档也有如下提示:
It’s important not to load balance traffic between Prometheus and its Alertmanagers, but instead, point Prometheus to a list of all Alertmanagers.
但根因是什么,网上找了很久没有找到原因。上述文档描述也摸棱两可。
问题根因
首先上一张alertmanager官方架构图:
注意到上图有三类provider:
- Alert Provider:负责处理通过API传入的告警,vmalert(Prometheus)产生的告警就是在这里接收处理的
- Silence Provider:负责处理静默规则,本次不涉及告警静默,因此不作讨论。
- Notify Provider:负责实例间发送告警信息。
根据如上分析,可以得出,一个alertmanager实例有两种途径获得告警信息,一种是由外部服务(如vmalert、Prometheus等)通过API传入的,另一种是通过alertmanager 实例间的Notification Logs消息获得的。
注:需要说明的是,alertmanager判定一个告警是不是恢复状态,主要是通过该告警的
EndsAt
字段,如果EndsAt
时间点早于当前时间,说明该告警已经失效,需要发送告警恢复,判断代码如下:// Resolved returns true iff the activity interval ended in the past. func (a *Alert) Resolved() bool { return a.ResolvedAt(time.Now()) } // ResolvedAt returns true off the activity interval ended before // the given timestamp. func (a *Alert) ResolvedAt(ts time.Time) bool { if a.EndsAt.IsZero() { return false } return !a.EndsAt.After(ts) }
API Provider的处理
alertmanager提供了两套API:v1和v2。但两个API内部处理还是一样的逻辑,以v1 API为例,
入口函数为insertAlerts
,该函数主要负责告警的有效性校验,处理告警的StartAt
和EndAt
,最后通过Put
方法将告警保存起来。
本案例场景中,vmalert会给所有告警加上
EndAt
,值为:当前时间 + 4倍的groupInterval(默认1min) = 4min。
func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) {
now := time.Now()
api.mtx.RLock()
resolveTimeout := time.Duration(api.config.Global.ResolveTimeout)
api.mtx.RUnlock()
for _, alert := range alerts {
alert.UpdatedAt = now
// Ensure StartsAt is set.
if alert.StartsAt.IsZero() {
if alert.EndsAt.IsZero() {
alert.StartsAt = now
} else {
alert.StartsAt = alert.EndsAt
}
}
// If no end time is defined, set a timeout after which an alert
// is marked resolved if it is not updated.
if alert.EndsAt.IsZero() {
alert.Timeout = true
alert.EndsAt = now.Add(resolveTimeout)
}
if alert.EndsAt.After(time.Now()) {
api.m.Firing().Inc()
} else {
api.m.Resolved().Inc()
}
}
// Make a best effort to insert all alerts that are valid.
var (
validAlerts = make([]*types.Alert, 0, len(alerts))
validationErrs = &types.MultiError{}
)
for _, a := range alerts {
removeEmptyLabels(a.Labels)
if err := a.Validate(); err != nil {
validationErrs.Add(err)
api.m.Invalid().Inc()
continue
}
validAlerts = append(validAlerts, a)
}
if err := api.alerts.Put(validAlerts...); err != nil {
api.re