Prometheus AlertManager代码阅读笔记

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来进行数据状态的同步。同时集群在接收到告警后也要进行通知,告知其他的节点关于告警的处理状态,防止多个通知同时被发送。

核心组件阅读

Notify组件代码阅读笔记

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值