提示:这篇文章的精华在最后一节,文章比较长,希望读者能够耐心读完
这几天由于都在找 Prometheus 高可用的集群的解决方案,所以断更了几天。后续的文章也会带来这方面研究的成果,来和大家一起讨论。
今天我们暂时从 Kubernetes 话题切换到 Prometheus系列。了解在Prometheus中产生的一条告警需要经过哪些环节?AlertMananger扮演的是怎样的角色?同时了解 AlertManager 中提供哪些机制避免报警泛滥?
我们首先看下下面的AlertManager的结构图,了解大致的数据走向:
prometheus server产生告警并调用AlertManager的接口下发告警
如果消息被静默了则进入静默管理,等过了静默时间判断是否需要发送
如果消息被被静默,则走正常的消息流程
分组(Group)
抑制(Inhibitor)
静默(silence)【再次静默拦截】
路由发送

Prometheus 产生告警
配置文件的rule_files节点指定配置文件
rule_files:- node.rules- mysql.rules- redis.rules
可指定多个文件,为便于管理,也可将所有的报警合并到一个文件中。
查看 mysql.rules 中定义的一条报警规则:
- name: MySQLStatsAlertrules:- alert: MySQL 服务宕机expr: mysql_up == 0for: 1mlabels:severity: criticalannotations:summary: "MySQL{{ $labels.instance }}已宕机"
name表示Group的名称,在MySQLStatsAlert分组下还有其他的报警项,例如连接数、慢查询个数等设置的阈值。
rules为告警项列表,其中alert为“MySQL 服务宕机”是其中一条报警的名称。
expr、for表示满足该表达式且长达多长时间会产生告警。expr可以为多条表达式的 or、and逻辑操作的结果。例如以下表达式表示内存不足10%且剩余空间不足100M产生告警。
node_memory_MemTotal_bytes/node_memory_MemTotal_bytes <0.1 and node_memory_MemTotal_bytes < 100*1024*1024
labels、annotations为自定义的显示内容。
在Prometheus中一条告警规则有三个状态:
inactive:还未被触发;
pending:已经触发,但是还未达到for设定的时间;
firing:触发且达到设定时间。
这里需要注意在pending状态如果触发条件又不满足了(恢复了),将会回归inactive状态。
在 Prometheus 中为 firing状态的告警需要发送给 AlertManager 来真正的发送出报警通知。在 Prometheus 的配置中需要指定 AlertManager的地址,如下所示:
alerting:alertmanagers:- scheme: httpstatic_configs:- targets:- 127.0.0.1:9093
AlertManager告警分组、抑制、静默
当报警到达AlertManager后,AlertManager并不会就简单的将告警立刻发送出去,而是会通过一些手段来避免告警泛滥。
分组(Group)
首先是对消息分组,将多条告警消息聚合成一条消息。
而分组的条件取决于AlertManager的router.group_by配置,在了解该配置前,我们需要先了解 Prometheus告警规则的labels字段,例如我们在上面的例子中的定义:
...labels:severity: critical...
这里我们只有一个报警级别的label,我们还可以根据自己的需求增加其他自定义的label。例如,同时指定了服务名称和报警的资源类型.
labels:severity: criticalserver: mysqltype: cpu
而AlertManager的router.group_by配置正是依赖label来组合条件的。如下配置表示根据监控的服务名称进行分组:
route:group_by:- mysqlgroup_interval: 3mgroup_wait: 2m
对于分组还有以下两个配置:
group_wait:等待2分钟来聚合分组消息。当mysql在两分钟的滑动窗口时间内产生了多条告警,会合并成一条告警发送出来。
group_interval:发送告警组的间隔时间。当mysql消息组发送出一条消息后,下一次消息通知将会在3分钟后再发送。
当然报警类型还可以以其他的维度,例如资源类型或者是告警级别。将同类型的消息聚合不仅仅只是减少了消息量,当大量消息时,聚合一起的消息也便于运维获取信息能够快速定位问题。
告警抑制
告警抑制是当先后产生了多条告警后,使用一定的条件来使一些告警不会被发出。
同样,这些条件的定义来源于告警配置的label。我们可以用最简单的告警优先级来做抑制规则,对于相同的服务,可以用高等级报警抑制低等级告警。
例如有以下两条label的告警:
labels:severity: criticalserver: mysqltype: cpulabels:severity: warmserver: mysqltype: connections
在alertManager的配置文件中我们可以这样配置抑制规则:
inhibit_rules:- source_match:severity: criticaltarget_match:severity: warmequal: ['server']
inhibit_rules节点表示告警抑制的规则列表,这条配置表达的是配置有label为severity等于 critical的报警将会抑制配置有label为severity等于 warm且这两者label中的server字段相等的报警。
告警延迟
在分组中已经体现出一组消息的延迟,在group_interval时间内不会发出相同组的告警,而对于一条告警也有延迟策略其配置在AlertManager的route.repeat_interval节点:
route:...repeat_interval: 5m
例如以上配置表示在5分钟的滑动窗口时间内不会发出相同的告警。
告警路由
对于不同的告警,可能通知的对象(人群、手机号、微信群等)不一样,则需要对告警进行路由分发。而AlertManager的路由依据仍然还是告警规则的labels。在AlertManager配置的route节点配置路由规则,示例子如下:
route:repeat_interval: 2hreceiver: default-grouproutes:- match:server: mysqlreceiver: group-1- match:server: redisreceiver: group-2
route是以树的结构递归下去的,当匹配路由时也是匹配到最深的节点。这里我们将mysql服务和redis服务分别告警至不同的群体。若没有匹配到match条件,则会走默认的路由,也就是树的根节点。
当然,如果有必要,也可以对mysql这个match再进行路由 , 区别出不同的资源,通知告警人群也不同。
而对应群体具体的对象我们需要在receivers节点中定义,例如:
receivers:- name: default-groupemail_configs:- to: '491101403@qq.com'send_resolved: trueheaders:subject: "报警邮件"from: "警报中心"- name: group-1webhook_configs:- url: http://localhost:8002/sms- name: group-2wechat_configs:- corp_id: 'xxxx'api_url: 'https://qyapi.weixin.qq.com/xxxxx'send_resolved: trueto_party: '3'agent_id: 'xxxxxx'api_secret: 'xxxxx'
在上面的示例中:
default-group:使用alertmanager自带的邮件功能发送邮件通知
group-1: 调用外部接口,需要实现该接口对报警信息的解析,然后可对接不同的邮件、电话、短信平台
group-2:使用AlertManager自带的企业微信功能
以上讲解了从Prometheus产生告警到发送告警的完整过程。
特殊难题
对于生产实践中,我们大部分的场景只需要知道如何使用配置就行,但是也存在着很棘手的情况,接下来我将给大家讲解一个我遇到问题的场景,以及提供我的解决方案,如果你们有更好的解决方案可以直接留言给我。
在上面讲解到了告警抑制功能,我们希望利用该机制能够在以下场景产生消息抑制:当某台主机宕机了,部署在该主机上的应用也将会宕机。希望告警发出主机宕机后,不再需要发出对应的应用宕机告警。
告警规则如下:
groups:- name: noderules:- alert: 主机宕机expr: up == 0for: 1mlabels:severity: criticalserver: nodetype: lifecycleannotations:summary: "主机 {{ $labels.instance }} 宕机"description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minutes."- name: MySQLStatsAlertrules:- alert: MySQL 服务宕机expr: mysql_up == 0for: 1mlabels:severity: criticalserver: mysqltype: lifecycleannotations:summary: "MySQL{{ $labels.instance }}宕机"description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minutes."
如果我们考虑多集群(多台主机、多个mysql集群),我们仅仅通过现有的labels发现无法组装inhibit_rules的,因为根本没法将Mysql所在的主机对应上。
因为部署node_exporter和mysql_exporter同主机的关系可能如下:

