从 prometheus 的架构图可以看到,告警和server实际上是两个单独的模块;
即Alert rule告警规则的存储和数据抓取是维护在server的,如果告警生效,则会推送给Alertmanager进行告警的通知。
本文会针对prometheus的告警进行讲解
Docker 部署Alertmanager
prometheus提供了Alertmanager的组件,可以使用docker直接部署
docker run -d -p 9093:9093 --restart=always --name alertmanager prom/alertmanager
进入容器,修改/etc/alertmanager/alertmanager.yml,设置告警推送的目标
alertmanager.yml配置文件主要包含以下几个部分:
-
全局配置(global):用于定义一些全局的公共参数,如全局的SMTP配置,Slack配置等内容;
-
模板(templates):用于定义告警通知时的模板,如HTML模板,邮件模板等;
-
告警路由(route):根据标签匹配,确定当前告警应该如何处理;
-
接收人(receivers):接收人是一个抽象的概念,它可以是一个邮箱也可以是微信,Slack或者Webhook等,接收人一般配合告警路由使用;
-
抑制规则(inhibit_rules):合理设置抑制规则可以减少垃圾告警的产生
下面的配置以发送邮件为例:
# 全局配置项
global:
resolve_timeout: 5m #处理超时时间,超过这个时间没有产生告警则认为告警已经解决
smtp_smarthost: 'smtp.qq.com:465' # 邮箱smtp服务器代理
smtp_from: '*****@qq.com' # 发送邮箱名称
smtp_auth_username: '****@qq.com' # 邮箱名称
smtp_auth_password: '*****' # 邮箱密码或授权码
smtp_require_tls: false
smtp_hello: 'qq.com'
# 定义模板
templates:
- 'template/*.tmpl'
# 定义路由树信息
route:
group_by: ['alertname'] # 报警分组依据,根据alertname分组
group_wait: 10s # 通过group_wait参数设置等待时间,如果在等待时间内当前group接收到了新的告警,这些告警将会合并为一个通知向receiver发送
group_interval: 10s # 在发送新警报前的等待时间
repeat_interval: 1m # 发送重复警报的周期 对于email配置中,此项不可以设置过低,否则将会由于邮件发送太多频繁,被smtp服务器拒绝
receiver: 'email' # 发送警报的接收者的名称,以下receivers name的名称
# 定义警报接收者信息
receivers:
- name: 'email' # 警报
email_configs: # 邮箱配置
- to: '*****@qq.com' # 接收警报的email配置
# html: '{{ template "test.html" . }}' # 设定邮箱的内容模板,没有模版可以去掉
# headers: { Subject: "[WARN] 报警邮件"} # 接收邮件的标题
# 一个inhibition规则是在与另一组匹配器匹配的警报存在的条件下,使匹配一组匹配器的警报失效的规则。两个警报必须具有一组相同的标签。
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
配置完成后,Alertmanager收到prometheus推送过来的的告警后,会通过邮件发送给指定邮箱进行通知,也可以修改成其他方式例如微信,打电话(一些云电话提供的webhook)等
其他告警方式可以参考:p8s.io/docs/alertm…
Prometheus配置告警
Alertmanager的数据源来自prometheus server,所以需要在server这边配置告警规则
groups:
- name: test-alert-group1
rules:
- alert: alert_name_1
expr: up == 1
for: 1m
labels:
status: normal
annotations:
summary: "{{$labels.instance}}:test summary"
description: "{{$labels.instance}}:test desc"
创建一个*.rule.yml文件用于编写告警规则,其中
- name 是告警规则组名称
- alert 是告警规则名称
- expr 是告警的Promql,如果表达式的值性结果为true,则会推送告警,例如
sum(increase(http_request_total{service="test"}[1m])) > 10,当http过去1min中请求量大于10就会告警 - for 是持续多长时间才需要告警,满足expr表达式并不是直接告警,而是连续满足这个条件一定时间才会触发告警
- label,annotations 可以自定义告警信息
配置好告警文件后,需要在prometheus的配置文件中(promethus.yml)中添加alertmanager的位置和告警文件的位置
启动容器,访问ip:9090/rules,即可看到生效的告警规则
同时也能看到触发的告警
告警有三个状态
- Inactive:表示没有达到告警的阈值,即
expr表达式不成立。 - pending:表示达到了告警的阈值,即
expr表达式成立了,但是未满足告警的持续时间,即for的值。 - firing:已经达到阈值,且满足了告警的持续时间。如果同一个告警数据达到了
Firing,那么不会再次产生一个告警数据,除非该告警解决了。
对于已经 pending 或者 firing 的告警,Prometheus 也会将它们存储到时间序列 ALERTS{} 中。当然我们也可以通过表达式去查询告警实例:
prometheus是如何进行告警的
prometheus 配置文件中有一个evaluation_interval的属性,该属性表示的是间隔多长时间对报警规则进行评估,如果配置了30s,那么每 30s 会去验证下我们的报警规则是否达到了阈值。
正常情况下,所有的报警状态都处于 inactive,如果30s后评估达到了阈值,则会变成 pending 状态,这个时候报警还没有发送给 Alertmanager,如果持续时间达到了for的配置时间,那么就会变成 firing 状态,并将报警发送给 Alertmanager,后续的操作就是 Alertmanager 来处理了。
所以有的场景下我们的监控图表上面已经有部分指标达到了告警的阈值了,但是并不一定会触发告警规则,比如我们上面的规则中,设置的是 1 分钟的 Pending Duration,对于下图这种情况就不会触发告警,因为持续时间太短,没有达到一分钟。
告警到达alertmanage后,隔多久发等设置都是由配置的router决定的,router还可以设置子路由,根据不同label进行不同告警。
如果多条告警都产生了,可以通过告警抑制部分告警
inhibit_rules: # 报警抑制
- source_match: # 源警报
severity: 'critical' # 报警级别为 critical
target_match: # 目标警报
severity: 'warning' # 报警级别为 warning
equal: ['alertname', 'dev', 'instance'] # 源警报和目标警报中必须具有相等值的标签才能使抑制生效。
inhibit_rules 这部分意思为: 在alertname、dev、instance 三个标签的值相同情况下,critaical 的报警会抑制 warning 级别的报警信息。
除了基于抑制机制可以控制告警通知的行为以外,用户或者管理员还可以直接通过Alertmanager的UI(https://ip:9093) 临时屏蔽特定的告警通知。通过定义标签的匹配规则(字符串或者正则表达式),如果新的告警通知满足静默规则的设置,则停止向receiver发送通知。
源码分析
Prometheus 告警规则的逻辑在rule文件夹下
触发告警的逻辑如下图所示
初始化的时候会加载配置,读取告警规则的rule.yaml文件,然后通过loadGroup加载规则组,每一个规则组会新起一个goroutine,通过无限循环持续检测告警条件
如果满足条件,则触发告警。
加载告警配置:
Prometheus 读取告警配置的入口在Manager.Update()函数,
- 通过LoadGroups()函数读取yml文件,组装告警规则
- 告警规则和记录规则都是通过LoadGroups()函数导入,通过判断
Alert.Value字段(即告警的名称)是否为空来区分 - 组装完告警后,New一个规则组group,用于管理这些告警规则
- 对于每一个group,会开启一个协程调用Group.run()函数,执行告警规则的查询和触发
- run函数中,最关键的是这个iter(),通过一个死循环+ticker,每隔evaluation_interval就会执行一次去判断告警阈值是否达到
for {
select {
case <-g.done:
return
default:
select {
case <-g.done:
return
case <-tick.C:
missed := (time.Since(evalTimestamp) / g.interval) - 1
if missed > 0 {
g.metrics.IterationsMissed.WithLabelValues(GroupKey(g.file, g.name)).Add(float64(missed))
g.metrics.IterationsScheduled.WithLabelValues(GroupKey(g.file, g.name)).Add(float64(missed))
}
evalTimestamp = evalTimestamp.Add((missed + 1) * g.interval)
useRuleGroupPostProcessFunc(g, evalTimestamp.Add(-(missed+1)*g.interval))
iter()
}
}
}
- 其中
g.Eval(ctx, evalTimestamp)去判断告警阈值是否满足,一个group中的多条告警规则是串行执行的 - rule.Eval会执行promql去查询告警规则配置好的expr,根据结果修改活跃rule的状态
- sendAlert函数会遍历查看满足条件的rule进行发送
func (r *AlertingRule) sendAlerts(ctx context.Context, ts time.Time, resendDelay, interval time.Duration, notifyFunc NotifyFunc) {
alerts := []*Alert{}
r.ForEachActiveAlert(func(alert *Alert) {
if alert.needsSending(ts, resendDelay) {
alert.LastSentAt = ts
// Allow for two Eval or Alertmanager send failures.
delta := resendDelay
if interval > resendDelay {
delta = interval
}
alert.ValidUntil = ts.Add(4 * delta)
anew := *alert
// The notifier re-uses the labels slice, hence make a copy.
anew.Labels = alert.Labels.Copy()
alerts = append(alerts, &anew)
}
})
notifyFunc(ctx, r.vector.String(), alerts...)
}
AlertManager:
类似告警规则的导入,Alertmanager也是在main中初始化
- 根据配置文件(prometheus.yml)建立和alertmanager的http连接,后续通知都是用这个建立好的client连接。