开源夜莺监控DIY发版时告警静默
年初时,我司已使用开源夜莺 v8 版本构建了公司级统一监控系统,但一直遗留了一个问题未被解决,就是发版时告警降噪。
据我们观察,50% 的告警噪声是由发版导致的,如果能够把监控系统和变更系统联动,发版时自动静默相关告警,那就太棒了。
发版时,通常是有人盯着服务仪表盘的,此时服务如果有问题可以及时发现。发版时的告警通知意义不大,所以我们一直想静默掉。
原始思路
起初,我们设想的是:把发版事件转成 Prometheus 指标,然后给相关告警规则都加上 and 条件,只有不发版时才会触发告警,举例:
rate(http_requests_total{service_name="gohttpd", status=~"5.."}[1m]) > 10
and on(service_name, env)
service_updating{} == 0
但由于我们的告警规则数量很多,上百个应用多维度告警都是精确到各小组,这种给相关规则配置 and 条件的做法工作量很大,还要处理好发版事件 Prometheus 指标的转换,所以迟迟未实施。
夜莺 Pipeline 新功能带来新的可能性
六月新发布的夜莺 v8.beta11 版,引入了告警事件 Pipeline 能力。简单阅读文档了解了下 Pipeline 功能,完美,正是我想要的,脑海中浮现出了一个个用法,当然首先是发版静默的实现。
下面我简单概况下 Pipeline 功能,方便大家理解后文理解,具体可以参阅夜莺官方文档(最新版本直接在页面上就可以看到文档)。
开源版本共放出了四个 Pipeline 处理器,分别是:
- Relabel:对告警事件的标签进行各种操作,包括删除、保留、重命名、合并和修改标签值
- Callback:通过 HTTP 调用将告警事件信息发送到外部服务。用于通知外部系统或触发自动化处理流程
- Event Update:通过 HTTP 调用外部服务来动态更新告警事件的内容。当告警事件流经此处理器时,处理器会将事件数据发送到指定的 URL,并根据外部服务的响应来更新事件信息
- Event Drop:根据自定义的逻辑条件来决定是否删除特定的告警事件
Relabel 和 Callback 功能其实之前版本就有了,这里不多介绍,新版本把它们封装成了事件管道处理器,使用上更便捷灵活。
重点是 Event Update 和 Event Drop 两个处理器,此次对我帮助很大。Event Update 用于附加一些额外信息到告警事件中,比如:把事件交给 AI 分析,得到一些结论性质的信息,附加到事件中;去 CMDB 查询一些元信息,附加到事件中。Event Drop 可以通过告警事件中的字段判断是否删除告警事件。
这些处理器是可以编排使用的,所以发版降噪的实现思路就有了。
🎯 分享一条近期思考:十年磨一剑,运维监控、可观测性领域创业,拼的是产品细节和交付迭代能力
新思路
通过 Event Update 处理器把告警事件发给发版外部接口,外部接口查询服务和环境所匹配的发版事件,查询到则给原始 event 事件标签中添加 updating=true
字段(据说更好的实践是把附加的信息放到 annotations 中);然后交给 Event Drop 处理器,Event Drop 处理器查询到 updating=true
则丢弃告警。
特别说明一下,任何发版事件都是可追溯的,如 Jenkins 和 ArgoCD 等都可以通过接口获取变更事件,没有 CICD 系统的也可以通过命令查询服务启动时间来获取。我们有自研发版系统,所有发版事件都已存入了MySQL,所以查询起来更方便点。此处的外部接口只要能获取到发版事件,并附加到原始 evnet 中返回即可。
发版静默实现
上面已经介绍了实现思路,这里展开介绍下实现流程。
1. 创建 Event Update 事件管道处理器
可以设置下过略条件,我这里设置了告警规则里包含 应用监控
的才匹配此规则。
重点是这个 /n9e_pipeline/cd_denoiser
接口的实现,下面再展开讲下,先往下配置。
2. 创建 Event Drop 事件管道处理器
Event Drop 的判断逻辑:使用 go template 语法,如果最后显示为 true,将会将 event 在此环节丢弃。
我这里的语法含义就是把带有 updating=true
标签的事件丢弃:
{{ if eq $labels.updating "true" }}true{{ else }}false{{ end }}
3. 修改或创建通知规则,关联事件管道处理器
事件处理那栏添加事件处理,选择上面添加的 Event Update 和 Event Drop 处理器即可,会自动按顺序执行处理器。
4. 告警规则里选上调整后的通知规则
哪些应用层面的告警规则想要关联这个通知规则,直接在告警规则里选择上面的通知规则即可。这里就算全部配置完了,配置很简单。
新版夜莺的“通知规则”、“通知媒介”、“消息模板”等一系列概念,使用很灵活便捷,理解之后能轻松配置各种想要的通知方式。但没有银弹,缺点就是对新手不友好,需要花时间理解下这里的流转逻辑,本文不作介绍,可参考官方文档。
5. Event Update 外部接口实现
下面具体讲下 Event Update 外部接口的实现。
Event Update 通过给原始 event 事件附加信息来更新事件,所以我们得先知道原始 event 长什么样,外部接口接收夜莺的 Event Update 事件,打印出body信息即是原始 event,如下:
{
"id": 4107,
"cate": "prometheus",
"cluster": "prometheus-skywalking",
"datasource_id": 2,
"group_id": 13,
"group_name": "🅰️应用监控|上游融资",
"hash": "ea724a85d3c27d17e006c3a837046151",
"rule_id": 1992,
"rule_name": "【应用监控|receive-server】JVM-GC次数大于100",
"rule_note": "",
"rule_prod": "metric",
"rule_algo": "",
"severity": 3,
"prom_for_duration": 180,
"prom_ql": "instance_jvm_young_gc_count{service='receive-server', layer='GENERAL'} > 100",
"rule_config": {
"algo_params": null,
"inhibit": false,
"prom_ql": "",
"queries": [
{
"prom_ql": "instance_jvm_young_gc_count{service='receive-server', layer='GENERAL'} > 100",
"recover_config": {
"judge_type": 0,
"recover_exp": ""
},
"severity": 3,
"unit": "milliseconds"
}
],
"severity": 0
},
"prom_eval_interval": 60,
"callbacks": [],
"runbook_url": "",
"notify_recovered": 1,
"target_ident": "SaaS-PRO",
"target_note": "",
"trigger_time": 1749722880,
"trigger_value": "106",
"trigger_values": "",
"trigger_values_json": {
"values_with_unit": {
"v": {
"value": 106,
"unit": "ms",
"text": "106.00 ms",
"stat": 106
}
}
},
"tags": [
"__name__=instance_jvm_young_gc_count",
"ident=SaaS-PRO",
"layer=GENERAL",
"rulename=【应用监控|receive-server】JVM-GC次数大于100",
"scope=ServiceInstance",
"service=receive-server",
"service_instance=instance1"
],
"tags_map": {
"__name__": "instance_jvm_young_gc_count",
"ident": "SaaS-PRO",
"layer": "GENERAL",
"rulename": "【应用监控|receive-server】JVM-GC次数大于100",
"scope": "ServiceInstance",
"service": "receive-server",
"service_instance": "instance1"
},
"annotations": {},
"is_recovered": false,
"last_eval_time": 1749722982,
"last_sent_time": 1749722982,
"notify_cur_number": 1,
"first_trigger_time": 1749722880,
"status": 0,
"claimant": "",
"sub_rule_id": 0,
"extra_info": null,
"rule_hash": "15efd67aa7a6c305a159f4b847ab256d",
"extra_info_map": null,
"notify_rule_ids": [
57
],
"notify_version": 0,
"notify_rules": null
}
可以看到,event 中有告警信息的所有详情。所以接下来要做什么就很清晰了,可以把任何关注的信息塞进去,让告警发出来或者供夜莺的后续事件处理器处理,比如把 CMDB 元信息、Deepseek 分析结果塞进去。
我这里做的就是查询发版系统对应服务对应环境的发版记录,5分钟内有发版则添加 updating=true
标签,认为需要静默。由于发版系统是Django写的,所以 Event Update 接口 /n9e_pipeline/cd_denoiser
也直接写在了发版系统的 Django 代码里,如下:
def cd_denoiser(request):
event = json.loads(request.body.decode('utf-8'))
logger.info("Received event: %s", request.body.decode('utf-8'))
# 提取n9e告警事件的信息,就是把上面打出的evnet body字段重新组织提取出重点信息
key_info = extract_key_info(event)
# 获取报警规则名、报警主机名
rule_name = key_info.get('rule_name', 'Unknown')
hostname = key_info.get('hostname', 'Unknown')
# 获取不到环境信息时,直接返回原事件
if hostname == 'Unknown':
return JsonResponse(event)
env = HOST_ENV_MAP.get(hostname, 'Unknown')
if env == 'Unknown':
return JsonResponse(event)
# 查询发版系统5分钟内相应发版事件
job_count = 0
if 'Dubbo掉线' not in rule_name:
# 提取出应用名称,如receive-server,如未知应用则不处理。
# rule_name格式如:【应用监控|receive-server】JVM-GC时间大于10s
app_name = rule_name.split('|')[1].split('】')[0] if '|' in rule_name else 'Unknown'
if app_name == 'Unknown':
return JsonResponse(event)
# 对应app_name,对应env,5分钟之内的发版数量
start_time = datetime.now() - timedelta(minutes=5)
job_count = tw_jobs.objects.filter(
job_servicename=app_name,
job_envno=env,
job_started__gte=start_time
).count()
else:
# dubbo掉线告警,查询下此环境5分钟内全部发版,有任何发版就丢掉
start_time = datetime.now() - timedelta(minutes=5)
job_count = tw_jobs.objects.filter(
job_envno=env,
job_started__gte=start_time
).count()
# 查询结果为0,说明无发版,直接返回原事件
if job_count == 0:
return JsonResponse(event)
# 查询结果不为0,说明有发版,打上标签
if 'tags_map' not in event:
event['tags_map'] = {}
event['tags_map']['updating'] = 'true'
if 'tags' not in event:
event['tags'] = []
event['tags'].append('updating=true')
logger.info(f"发现近期发版事件,标签更新为: {event['tags_map']}")
return JsonResponse(event)
其中这段代码是直接查询发版系统的 MySql,并根据不同告警规则做了些定制化的查询。因为我司所有发版记录都已经记录在 MySql 了,大家也可以对接 Jenkins、ArgoCD 等接口获取到发版历史。
# 查询发版系统5分钟内相应发版事件
job_count = 0
if 'Dubbo掉线' not in rule_name:
# 提取出应用名称,如receive-server,如未知应用则不处理。
# rule_name格式如:【应用监控|receive-server】JVM-GC时间大于10s
app_name = rule_name.split('|')[1].split('】')[0] if '|' in rule_name else 'Unknown'
if app_name == 'Unknown':
return JsonResponse(event)
# 对应app_name,对应env,5分钟之内的发版数量
start_time = datetime.now() - timedelta(minutes=5)
job_count = tw_jobs.objects.filter(
job_servicename=app_name,
job_envno=env,
job_started__gte=start_time
).count()
else:
# dubbo掉线告警,查询下此环境5分钟内全部发版,有任何发版就丢掉
start_time = datetime.now() - timedelta(minutes=5)
job_count = tw_jobs.objects.filter(
job_envno=env,
job_started__gte=start_time
).count()
6. 实现效果
观察日志,发现外部接口服务打印出了如下日志,附加上了 'updating': 'true'
标签,流转给 Event Drop 处理器后,成功把发版期间的相关告警事件静默 🎉
2025-06-12 18:09:42,171 - n9e_pipeline - INFO - 发现近期发版事件,标签更新为: {'__name__': 'instance_jvm_young_gc_count', 'ident': 'SaaS-PRO', 'layer': 'GENERAL', 'rulename': '【应用监控|receive-server】JVM-GC次数大于100', 'scope': 'ServiceInstance', 'service': 'receive-server', 'service_instance': 'instance1', 'updating': 'true'}
如上便是整个发版时静默的实现逻辑,希望对社区各位小伙伴有所借鉴。你觉得事件 Pipeline 还有哪些玩法,欢迎一起交流分享。