也可能如下:

所以,无法确定exporter的instance与对应的监控对象instance的ip一致。就算ip一致,由于instance是带有ip和端口的,而inhibit_rules的规则只能配置简单的字符串相等或者正则表达式,也无法对字符串进行处理。
该如何解决这个问题呢?
我发现在prometheus定义scrape_configs(收集指标的exporter或者是pushGateway)时是可以指定固定label的,我们可以这样去定义对应的static_configs:
scrape_configs:- job_name: 'node_exporter'static_configs:- targets: ['172.16.212.38:9100']labels:extra: group1- job_name: 'node_exporter'static_configs:- targets: ['172.16.212.39:9100']extra: group2- job_name: 'mysql_exporter'static_configs:- targets: ['172.16.212.38:9090']extra: group1- job_name: 'node_exporter'static_configs:- targets: ['172.16.212.39:9090']extra: group2
这样的label起到了成对匹配的作用,我们可以这样定义inhibit_rules完成消息抑制:
inhibit_rules:- source_match:type: lifecycleserver: nodetarget_match:type: lifecycleequal: ['extra']
解释:
主机(server: Node)宕机(type: lifecycle)的告警将会抑制其他与其extra值相同(extra都为group1或者group2)的宕机(type: lifecycle)告警。
注意告警抑制中,如果先产生了mysql宕机告警,再产生主机宕机的告警,那么mysql宕机告警不会被抑制。只有较后产生的报警会被较前产生的报警抑制。
如果你有更好的解决方案,可以留言与我交流,谢谢。
本系列回顾:




