深入理解Alertmanager:源碼解讀如何自定義Alert的恢復(fù)時(shí)間
Alertmanager 處理由 Prometheus 服務(wù)器等客戶(hù)端應(yīng)用程序發(fā)送的告警。負(fù)責(zé)對(duì)它們進(jìn)行分組、靜默、抑制、去重并路由到正確的接收方,例如Email、Wechat、Webhook。
Prometheus告警處理邏輯的問(wèn)題
在prometheus告警體系中,在告警策略正常運(yùn)行時(shí),檢測(cè)到有新的符合告警規(guī)則的信息,就產(chǎn)生告警發(fā)送給alertmanager,如果恢復(fù)了,也會(huì)產(chǎn)生恢復(fù)的信息發(fā)送給alertmangaer,這是理想的情況。
如果在告警過(guò)程中有發(fā)生告警規(guī)則的更新,比如發(fā)現(xiàn)告警閾值太低,調(diào)整了閾值,那么在prometheus的更新過(guò)程中,會(huì)丟棄老的評(píng)估信息,直接使用新的評(píng)估規(guī)則再次運(yùn)行評(píng)估,評(píng)估過(guò)程中,如果不會(huì)再產(chǎn)生告警,也不會(huì)產(chǎn)生恢復(fù)信息。
那就會(huì)產(chǎn)生一個(gè)問(wèn)題,以前發(fā)送給Alertmanager的舊規(guī)則生成的告警,不會(huì)收到恢復(fù)了。
總結(jié)下就是:
- 每個(gè)評(píng)估周期持續(xù)發(fā)送告警給Alertmanger。
- 如果有規(guī)則更新,直接新啟goroutine執(zhí)行新的評(píng)估,直接放棄老的規(guī)則和goroutine。
Alertmanager的修復(fù)邏輯
Prometheus評(píng)估后發(fā)送給Alertmanger的firing告警是沒(méi)有結(jié)束時(shí)間的。
[
{
"labels": {
"alert_class": "metric",
"alert_rule_id": "940",
"alert_severity": "1",
"alert_strategy": "cwhistle_demo_00",
"alert_strategy_id": "100",
"alertname": "入流量異常告警",
"city": "chongqing2",
"tcs_instance": "10.27.38.145",
"tcs_product": "clb",
"tcs_type": "clb_tgw_inner_outer"
},
"annotations": {
"query": "barad_tbr{tcs_type=~\"clb_tgw_inner_outer\",tcs_product=~\"clb\",clb_tgw_inner_outer=~\"10.27.38.145|10.27.38.146\"} < 5",
"value": "0.00"
},
"state": "firing",
"activeAt": "2021-01-25T08:31:34.941070644Z",
"value": "0e+00"
}
]
在Alertmanger中,告警的觸發(fā)和恢復(fù)判斷是基于時(shí)間范圍實(shí)現(xiàn)的,Alertmanager中Alert定義如下,自帶時(shí)間范圍。
// Alert is a generic representation of an alert in the Prometheus eco-system.
type Alert struct {
// Label value pairs for purpose of aggregation, matching, and disposition
// dispatching. This must minimally include an "alertname" label.
Labels LabelSet `json:"labels"`
// Extra key/value information which does not define alert identity.
Annotations LabelSet `json:"annotations"`
// The known time range for this alert. Both ends are optional.
StartsAt time.Time `json:"startsAt,omitempty"`
EndsAt time.Time `json:"endsAt,omitempty"`
GeneratorURL string `json:"generatorURL"`
}
當(dāng) Alert.EndsAt < time.Now() 時(shí)判定為恢復(fù)。
// 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的接收過(guò)程中,會(huì)確保每個(gè)Alert的StartsAt和EndsAt有值。
func (api *API) postAlertsHandler(params alert_ops.PostAlertsParams) middleware.Responder {
logger := api.requestLogger(params.HTTPRequest)
alerts := OpenAPIAlertsToAlerts(params.Alerts)
now := time.Now()
api.mtx.RLock()
resolveTimeout := time.Duration(api.alertmanagerConfig.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()
}
}
.....
在上述代碼中可以看到,alert.EndsAt= time.Now() + 全局配置的ResolveTimeout(默認(rèn)5分鐘),也就是每個(gè)Alert默認(rèn)給5分鐘的過(guò)期時(shí)間,過(guò)期就恢復(fù)。
事件告警自定義過(guò)期時(shí)間
默認(rèn)的5分鐘對(duì)于prometheus metric告警是足夠的,但如果想使用基于loki的日志告警(通常為了控制資源消耗,不會(huì)設(shè)置很大的評(píng)估范圍),有時(shí)候偶發(fā)一個(gè)告警,然后很快就恢復(fù)了;或者想基于Event類(lèi)型的事件告警,因?yàn)橛|發(fā)頻率低,且不會(huì)持續(xù)發(fā)送,5分鐘就比較容易誤解。
那么在這里我們就可以基于Label著色和修改過(guò)期時(shí)間的方法自定義事件告警過(guò)期恢復(fù)時(shí)間。
實(shí)現(xiàn)如下:
這樣就可以實(shí)現(xiàn)事件告警的自定義恢復(fù)時(shí)間,同時(shí)可以利用Alertmanager已有的其他功能。