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組件代碼閱讀筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章