AlertManager用于接收Prometheus发送的告警并对于告警进行一系列的处理后发送给指定的用户。系统的整体设计图如下面所示,并且支持HA高可用部署。
AlertManager接收告警
Prometheus或者告警发送系统可以通过API的方式发送给Alertmanager,收到告警后将告警分别存储在AlertProvider中(当前实现是存储在内存中,可以通过接口的方式自行实现其他存储方式比如MySQL或者ES)。
# api/api.go
r.Get("/alerts", wrap(api.listAlerts))
r.Post("/alerts", wrap(api.addAlerts))
func (api *API) insertAlerts(w http.ResponseWriter, r *http.Request, alerts ...*types.Alert) {
...
for _, a := range alerts {
removeEmptyLabels(a.Labels)
if err := a.Validate(); err != nil {
validationErrs.Add(err)
numInvalidAlerts.Inc()
continue
}
validAlerts = append(validAlerts, a)
}
if err := api.alerts.Put(validAlerts...); err != nil {
api.respondError(w, apiError{
typ: errorInternal,
err: err,
}, nil)
return
}
...
}
AlertManager内部的Dispatcher通过订阅的方式获得告警信息更新(获得Alerts的迭代器,通过for循环不断的获得发送到信道中的Alerts,通过route的match函数获得匹配的route对象(比如基于标签的正则表达,传递到不同的邮件或者slack信道路由),并且每隔一段时间将执行一次清理操作(当ag中的告警数量为空的时候),删除之前的记录。收到的Alert通过标签匹配的方式被送到不同的聚合组中等待Pipeline流程进行处理。
func (d *Dispatcher) run(it provider.AlertIterator) {
...
for {
select {
case alert, ok := <-it.Next():
if !ok {
// Iterator exhausted for some reason.
...
for _, r := range d.route.Match(alert.Labels) {
d.processAlert(alert, r)
}
case <-cleanup.C:
d.mtx.Lock()
for _, groups := range d.aggrGroups {
for _, ag := range groups {
if ag.empty() {
ag.stop()
delete(groups, ag.fingerprint())
}
}
}
d.mtx.Unlock()
case <-d.ctx.Done():
return
}
}
}
聚合组用来管理具有相同属性信息的告警,通过将相同类型的告警进行分组可以统一的管理,因为有时候告警处理是大量同时出现的(比如一个数据中心的失效将导致成百上千的告警产生,通过分组可以聚合相同标签到一个邮件或者接收者中)。分组创建将依赖于处理route路由和告警的labels标签,不同的告警labels将产生不同的聚合组,所有接受到的告警将首先计算一个聚合组的Fingerprint如果找到则直接插入到该组,否则创建一个新的聚合组,每次新创建的聚合组都会启动一个goroutine来执行实际的pipeline work.
# dispatch/dispatch.go
# processAlert()
groupLabels := model.LabelSet{}
for ln, lv := range alert.Labels {
if _, ok := route.RouteOpts.GroupBy[ln]; ok {
groupLabels[ln] = lv
}
}
fp := groupLabels.Fingerprint()
d.mtx.Lock()
group, ok := d.aggrGroups[route]
if !ok {
group = map[model.Fingerprint]*aggrGroup{}
d.aggrGroups[route] = group
}
d.mtx.Unlock()
每一个聚合组在管理告警上都会通过内部的run方法来不断的循环获取一段时间内的告警,并对于时间段的告警进行聚合处理,如下面的代码所示,当接收到完成信号时候退出run方法结束该组。默认的GroupInterval时间为5分钟
func (ag *aggrGroup) run(nf notifyFunc) {
ag.done = make(chan struct{})
defer close(ag.done)
defer ag.next.Stop()
for {
select {
case now := <-ag.next.C:
// Give the notifications time until the next flush to
// finish before terminating them.
ctx, cancel := context.WithTimeout(ag.ctx, ag.timeout(ag.opts.GroupInterval))
...
ag.flush(func(alerts ...*types.Alert) bool {
return nf(ctx, alerts...)
})
cancel()
case <-ag.ctx.Done():
return
}
}
}
执行的flush函数将首先对于alerts进行排序(依赖于job和instance),排序后的alerts组将被传递给notify函数进行处理
Pipeline
Pipeline用来定义告警处理流程,Alertmanager当前对于告警处理支持的流程包括:Inhibitor, Silencer。
Inhibit 管理
Inhibitor用于管理相同的告警配置,比如下面的配置定义了当告警名称alertname一致的时候,如果严重告警存在的时候,途同级别告警将被过滤掉。
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
# Apply inhibition if the alertname is the same.
equal: ['alertname']
查询流程上将获得的alert的label进行检查,匹配检查的内容满足target匹配但是source不匹配的标记为Inhibited.
// Mutes returns true iff the given label set is muted.
func (ih *Inhibitor) Mutes(lset model.LabelSet) bool {
fp := lset.Fingerprint()
for _, r := range ih.rules {
// Only inhibit if target matchers match but source matchers don't.
if inhibitedByFP, eq := r.hasEqual(lset); !r.SourceMatchers.Match(lset) && r.TargetMatchers.Match(lset) && eq {
ih.marker.SetInhibited(fp, inhibitedByFP.String())
return true
}
}
ih.marker.SetInhibited(fp)
return false
}
其中的inhibited.marker是一个结构体对象实现了Marker接口,结构对象定义如下,通过这个接口实现,可以用来管理告警状态比如设置Inhibited和Silieced状态,获取统计信息和按状态列出指定的类别告警:
// Marker helps to mark alerts as silenced and/or inhibited.
// All methods are goroutine-safe.
type Marker interface {
SetActive(alert model.Fingerprint)
SetInhibited(alert model.Fingerprint, ids ...string)
SetSilenced(alert model.Fingerprint, ids ...string)
Count(...AlertState) int
Status(model.Fingerprint) AlertStatus
Delete(model.Fingerprint)
Unprocessed(model.Fingerprint) bool
Active(model.Fingerprint) bool
Silenced(model.Fingerprint) ([]string, bool)
Inhibited(model.Fingerprint) ([]string, bool)
}
Silence管理
Silencer用来取消告警,比如直接配置告警在某一段时间内不触发任何消息,可以基于正则表达式的匹配,该配置可以通过alertmanager的WebUI或者API接口配置。
下面的代码是Pipeline中执行的过程,当流程传递到Silence步骤时候,Silence模块将循环检查每一个告警是否满足匹配,比如设置某一个告警标签出现后取消告警。当查询结束后返回一个sils(Silence的结构体,用来指定某一类告警的Silence在一段时间内的处理对象。)一个告警可能会被多个Silence同时管理。
func (n *SilenceStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) {
var filtered []*types.Alert
for _, a := range alerts {
// TODO(fabxc): increment total alerts counter.
// Do not send the alert if the silencer mutes it.
sils, err := n.silences.Query(
silence.QState(types.SilenceStateActive),
silence.QMatches(a.Labels),
)
if err != nil {
level.Error(l).Log("msg", "Querying silences failed", "err", err)
}
if len(sils) == 0 {
// TODO(fabxc): increment muted alerts counter.
filtered = append(filtered, a)
n.marker.SetSilenced(a.Labels.Fingerprint())
} else {
ids := make([]string, len(sils))
for i, s := range sils {
ids[i] = s.Id
}
n.marker.SetSilenced(a.Labels.Fingerprint(), ids...)
}
}
return ctx, filtered, nil
}
同时要实现集群管理,彼此之间的Silence状态也要共享(告警发送给多个AM),因此系统设计的时候加入了SilenceProvider来进行集群之间的Silence管理,彼此之间通过protoBuf来进行数据状态的同步。同时集群在接收到告警后也要进行通知,告知其他的节点关于告警的处理状态,防止多个通知同时被发送。