告警平台2.0——仿出强大

在《告警平台1.0》中,我们实现了告警平台,可以实现纳管通过AlertManager推送的告警信息,然后进行灵活的告警通知发送。

在这个基础上,我们可以实现对告警进行认领、屏蔽、关闭等操作,也能在移动端进行操作。

但是,这个方案现在只能被动的接受告警,对于告警规则还是需要到Prometheus中去配置,当告警规则较多的情况下,配置分类比较麻烦,所以在想:能不能在现有平台上增加规则配置监控功能?

所以,我又到老朋友《快猫Flashcat》上进行学习,它们除了有故障管理,也有告警管理,可以实现监控告警一体化。

而且,它支持的数据源还比较多,如下:

当然,我不需要实现这么多,只要把常用的Prometheus和Elasticsearch实现即可。

逻辑梳理

相对来说,监控规则的实现逻辑还是比较清晰,如下:

本质上就是后台系统周期性的在数据库(Prometheus、Elasticsearch)中通过规则进行查询,当异常条件满足后,就生成对应的告警事件,然后将告警事件分发到协作空间

另外,为了提升告警事件的管理效率,避免频繁查询、更新数据库,当产生告警事件后,会将其存入Redis,告警事件更新会同步更新Redis中的数据。同时,后端会有一个常驻的携程,获取Redis中的告警事件,评估是否需要发送到协作空间。

实现效果

为了便于规则的管理,我们建立了分组,这里的分组没有和协作空间建立必然的联系,只是为了便于管理告警规则。

告警规则就会按组进行分类展示

创建规则,目前支持创建Prometheus和Elasticsearch规则。

创建Prometheus规则。

我们可以:

为规则添加附加标签,比如为了按标签进行告警发送的时候添加biz=a的标签。定义具体的PromQL。指定告警评估表达式,目前支持严重、警告、通知三个表达式。可以配置告警持续时间,只有当告警事件超过持续时间,才会产生真正的告警事件。可以配置通知详情,便于人员阅读。可以配置通知渠道,将事件推送到某个协作空间。

当告警产生后就会发送到对应的渠道,比如发送到企微的消息如下:

创建Elasticsearch规则。

主要配置的地方:

指定数据源:需要配置告警的数据源地址指定索引:针对哪个索引做规则指定筛选字段:通过这些字段进行过滤日志指定标注字段:在发送告警通知的时候会将该部分发送到群里,便于运维开发提取关键信息

其中,添加标注是为了在展示需要的告警信息,比如:

另外,分组评估用户将告警信息进行分组发送,触发条件用于判断是不是发送告警通知。

当告警事件产生,就会发送一条告警通知,如下:

日志监控的告警逻辑也比较简单,如下:

代码实现

对于Prometheus监控规则,定时从Prometheus时序数据库中查询值,然后和配置的策略进行比较,如果满足要求则产生告警事件。

复制
// 从时序数据库中查询数据 resQuery, err = cli.(global.PrometheusProvider).Query(rule.PrometheusConfig.PromQL) // 然后将值进行比较 for _, v: = range resQuery { for _, ruleExpr: = range rule.PrometheusConfig.Rules { // 去除空格 cleanedExpr: = strings.ReplaceAll(ruleExpr.Expr, " ", "") re: = regexp.MustCompile(`([^\d]+)(\d+)`) matches: = re.FindStringSubmatch(cleanedExpr) if len(matches) < 2 { continue } t, _: = strconv.ParseFloat(matches[2], 64) option: = alarmCenter.EvalCondition { Operator: matches[1], QueryValue: v.Value, ExpectedValue: t, } // 进行比较,当满足条件后生成事件 if ok := alarmCenter.EvalCondition(option);ok{ event := alarmCenter.BuildAlertEvent(rule) alarmCenter.SaveAlertEvent(event) } } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.

对于Elasticsearch监控规则,定时从Elasticsearch数据库中查询满足日志条数,当满足告警条件后生成告警事件。

复制
// 从ES中查询值 curAt: = time.Now() startsAt: = utils.ParserDuration(curAt, int(rule.ElasticsearchConfig.Scope), "m") queryOptions: = provider.LogQueryOptions { ElasticSearch: provider.Elasticsearch { Index: rule.ElasticsearchConfig.Index, QueryFilter: rule.ElasticsearchConfig.Filter, Annotations: rule.ElasticsearchConfig.Annotations, GroupEval: rule.ElasticsearchConfig.GroupEval, }, StartAt: utils.FormatTimeToUTC(startsAt.Unix()), EndAt: utils.FormatTimeToUTC(curAt.Unix()), } queryRes, count, err = cli.(global.ElasticsearchProvider).Query(queryOptions) option := alarmCenter.EvalCondition { Operator: rule.ElasticsearchConfig.TriggerCondition.Operator, QueryValue: float64(count), ExpectedValue: rule.ElasticsearchConfig.TriggerCondition.ExpectedValue, } // 进行比较,当满足条件后生成事件 if ok := alarmCenter.EvalCondition(option);ok{ event := alarmCenter.BuildAlertEvent(rule) alarmCenter.SaveAlertEvent(event) }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.

条件评估的代码如下:

复制
func EvalCondition(ec alarmCenter.EvalCondition) bool { // 使用 map 存储操作符对应的比较函数 operatorMap := map[string]func(float64, float64) bool{ ">": func(a, b float64) bool { return a > b }, ">=": func(a, b float64) bool { return a >= b }, "<": func(a, b float64) bool { return a < b }, "<=": func(a, b float64) bool { return a <= b }, "==": func(a, b float64) bool { return a == b }, "=": func(a, b float64) bool { return a == b }, "!=": func(a, b float64) bool { return a != b }, } // 查找并执行对应的比较函数 if compareFunc, exists := operatorMap[ec.Operator]; exists { return compareFunc(ec.QueryValue, ec.ExpectedValue) } global.GVA_LOG.Error(fmt.Sprintf("无效的评估条件,%s:%s:%f", ec.Type, ec.Operator, ec.ExpectedValue)) return false }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.

告警通知的逻辑还是不变,当监听到告警事件后,进行对应的告警通知。

阅读剩余
THE END