5、Prometheus告警进阶

Prometheus入门 / 2022-05-30

本文系朱政科《Prometheus云原生监控:运维与开发实战》学习笔记

配置部分引用官方配置文档:https://prometheus.io/docs/alerting/latest/configuration/

一、前言

在第二大章节的学习中我们知道。Prometheus的告警功能是由Alertmanager组件独立负责。Prometheus服务器自定义告警规则,这些规则达到预设的阈值后首先触发事件,接着通过notifier模块向Alertmanager发送告警;Alertmanager再根据相关配置及当前的告警状态决定如何处理响应的告警:分组、过滤、抑制、消息推送等。

本章节主要围绕Alertmanager告警的架构、原理、集群、触发流程等内容展开。

二、Alertmanager架构解析

下图为官方架构图,我们根据此图来对各模块的功能进行简单介绍:

51680cc2cb453d6cbb4d02b3682e0ace.jpeg

2.1、API组件

用于接受Prometheus服务端的告警相关的HTTP请求。当前的API版本v2(此前一直是v1),API是由OpenAPI项目与Go Swagger生成,相关规范可在此处查阅。v2版本的/status的路径为/api/v2/status,如果有在启动时添加参数--web.route-prefix,比如--web.route-prefix=/alertmanager会映射到/alertmanager/api/v2/status

2.2、Alert Provide组件

API组件将来自Prometheus服务端的告警信息存储到Alert Provide上,其原理就是内置了一个Map(映射器),放在本机内存中,这样可以很容易扩展其它持久化的Provide,比如MySQLelasticsearch等。

2.3、Dispatcher组件

这是一个单独的goroutine(轻量级线程,也叫携程),它能够不断地通过订阅的方式从Alert Provide这个供应商处获取最新的告警,并根据配置文件中的Routing Tree将告警通过Label路由到不同的分组中,借以实现告警信息的分组处理。

2.4、Notification Pipeline组件

这是一个流水线组件,通过一系列逻辑(抑制、静默、去重等)来优化告警质量。在源码中,它是通过一个个实现stage接口的具有不同功能的实例串联起来的得到的。

2.5、Silence Provide组件

API组件将来自Prometheus服务端的告警信息存储到Silence Provide上,然后由这个组件去实现去重逻辑的处理,而静默规则用来关闭部分告警的通知。

2.6、Notify Provide组件

它是Silence Provide组件下游,会在本地记录日志,并通过Peers的方式将日志广播给集群中的其它阶段,判断当前节点自身或其它节点是否已经发送过了,避免告警通知在集群中重复出现。

三、AMTool使用

AMTool是和Alertmanager架构中的API组件进行交互的命令行工具,它是Alertmanager自带的工具,因此在同级目录下可以找到。

3.1、配置文件

查询最详细的命令行使用帮助

# 查询帮助且显示详情,比--help和-h要详细得多,最重要的选项!
amtool --help-long

amtool添加配置文件后,可以不用在命令行额外指定选项和参数

sudo mkdir /etc/amtool
sudo vim /etc/amtool/config.yml

配置参考如下:

# alertmanager地址,设置后就无须在命令行额外指定。
alertmanager.url: http://localhost:9093

# 提交作者
author: sanxi

# 提交屏蔽时是否需要写注释
require-comment: false

# 设置默认输出格式,sample|extended|json
output: extended

# 时间格式,默认:"2006-01-02 15:04:05 MST"
#date.format

以下为AMTool常见用法:

3.2、查看告警

查看当前所有已触发的告警,--alertmanager.url后面跟alertmanager地址,不带参数则默认查询所有

amtool alert query --alertmanager.url="http://localhost:9093"
Alertname  Starts At                Summary  State   
IfNodeUp   2022-05-23 02:41:27 UTC  节点异常     active

3.3、扩展输出告警

查看具有扩展输出的所有当前触发的告警

/usr/local/prometheus/alertmanager/amtool -o extended alert query --alertmanager.url=
Labels                                                                         Annotations                                                      Starts At                Ends At                  Generator URL                                                                                   State   
alertname="IfNodeUp" instance="localhost:9100" job="prometheus" level="error"  description="instance: localhost:9100 was down!" summary="节点异常"  2022-05-23 02:41:27 UTC  2022-05-23 02:47:57 UTC  http://sanxi-PC:9090/graph?g0.expr=up%7Binstance%3D%22localhost%3A9100%22%7D+%21%3D+1&g0.tab=1  active  
sanxi@sanxi-PC:/usr/local/prometheus/alertmanager-0.24.0.linux-amd64$ 

3.4、屏蔽告警

默认有效期为1h,可以使用--expires--expire-on指定时间。

amtool silence add alertname="test_alert" 
fa028389-bbe5-4c70-ac22-ae282d0e7e9b

3.5、提交屏蔽

屏蔽告警后需要提交方能生效

amtool silence update fa028389-bbe5-4c70-ac22-ae282d0e7e9b
ID                                    Matchers                  Starts At                Ends At                  Updated At               Created By  Comment  
fa028389-bbe5-4c70-ac22-ae282d0e7e9b  {alertname="test_alert"}  2022-05-23 08:40:42 UTC  2022-05-23 09:40:42 UTC  2022-05-23 08:40:42 UTC  sanxi         

3.6、查询屏蔽告警

查询所有被静默的告警

./amtool silence query
ID                                    Matchers                  Starts At                Ends At                  Updated At               Created By  Comment  
fa028389-bbe5-4c70-ac22-ae282d0e7e9b  {alertname="test_alert"}  2022-05-23 08:40:42 UTC  2022-05-23 09:40:42 UTC  2022-05-23 08:41:14 UTC  sanxi         

四、配置解析

Alertmanager可以通过命令行和配置文件来更改配置参数。其中,命令行更改的是系统参数,配置文件是定义规则用于通知路由和告警接收者。

以下为Alertmanager常见选项:

选项说明
--config.file指定alertmanager.yml配置文件的路径
--web.listen-addressweb监听地址与端口
--web.external-url用于返回Alertmanager的相对、绝对URL,可以在后续告警通知中直接点击链接地址来访问其web页面。
--data.retention历史数据最大保留时间,默认为120h,即5天。
--storage.path数据存储路径

注意:如果是在kubernetes集群中,需要使用docker镜像进行安装,指定配置文件需要使用一个kindConfigMap的资源对象,具体见官方文档。

Alertmanager配置文件是YAML格式,以下为示例:

# 全局配置,会被子模块覆盖。
global:
  # 配置邮件接收告警
  resolve_timeout: 5m  # 解析超时时间
  smtp_smarthost: 'smtp.qq.com:465'
  smtp_from: 'smtp.qq.com'
  smtp_auth_username: '88888888'
  smtp_auth_password: 'hahhhhhahaha'
# 每个告警信息进入的根路由,用于设置告警的分发策略。根路由不能有匹配器,因它是告警入口。
# 它需要配置一个接收器,以便将不匹配任何子路由的告警发送出去。
# receiver默认为default,如某告警没被route匹配,则发送给默认的接收器。
route:
  group_by: [ 'alertname' ]  # 按指定字段,将多个告警批量聚合到一个组
  # 生成告警组后,等待多久开始发送,默认30s。
  # 这样可确保有足够多时间为同一分组获取多条告警,然后一起触发。
  group_wait: 30s
  group_interval: 1m  # 发送完一组告警后,等待多久来发送一组新的告警信息。
  repeat_interval: 1h  # 同一告警重新发送间隔
  receiver: 'dingtalk'  # 告警接收器
# route为根路由,routes为子路由继承根路由。  
routes:
  # 此路由对告警标签执行正则匹配,用以捕获相关告警。
  - match_re:
    service: ^(foo1|foo2|baz)$
    receiver: 'dingtalk'
  - match:
    service: critical
    receiver: 'dingtalk'
# 告警接收器,可自定义多个。
receivers:
#  - name: 'mail-receiver'
#    email_configs:
#      - to: 'imap.qq.com'
- name: 'dingtalk'
  webhook_configs:
  - url: 'http://localhost:8060/dingtalk/webhook1/send'
    send_resolved: true
# 抑制规则
inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: [ 'alertname', 'dev', 'instance' ]

其实Alertmanager配置主要分五部分:

  • 全局配置(global):定义公共参数,如全局的SMTPslack(slack、微信等API)配置等。
  • 告警路由(route):根据标签匹配,确定当前告警处理方式。
  • 抑制规则(inhibit_rules):合理设置抑制规则可以减少垃圾告警的产生
  • 告警接收者(receivers):抽象概念,可以是邮箱、微信、钉钉、webhook等任何可以接收消息的,一般配合告警路由使用
  • 模板(templates):用于定义告警通知时的模板,比如HTML模板、邮件模板等等。

4.1、全局配置(global)

定义公共参数,如全局的SMTPslack(slack、微信等API)配置等。

global:
  # SMTP服务器
  [ smtp_from: <tmpl_string> ]
  # 邮件发送服务器,格式:smtp.example.org:587
  [ smtp_smarthost: <string> ]
  # SMTP认证默认主机名
  [ smtp_hello: <string> | default = "localhost" ]
  # SMTP登录认证账号,为空则不验证。
  [ smtp_auth_username: <string> ]
  # SMTP登录认证密码
  [ smtp_auth_password: <secret> ]
  # SMTP明文验证,不推荐。
  [ smtp_auth_identity: <string> ]
  # SMTP验证密钥
  [ smtp_auth_secret: <secret> ]
  # SMTP TLS加密连接,默认开启,且Go不支持与对端未加密连接。
  [ smtp_require_tls: <bool> | default = true ]

  # 用于第三方消息通知的API链接
  [ slack_api_url: <secret> ]
  [ slack_api_url_file: <filepath> ]
  [ victorops_api_key: <secret> ]
  [ victorops_api_url: <string> | default = "https://alert.victorops.com/integrations/generic/20131114/alert/" ]
  [ pagerduty_url: <string> | default = "https://events.pagerduty.com/v2/enqueue" ]
  [ opsgenie_api_key: <secret> ]
  [ opsgenie_api_key_file: <filepath> ]
  [ opsgenie_api_url: <string> | default = "https://api.opsgenie.com/" ]
  [ wechat_api_url: <string> | default = "https://qyapi.weixin.qq.com/cgi-bin/" ]
  [ wechat_api_secret: <secret> ]
  [ wechat_api_corp_id: <string> ]
  [ telegram_api_url: <string> | default = "https://api.telegram.org" ]
  # HTTP客户端默认配置
  [ http_config: <http_config> ]

  # 解析超时时间
  [ resolve_timeout: <duration> | default = 5m ]
4.1.1、HTTP配置

http_config允许配置接收器用来与基于 HTTPAPI 服务通信的客户端。

# 请注意,basic_auth和authorization是互斥的
# 使用用户名和密码设置Authorization标头,password和password_file是互斥的。
basic_auth:
  [ username: <string> ]
  [ password: <secret> ]
  [ password_file: <string> ]

# 可选的Authorization头部配置。
authorization:
  # 配置身份验证类型
  [ type: <string> | default: Bearer ]
  # 配置凭据,它与credentials_file互斥
  [ credentials: <secret> ]
  # 使用从配置文件读取的凭据设置凭据,与credentials互斥。
  [ credentials_file: <filename> ]

# OAuth 2.0验证方式,与basic_auth、authorization互斥。
oauth2:
  [ <oauth2> ]

# 代理地址,可选。
[ proxy_url: <string> ]

# 配置HTTP请求是否遵循遇到HTTP 3xx就重定向。
[ follow_redirects: <bool> | default = true ]

# 配置TLS设置
tls_config:
  [ <tls_config> ]
4.1.1.1、oauth2

OAuth 2.0是使用客户端授予凭据的身份验证类型,Alertmanager 使用指定的客户端访问或密钥从指定的端点获取访问令牌。

client_id: <string>
[ client_secret: <secret> ]  # 客户端密文

# 从文件中读取客户端密文,和client_secret互斥。
[ client_secret_file: <filename> ]

# 令牌请求的作用范围
scopes:
  [ - <string> ... ]

# 提取令牌的URL
token_url: <string>

# 追加到令牌的URL中的可选参数
endpoint_params:
  [ <string>: <string> ... ]
4.1.1.2、tls_config

tls_config允许配置 TLS 连接

# 用于验证服务器证书的CA证书
[ ca_file: <filepath> ]

# 用于向服务器进行客户端证书的身份验证的证书和密钥文件。
[ cert_file: <filepath> ]
[ key_file: <filepath> ]

# ServerName扩展名用来指示服务器名称
# 更多详情见:http://tools.ietf.org/html/rfc4366#section-3.1
[ server_name: <string> ]

# 关闭服务器证书验证
[ insecure_skip_verify: <boolean> | default = false]

4.2、路由配置(route)

路由配置模块定义路由树中的根节点及其子节点,子节点默认继承父节点配置。

每个告警在配置的路由的顶部节点上必须匹配所有告警(即没有任何已配置的匹配器),然后遍历子节点。如果continue设置为 false,则在匹配第一个子节点后停止;如果continue设置为true,则告警将继续与后续的子节点匹配。如果警报不匹配节点的任何子节点(没有匹配的子节点,或不存在),则根据当前节点的配置参数处理警报。

route:
  # 告警接收者
  [ receiver: <string> ]
  # 将传入的告警进行分组的所依据的标签,比如对于cluster=A
  # 和alertname=LatencyHigh传入的多个告警将被批处理合成一个组
  # 要按所有可能的标签进行聚合,请使用特定值来作为唯一的标签名称,比如:
  # group_by: ['...']
  # 但这实际会使聚合失效,即不分组,这不是我们想要的,除非告警数量很少
  # 或上游通知系统执行自己的分组
  [ group_by: '[' <labelname>, ... ']' ]
  
  # 是否应继续匹配后续同级节点
  [ continue: <boolean> | default = false ]
  
  # 必须满足一组相等匹配器才能与节点匹配,已弃用,用matchers即可。
  match:
    [ <labelname>: <labelvalue>, ... ]
  
  # 必须满足一组正则表达式匹配器才能与节点匹配,已弃用,用matchers即可。
  match_re:
    [ <labelname>: <regex>, ... ]
  
  # 告警必须满足匹配器列表之一才能与节点匹配
  matchers:
    [ - <matcher> ... ]
  
  # 发送一组告警的初始等待时间,允许等待1个抑制告警或收集同一组的更多初始告警。
  [ group_wait: <duration> | default = 30s ]
  
  # 发送一组新告警之前,需要等待多长时间。新告警将被添加到已发送初始通知的告警组。
  # 即发送完一组告警后,等待多久来发送一组新的告警信息,通常为5m或更久。
  [ group_interval: <duration> | default = 5m ]
  
  # 重新发送告警的间隔,通常大于3h,低了就会短信轰炸,过来人都懂。
  [ repeat_interval: <duration> | default = 4h ]
  
  # 路由静默时间,它们必须与time_intervals section定义的静默时间的名称一致
  # 此外,根路由不能静默
  # 当路由静默时,不会发送任何通知,但会正常运行。,如未设置continue,则结束路由匹配
  mute_time_intervals:
    [ - <string> ...]
  
  # 路由应处于活动状态的时间。它们必须与time_intervals中定义的时间间隔的名称匹配。
  # 空值表示该路由始终处于活动状态,此外,根节点不能有任何活动时间。
  # 该路由只有在激活时才会发送通知,但会正常运行(如未设置Continue,则结束路由匹配)。
  active_time_intervals:
    [ - <string> ...]
  
  # 0或多个子路由
  routes:
    [ - <route> ... ]
time_intervals

time_intervals可以指定在路由树中引用的命名时间间隔,以在一天中的特定时间静音/激活特定路由。

name: <string>
time_intervals:
  [ - <time_interval> ... ]

time_intervals语法如下所示:

- times:
  - start_time: HH:MM  # 开始时间,几点几分几秒
    end_time: HH:MM  # 结束时间,xxx时间前结束。
  weekdays:
  [ - <weekday_range> ...]  # 星期几到星期几
  days_of_month:
  [ - <days_of_month_range> ...]  # 几号到几号
  months:
  [ - <month_range> ...]  # 几月到几月
  years:
  [ - <year_range> ...]  # 哪年到哪年

4.3、抑制规则(inhibit_rule)

当存在与另一组匹配器匹配的告警(源)时,抑制规则将与一组匹配器匹配的告警(目标)静默。对于equal列表中的标签名称,目标告警和源告警必须具有相同的标签值equal

从语义上讲,缺少标签和具有空值的标签是一样的。因此,如果源告警和目标告警中都缺少equal列出的所有标签名称,则将应用抑制规则。

为了防止告警抑制自身,同时匹配规则的目标端和源端的告警,不能通过告警(包括自身)来抑制告警。但是,我们建议在选择目标匹配器和源匹配器时,不要同时发出告警。

# 要静音的告警中必须满足的匹配项,已被弃用,有target_matchers即可。
target_match:
  [ <labelname>: <labelvalue>, ... ]
# 同上,必须满足的正则匹配项,已被弃用,有target_matchers即可。
target_match_re:
  [ <labelname>: <regex>, ... ]

# 要静音的目标告警必须满足的匹配项列表
target_matchers:
  [ - <matcher> ... ]

# 必须存在一个或多个告警才能使抑制生效的匹配项,已弃用,有source_matchers即可。
source_match:
  [ <labelname>: <labelvalue>, ... ]
# 同上,已弃用,有source_matchers即可。
source_match_re:
  [ <labelname>: <regex>, ... ]

# 必须存在一个或多个警报才能使抑制生效的匹配器列表。
source_matchers:
  [ - <matcher> ... ]

# 源告警和目标告警中的标签必须具有相同的值,才能使禁止生效。
[ equal: '[' <labelname>, ... ']' ]

4.4、告警接收者(receiver)

Receiver 是一个或多个通知集成的命名配置,也就是告警接收者,推荐通过webhook实现自定义通知集成。

# 接收者名称必须是唯一
name: <string>

# 常见通知集成的配置
email_configs:
  [ - <email_config>, ... ]
opsgenie_configs:
  [ - <opsgenie_config>, ... ]
pagerduty_configs:
  [ - <pagerduty_config>, ... ]
pushover_configs:
  [ - <pushover_config>, ... ]
slack_configs:
  [ - <slack_config>, ... ]
sns_configs:
  [ - <sns_config>, ... ]
victorops_configs:
  [ - <victorops_config>, ... ]
webhook_configs:
  [ - <webhook_config>, ... ]
wechat_configs:
  [ - <wechat_config>, ... ]
telegram_configs:
  [ - <telegram_config>, ... ]
4.4.1、email_configs
# 是否通知接收者告警已解除,建议开启。
[ send_resolved: <boolean> | default = false ]

# 邮件接收方的邮箱地址
to: <tmpl_string>

# 邮件发送者的邮箱地址
[ from: <tmpl_string> | default = global.smtp_from ]

# 发邮件所使用的SMTP主机
[ smarthost: <string> | default = global.smtp_smarthost ]

# 要向SMTP服务器标识的主机名。
[ hello: <string> | default = global.smtp_hello ]

# SMTP验证信息
[ auth_username: <string> | default = global.smtp_auth_username ]
[ auth_password: <secret> | default = global.smtp_auth_password ]
[ auth_secret: <secret> | default = global.smtp_auth_secret ]
[ auth_identity: <string> | default = global.smtp_auth_identity ]

# 是否开启SMTP TLS加密,Go不支持与远端进行未加密连接。
[ require_tls: <bool> | default = global.smtp_require_tls ]

# TLS配置
tls_config:
  [ <tls_config> ]

# 通知邮件的HTML正文
[ html: <tmpl_string> | default = '{{ template "email.default.html" . }}' ]
# 通知邮件的文本的正文
[ text: <tmpl_string> ]

# 更多标题电子邮件标题键/值对。 覆盖通知实现之前设置的任何标头。
[ headers: { <string>: <tmpl_string>, ... } ]
4.4.2、opsgenie_configs

OpsGenie 通知通过OpsGenie API发送

# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# 与OpsGenie API对话时使用的API密钥.
[ api_key: <secret> | default = global.opsgenie_api_key ]

# 与OpsGenie API对话时使用的API密钥的文件路径。与api_key冲突。
[ api_key_file: <filepath> | default = global.opsgenie_api_key_file ]

# 要向其发送OpsGenie API请求的主机。
[ api_url: <string> | default = global.opsgenie_api_url ]

# 告警文本限制为130个字符
[ message: <tmpl_string> | default = '{{ template "opsgenie.default.message" . }}' ]

# 告警描述
[ description: <tmpl_string> | default = '{{ template "opsgenie.default.description" . }}' ]

# 指向告警发件人的反向链接,即在告警信息中附上来源URL,点击即可跳转。
[ source: <tmpl_string> | default = '{{ template "opsgenie.default.source" . }}' ]

# 提供有关告警的更多详细信息的一组任意键/值对。
# 默认情况下,所有常见标签都作为详细信息包含在内。
[ details: { <string>: <tmpl_string>, ... } ]

# 负责通知的响应者列表
responders:
  [ - <responder> ... ]

# 附加到通知的标签的逗号分隔列表
[ tags: <tmpl_stringThe HTTP client's configuration.> ]

# 附加的告警备注
[ note: <tmpl_string> ]

# 告警优先级别,可能的值为P1, P2, P3, P4, and P5.
[ priority: <tmpl_string> ]

# 是否更新OpsGenie中的告警消息和描述(如果已存在)。
# 默认情况下,OpsGenie中的告警永远不会更新,新消息只会出现在活动日志中。
[ update_alerts: <boolean> | default = false ]

# 可用于指定与哪个作用域相关的告警,可选字段。
[ entity: <tmpl_string> ]

# 将用于警报的操作的逗号分隔列表。
[ actions: <tmpl_string> ]

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]
4.4.2.1、responder
# 这些字段中只有一个应该定义,即最好只定义1个响应者。
[ id: <tmpl_string> ]
[ name: <tmpl_string> ]
[ username: <tmpl_string> ]

# "team", "teams, "user", "escalation" or "schedule".
type: <tmpl_string>
4.4.3、pagerduty_configs

PagerDuty 的通知是通过PagerDuty API发送的。PagerDuty 提供有关如何集成的文档。Alertmanagerv0.11 和对 PagerDutyEvents API v2 的更大支持存在重要差异。

# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# 以下两个选项是互斥的。
# PagerDuty集成密钥(使用PagerDuty集成类型Events API v2时)
routing_key: <tmpl_secret>
# PagerDuty集成密钥(使用PagerDuty集成类型Prometheus时)
service_key: <tmpl_secret>

# 要向其发送API请求的URL
[ url: <string> | default = global.pagerduty_url ]

# Alertmanager的客户端标识
[ client:  <tmpl_string> | default = '{{ template "pagerduty.default.client" . }}' ]
# 指向告警发件人的反向链接,即在告警信息中附上来源URL,点击即可跳转。
[ client_url:  <tmpl_string> | default = '{{ template "pagerduty.default.clientURL" . }}' ]

# 告警事件描述
[ description: <tmpl_string> | default = '{{ template "pagerduty.default.description" .}}' ]

# 告警事件的严重性
[ severity: <tmpl_string> | default = 'error' ]

# 一组任意键/值对,提供有关事件的更多详细信息。
[ details: { <string>: <tmpl_string>, ... } | default = {
  firing:       '{{ template "pagerduty.default.instances" .Alerts.Firing }}'
  resolved:     '{{ template "pagerduty.default.instances" .Alerts.Resolved }}'
  num_firing:   '{{ .Alerts.Firing | len }}'
  num_resolved: '{{ .Alerts.Resolved | len }}'
} ]

# 附加图片到事件中
images:
  [ <image_config> ... ]

# 附加链接到事件中
links:
  [ <link_config> ... ]

# 受影响的系统中损坏的部分或组件
[ component: <tmpl_string> ]

# 来源的集群/组
[ group: <tmpl_string> ]

# 事件的类/类型
[ class: <tmpl_string> ]

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]
4.4.3.1、image_config

PagerDuty API 文档中记录了这些字段。

href: <tmpl_string>
source: <tmpl_string>
alt: <tmpl_string>

PagerDuty API 文档中记录了这些字段。

href: <tmpl_string>
text: <tmpl_string>
4.4.4、pushover_configs

Pushover 通知通过Pushover API发送。

# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# 收件人的用户密钥
user_key: <secret>

# 你注册的应用的API token,见https://pushover.net/apps。
# 也可通过克隆这个Prometheus APP来注册令牌
# https://pushover.net/apps/clone/prometheus
token: <secret>

# 通知标题
[ title: <tmpl_string> | default = '{{ template "pushover.default.title" . }}' ]

# 通知消息的内容
[ message: <tmpl_string> | default = '{{ template "pushover.default.message" . }}' ]

# 通知消息旁边显示的补充URL
[ url: <tmpl_string> | default = '{{ template "pushover.default.url" . }}' ]

# 优先级,见https://pushover.net/api#priority
[ priority: <tmpl_string> | default = '{{ if eq .Status "firing" }}2{{ else }}0{{ end }}' ]

# Pushover 服务器多久向用户发送一次相同的通知,不能低于30s。
[ retry: <duration> | default = 1m ]

# 除非用户确认通知,否则您的通知将继续重试多长时间。
[ expire: <duration> | default = 1h ]

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]
4.4.5、slack_configs

Slack 通知是通过Slack webhooks发送的,它通知包含一个附件

# 是否通知接收者告警已解除,建议开启。
[ send_resolved: <boolean> | default = false ]

# Slack WebHook URL,应设置api_url或api_url_file,未配置则默认为全局设置。
[ api_url: <secret> | default = global.slack_api_url ]
[ api_url_file: <filepath> | default = global.slack_api_url_file ]

# 要向其发送通知的通道或用户
channel: <tmpl_string>

# 由Slack WebHook API定义的API请求数据
[ icon_emoji: <tmpl_string> ]
[ icon_url: <tmpl_string> ]
[ link_names: <boolean> | default = false ]
[ username: <tmpl_string> | default = '{{ template "slack.default.username" . }}' ]
# 以下参数定义附件
actions:
  [ <action_config> ... ]
[ callback_id: <tmpl_string> | default = '{{ template "slack.default.callbackid" . }}' ]
[ color: <tmpl_string> | default = '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}' ]
[ fallback: <tmpl_string> | default = '{{ template "slack.default.fallback" . }}' ]
fields:
  [ <field_config> ... ]
[ footer: <tmpl_string> | default = '{{ template "slack.default.footer" . }}' ]
[ mrkdwn_in: '[' <string>, ... ']' | default = ["fallback", "pretext", "text"] ]
[ pretext: <tmpl_string> | default = '{{ template "slack.default.pretext" . }}' ]
[ short_fields: <boolean> | default = false ]
[ text: <tmpl_string> | default = '{{ template "slack.default.text" . }}' ]
[ title: <tmpl_string> | default = '{{ template "slack.default.title" . }}' ]
[ title_link: <tmpl_string> | default = '{{ template "slack.default.titlelink" . }}' ]
[ image_url: <tmpl_string> ]
[ thumb_url: <tmpl_string> ]

# HTTP配置
[ http_config: <http_config> | default = global.http_config ]
4.4.5.1、action_config

这些字段记录在消息附件交互式消息的 Slack API 文档中。

text: <tmpl_string>
type: <tmpl_string>
# URL或名称和值为必填项.
[ url: <tmpl_string> ]
[ name: <tmpl_string> ]
[ value: <tmpl_string> ]

[ confirm: <action_confirm_field_config> ]
[ style: <tmpl_string> | default = '' ]
4.4.5.2、action_confirm_field_config

这些字段记录在Slack API 文档中。

text: <tmpl_string>
[ dismiss_text: <tmpl_string> | default '' ]
[ ok_text: <tmpl_string> | default '' ]
[ title: <tmpl_string> | default '' ]
4.4.5.3、field_config

这些字段记录在Slack API 文档中。

title: <tmpl_string>
value: <tmpl_string>
[ short: <boolean> | default = slack_config.short_fields ]
4.4.6、sns_configs
# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# SNS API URL,即https://sns.us-east-2.amazonaws.com.。
# 如果未指定,将使用SNS SDK中的SNS API URL。
[ api_url: <tmpl_string> ]

# 配置AWS的签名验证4签名流程以签署请求
sigv4:
  [ <sigv4_config> ]

# SNS主题ARN,即arn:aws:sns:us-east-2:698519295917:My-Topic。
# 如果不指定此值,则必须为phone_number或target_arn指定值。
# 如果您使用的是FIFO SNS主题,则应将消息组间隔设置为大于5分钟,以防止SNS默认重复数据消除窗口对具有相同组密钥的邮件进行重复数据消除
[ topic_arn: <tmpl_string> ]

# 邮件传递到电子邮件端点时的主题行.
[ subject: <tmpl_string> | default = '{{ template "sns.default.subject" .}}' ]

# 电话号码,如果消息是通过短信以E.164格式发送的。
# 如果不指定此值,则必须为topic_arn或target_arn指定值.
[ phone_number: <tmpl_string> ]

# 如果消息通过移动通知传递,则为移动平台端点ARN。
# 如果不指定此值,则必须为topic_arn或phone_number指定一个值。
[ target_arn: <tmpl_string> ]

# SNS通知的消息内容
[ message: <tmpl_string> | default = '{{ template "sns.default.message" .}}' ]

# SNS消息属性
attributes:
  [ <string>: <string> ... ]

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]
4.4.6.1、sigv4_config
# AWS地区,如果为空,则使用默认凭据链中的区域。
[ region: <string> ]

# AWS API密钥,必须同时提供access_key和secret_key或两者都为空。
# 如果为空,则使用环境变量 `AWS_ACCESS_KEY_ID` 和 `AWS_SECRET_ACCESS_KEY`。
[ access_key: <string> ]
[ secret_key: <secret> ]

# 用于身份验证的命名AWS配置文件
[ profile: <string> ]

# AWS角色ARN,使用AWS API密钥的替代方案。
[ role_arn: <string> ]
4.4.7、victorops_configs

VictorOps 通知通过VictorOps API发送

# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# 与VictorOps API对话时使用的API密钥
[ api_key: <secret> | default = global.victorops_api_key ]

# VictorOps API URL.
[ api_url: <string> | default = global.victorops_api_url ]

# 用于将告警映射到team的键
routing_key: <tmpl_string>

# 描述告警的行为(CRITICAL, WARNING, INFO).
[ message_type: <tmpl_string> | default = 'CRITICAL' ]

# 包含告警问题的摘要.
[ entity_display_name: <tmpl_string> | default = '{{ template "victorops.default.entity_display_name" . }}' ]

# 包含对告警问题的详细说明.
[ state_message: <tmpl_string> | default = '{{ template "victorops.default.state_message" . }}' ]

# 状态消息来自哪个监视工具.
[ monitoring_tool: <tmpl_string> | default = '{{ template "victorops.default.monitoring_tool" . }}' ]

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]
4.4.8、webhook_configs

webhook接收器允许配置通用接收器。

# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# 将HTTP POST请求发到哪里
url: <string>

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]

# 单个webhook消息中包含的最大告警数,超过此阈值的告警将被截断,默认为0时,即保留所有告警。
[ max_alerts: <int> | default = 0 ]

Alertmanager将以以下JSON格式向配置的端点发送 HTTP POST请求:

{
  "version": "4",
  "groupKey": <string>,              // 标识警报组的关键字(例如,去重)
  "truncatedAlerts": <int>,          // "max_alerts"截断了多少警报
  "status": "<resolved|firing>",
  "receiver": <string>,
  "groupLabels": <object>,
  "commonLabels": <object>,
  "commonAnnotations": <object>,
  "externalURL": <string>,           // 指向Alertmanager的反向链接
  "alerts": [
    {
      "status": "<resolved|firing>",
      "labels": <object>,
      "annotations": <object>,
      "startsAt": "<rfc3339>",
      "endsAt": "<rfc3339>",
      "generatorURL": <string>,      // 标识触发告警的实例
      "fingerprint": <string>        // 指纹识别警报
    },
    ...
  ]
}

有一个 与此功能的集成列表。

4.4.9、wechat_configs

微信通知通过微信 API发送。

# 是否通知接收者告警已解除,建议开启。
[ send_resolved: <boolean> | default = false ]

# 与wechat API对话时使用的API密钥
[ api_secret: <secret> | default = global.wechat_api_secret ]

# WeChat API URL.
[ api_url: <string> | default = global.wechat_api_url ]

# 用于身份验证的公司ID,在企业微信管理后台-企业信息-企业ID可以看到
[ corp_id: <string> | default = global.wechat_api_corp_id ]

# 微信API定义的API请求数据.
[ message: <tmpl_string> | default = '{{ template "wechat.default.message" . }}' ]
# 消息类型,支持text和markdown。
[ message_type: <string> | default = 'text' ]
[ agent_id: <string> | default = '{{ template "wechat.default.agent_id" . }}' ]
[ to_user: <string> | default = '{{ template "wechat.default.to_user" . }}' ]
[ to_party: <string> | default = '{{ template "wechat.default.to_party" . }}' ]
[ to_tag: <string> | default = '{{ template "wechat.default.to_tag" . }}' ]
4.4.10、telegram_configs
# 是否通知接收者告警已解除,默认开启。
[ send_resolved: <boolean> | default = true ]

# Telegram API URL,即https://api.telegram.org
# 未指定, 则使用默认API URL
[ api_url: <string> | default = global.telegram_api_url ]

# Telegram机器人token
[ bot_token: <string> ]

# 要向其发送消息的聊天ID,即发到哪个telegram群组。
[ chat_id: <int> ]

# 消息模板
[ message: <tmpl_string> default = '{{ template "telegram.default.message" .}}' ]

# 关闭telegram消息通知
[ disable_notifications: <boolean> | default = false ]

# telegram消息解析方式,支持Markdown V2、Markdown、Html、纯文本的空字符串.
[ parse_mode: <string> | default = "MarkdownV2" ]

# HTTP客户端配置
[ http_config: <http_config> | default = global.http_config ]

五、定义告警规则

在上章节定期执行规则小节中有提到Prometheus有两种规则,分别是记录规则和告警规则,Prometheus的配置文件全部是强语法的YAML格式。告警规则允许用户基于Prometheus表达式定义告警条件,并在触发告警后通知外部接收者。每当告警表达式的指定时间点产生一个或多个向量元素时,此告警规则将统计活跃元素的标签集。

为了能够让Prometheus启用定义的告警规则,需要在Prometheus全局配置模块中通过rule_files指定告警规则文件的路径。Prometheus启动后会自动扫描该路径下所有规则文件中定义的内容,并根据里面的规则计算是否达到触发阈值并通告。

在告警规则文件中,我们可以将一组相关的规则定义在同一个group。在每个组中,可以定义多个告警规则

groups:
  - name: <alert name>  # test_alert
    rules:
      - alert: <alertname>  # IfNodeUp
        expr: <expression>  # up{instance="localhost:9100"} != 1
        for: <duration>  # 2m
        labels:
          level: <label set>  # error
        annotations:summary
          description: <text>  # "instance: {{ $labels.instance }} was down!"
          summary: <text>  # 节点异常

选项参数如下:

  • alert:告警规则的名称,必须唯一。
  • expr:基于PromQL表达式的告警触发条件,用于计算是否有时间序列满足条件。
  • for:指定Prometheus服务等待时间。该参数表示当达到阈值后继续等待一段时间后才发送告警,等待期间,告警状态为pending,即挂起状态。
  • labels:允许指定额外的标签列表,并把它们附加到告警中。任何已存在的冲突标签都会被它重写,标签值也能够被模板化。自定义标签则允许用户指定要附加到告警中的一组附加标签
  • annotations:指定一组标签,不被当作告警实例的身份标识。该参数多用于存储额外的信息,比如告警描述,也可以模板化。这组附加信息,比如用于描述告警详细信息的文字等,在告警产生时会一同作为参数被发送到Alertmanager。

5.1、告警模板

告警消息中可以使用模板,它是一种在告警中以变量方式调用时间序列数据的标签及其值的方法,用于注解标签。模板使用的是标准的Go语法,并暴露一些包含时间序列的标签和值的变量。标签以变量$labels形式表示,指标以变量$value形式表示。比如,summary注解中可以通过{{ $labels.<labelname> }}{{ $value }}分别引用instance标签和时间序列的值。

可以在Alertmanager的配置文件中使用模板字符串,如下所示:

详情可见PromQL实战进阶之3.2小节

// $labelname可访问当前告警实例中指定标签的值
{{ $labels.<labelname> }}
 // $values可以获取expr的表达式计算结果
 {{ $value }}

也可以自定义可复用的模板文件,比如,创建自定义模板文件custom-template.tmpl

{{ define "slack.myorg.text" }}https://internal.myorg.net/wiki/alerts/{{ .GroupLabels.app }}/{{ GroupLabels.alertname }}{{ end }}

然后在Alertmanager配置文件中的全局配置中定义templates配置块来指定模板路径:

templates:
  [- <filepath>...]

在设置了自定义模板的访问路径后,用户就可以直接在配置中使用该模板

receivers:
  - name: "slack-notifications"
    slack_configs:
      - channels: "#alerts"
        text: '{{ template "slack.myorg.text" . }}'
  templates:
    - "/usr/local/prometheus/alertmanager/templates/custom-myorg.tmpl"

六、告警高级应用

本小节主要结合实践过程中可能会遇到的场景和问题(如告警分组、抑制、静默、延迟等)展开介绍,目的是提高理论与实践相结合的能力。

6.1、Prometheus告警失灵

有时候在运维Prometheus的时候,可能会遇到以下问题:

  • 为什么该告警时没告警?
  • 为什么不该告警时却告警?

上述问题的关键所在还是得先理清Prometheus的告警原理

groups:
  - name: test_alert
    rules:
      - alert: IfNodeUp
        expr: up{instance="localhost:9100"} != 1
        for: 2m
        labels:
          level: error
        annotations:summary
          description: "instance: {{ $labels.instance }} was down!"
          summary: 节点异常

for语句用于表示只有当达到触发条件后持续一段for指定的时间才会推送告警消息。在等待期间,新产生的告警为pending状态。这个参数主要作用是降噪,因为有些指标都是都是瞬间波动很大,单次采集不一定就真的是出问题了,只有持续一段时间,才能认为真的是出问题了。

如果配置中不设置for或者将其设置为0,那么pending状态将会被跳过,直接变为firing状态,同时发送相关告警信息给Alertmanager。

这意味着什么呢?

  • 在for等待的2m时间里,就算总指标100个有99个达到触发阈值也不会立刻发送告警。
  • 只有达到阈值后,且持续2m(for指定的时间),才会推送告警。

Prometheus以scrape_interval(默认1m)规则为周期,从监控目标上采集到指标信息后将其持久存储在本地;Prometheus以evaluation_interval(默认1m)规则为周期,对告警规则做定期计算。

来看看Prometheus关键配置

global:
  scrape_interval: 15s # 采集间隔,默认1分钟。
  evaluation_interval: 15s # 告警检测时间,即多久检查一次是否达到告警的条件,默认1m
  scrape_timeout: 10s

来看看Alertmanager的关键配置

route:
  group_by: [ 'alertname' ]
  # 发送一组告警的初始等待时间,即初次发送告警的延时。
  [ group_wait: <duration> | default = 30s ]
  # 发送完一组告警后,等待多久来发送同组新的告警信息,通常为5m或更久。
  group_interval: 1m
  # 重新发送告警的间隔,通常大于3h,低了就会短信轰炸,过来人都懂。
  repeat_interval: 1h

由于告警规则计算只是稀疏的采样点,因此告警规则中的告警持续时间和for指定的expr条件也是由这些稀疏采样点决定的。从上述配置来看,整体流程如下所示:

  1. Prometheus 15秒采集一次数据,然后根据采集到的状态以evaluation_interval评估周期15秒评估一次是否达到阈值。
  2. 当采集目标无法获取数据时,Prometheus会持续尝试采集,scrape_timeout时间后停止尝试
  3. 如果评估某指标达到预设的告警阈值,该告警规则变为active,告警状态切换为pending
  4. 如持续时间超过for指定的时间,告警状态变为firing,并将告警从Prometheus发送给Alertmanager
  5. 下个evaluation_interval计算周期若仍旧达到预设的阈值,且持续时间仍旧超过for指定的时间还是这样,那么将继续发送告警给Alertmanager。
  6. 直到某个evaluation_interval计算周期时表达式为否,低于预设阈值,则告警状态变更为inactive,并发送resolve通知给Alertmanager,说明此告警已消失。
  7. Alertmanager收到Prometheus的告警数据后,会将告警信息按照配置文件中group_by进行分组,然后根据配置里面的group_wait时间等待一段时间,过了这段时间再发送告警。
  8. 同组的告警,在等待期间可能会产生新的告警,如果之前的告警已经成功发送,那么等待group_interval时间后再重新发送告警信息
  9. 如果告警组里的告警一直没发生变化并且已经成功发送,那么将等待repeat_interval时间后再重复发送相同的告警消息;如果之前的告警没发送成功,那么需要等待group_interval时间后重复发送。

注意,Prometheus的告警检测时间evaluation_interval跟ALertmanager的group_wait是分开计算的,也就是说Prometheus在evaluation_interval时间计算后发现达到阈值,将告警消息发给Alertmanager,ALertmanager分组后等待group_wait时间后再进行发送。

group_wait设置过大,会导致告警推送延误;设置过小,会导致告警轰炸,建议根据不同告警的重要性进行配置。

三种告警状态如下:

  • inactive:没有达到阈值
  • pending:已达到阈值但未满足group_wait告警持续时间
  • firing:已达到阈值且满足group_wait持续时间

综上所述,不能通过Grafana展示的趋势图来直接匹配告警的情况,因为它们的数据源不一致。Grafana的数据源来自于Prometheus API发出的Range Query等查询,是一直持续获取数据的;而告警的数据源则是稀疏采样点,所以告警的数据源往往可能跳过一些原始数据。

这意味着在for等待过程中,原始指标已经恢复正常,但告警模块恰好错过了,那么达到for时间后就可能继续触发这次告警。还看不明白?因为在for等待期间,是属于Prometheus阶段,还没发给ALertmanager分组和group_wait,这就可能导致不该告警的时候偏偏告警了。因此,建议将设定了告警阈值的指标做成记录规则,然后针对该指标做阈值告警,这在kube-prometheus的告警规则中就大量采用这种方式。

除此之外,告警分组、抑制、静默、延迟尤其是在大规模监测中时计算等可能会有延迟导致告警丢失或者延迟发送,这种情况属于ALertmanager告警治理,后面将对此介绍。

6.2、告警轰炸问题

在运维监控系统时,多少会遇到告警轰炸的情况,本小节就来介绍告警过程中合理收敛的管控方式。

6.2.1、分组

分组(grouping)机制指的是ALertmanager将同类型的告警进行分组,将同一类型的多条告警合并到一条告警通知里面来,这样可以减少告警通知的次数,通过精简的方式帮助分析问题。比如,A和B主机都宕机了,就可以合并在一条告警通知里面,而不用分开发告警通知。

分组是将性质类似的告警归纳成一个通知类,不同性质分不同分组,互不干扰。再比如,还是主机宕机,假设是一个集群里面有几百台主机宕机了,不分组则将会产生数百条告警通知,这可能会令人崩溃;但有分组的话,只需要一条告警,里面说明哪些主机宕机即可。

如果仅想看到一个能精确描述具体实例受影响的页面,ALertmanager也可以通过集群和告警名称分组,然后发送一个单独受影响的通知。

告警分组、分组通知的时间和通知的接收者都是在配置文件中由route负责。分组还有一个概念,就是告警延迟:group_wait,合理分配分组延时,可避免告警不及时的问题并避免告警轰炸。

group_wait参数主要作用在第2小节的架构图中的Dedup部分,此阶段有4个重要参数可以调整:

  • group_by:分组参数
  • group_wait:第一次发送通知之前等待并缓冲同一组告警的时间,这使ALertmanager可以等待告警到达或为同一组收集更多初始告警。它实际上缓冲了从Prometheus发送到ALertmanager的告警,这些告警按相同的标签分组。虽然减少了告警频率,但可能会导致接收告警通知的时间更长。
  • group_interval:发送一组告警的初始等待时间,即初次发送告警的延时。
  • repeat_interval:重新发送相同告警的间隔。比如我主机宕机了,一直没处理,那么就一直有告警,发完第一次后以后每次都要等repeat_interval时间才会发送第二次。

group_wait是比较重要的。比如一个分组同时有A、B两个告警,A先到达进去group_wait里面蹲着,在group_wait期间,B也进来了,两人合并成一组,并发送一个告警消息出去;发完后A、B的问题还未解决,还持续触发告警,然后分组内又来了个新人C,这就触发了group_interval,由于同组的状态发生了变化,A、B、C会在group_interval内被快速推送告警,不会等到group_interval后再发;如果发完后,A、B、C三者都持续无变化,那么就会在repeat_interval周期性发送告警信息。

6.2.2、抑制

ALertmanager的抑制(Inhibition)指的是:当某告警已经发出时,停止重复发送此告警引发的其它异常或故障。

告警抑制主要用于消除冗余的告警。譬如,A主机宕机了,那么A主机上的诸如:MySQL、Redis等应用服务的告警还有触发意义吗?这时可以进行告警的抑制,来消除冗余的告警,帮助系统第一时间掌握最核心最有用的告警消息。

在ALertmanager配置文件中,可使用inhibit_rules定义一组告警的抑制规则,语法见[4.3小节](# 4.3、抑制规则(inhibit_rule))。

6.2.3、静默

告警静默(silence)提供了相比抑制简单得多的机No alert groups found制,它可以根据标签快速对告警进行静默(可以理解为静音免打扰)处理。对传入的告警进行匹配检查,如果收到的告警符合静默规则,ALertmanager则不会发送该告警。此外,管理员也可以直接在ALertmanager的web页面临时屏蔽指定的告警通知,比如:http://localhost:9093。

静默用于阻止推送可预计的告警,常用于无人值守时间的场景,再比如你的机器正在维修期间,不希望因此收到告警。

七、告警集群

ALertmanager包含由HashiCorp Memberlist库提供的集群功能。为了避免单点问题,通常会对多个ALertmanager进行高可用集群部署。

ALertmanager节点之间通过Gossip机制进行集群间成员管理和故障检测,以确保在多个ALertmanager分别接收到相同告警通知的情况下,只有一个告警通知被发送给接收者。

7.1、常见参数

ALertmanager节点间需要保持配置一致,其常见参数(--cluster.*)如下:

# 当前实例集群服务监听地址,默认0.0.0.0:9094,空字符串将禁止高可用。
--cluster.listen-address <string>
# 集群发布地址
--cluster.advertise-address <string>
# 初始化时关联的其它实例的集群服务地址(每个其它对等方的重复标志)
--cluster.peer <value>
# 对等超时时间,默认15s。
--cluster.peer-timeout <value>
# 集群消息传播时间,默认200ms。
--cluster.gossip-interval <value>
# 收敛时间,较低的值将增加带宽,默认10ms。
--cluster.pushpull-interval <value>
# 评估发送通知之前等待集群连接建立的最长时间
--cluster.settle-timeout <value>
# TCP连接超时时间,默认10s
--cluster.tcp-timeout value
# 在标记节点不正常之前等待确认时间,默认500ms。
--cluster.probe-timeout <value>
# 随机节点之间探测的间隔,默认1s。
--cluster.probe-interval <value>
# 重新尝试连接到丢失的对等设备之间的间隔,默认10s。
--cluster.reconnect-interval <value>

7.2、构建集群

因为资源有限,我是直接在本机进行集群的部署,有条件的朋友可以在多台服务器部署。

7.2.1、节点启动参数
主节点配置

从节点的--cluster.peer填的是主节点

# alertmanager本身监听9093,集群端口是9094,二者独立分开的。
/usr/local/alertmanager/alertmanager --web.listen-address=192.168.69.1:9093 --cluster.listen-address=192.168.69.1:9094
从节点1

可以制作成service启动,用这种方式仅是为了更好展示启动参数。

# alertmanager本身监听9093,集群端口是9094,二者独立分开的。
/home/sanxi/alert/alertmanager --web.listen-address=192.168.69.42:9093 --cluster.listen-address=192.168.69.42:9094 --cluster.peer=192.168.69.1:9094 > alert.log 2>&1 &

有防火墙的需要放开规则,以下为centos7示例:

# 放通TCP
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=tcp port=9093-9094 accept'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=tcp port=9090 accept'
# 放通UDP
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=udp port=9093-9094 accept'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=udp port=9090 accept'
# 重载配置
sudo firewall-cmd --reload
从节点2

可以制作成service启动,用这种方式仅是为了更好展示启动参数。

# alertmanager本身监听9093,集群端口是9094,二者独立分开的。
nohup /home/sanxi/alert/alertmanager --web.listen-address=192.168.69.43:9093 --cluster.listen-address=192.168.69.43:9094 --cluster.peer=192.168.69.1:9094 > alert.log 2>&1 & 

有防火墙的记得放通防火墙规则,以下为centos7示例:

# 放通TCP
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=tcp port=9093-9094 accept'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=tcp port=9090 accept'
# 放通UDP
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=udp port=9093-9094 accept'
sudo firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="192.168.69.0/24" port protocol=udp port=9090 accept'
# 重载配置
sudo firewall-cmd --reload
7.2.2、Prometheus配置

集群各节点启动完毕后,需要在prometheus.yml配置中添加节点并重载配置。

# 告警配置
alerting:
  alertmanagers:
    # 静态配置alertmanager地址,也可以使用服务动态识别。
    - static_configs:
        - targets:  # 可以有多个地址
          - 192.168.69.1:9093
          - 192.168.69.42:9093
          - 192.168.69.43:9093
7.2.3、查看集群状态
./amtool cluster show  # 查看ALertmanager集群状态
Cluster Status:  ready
Node Name:       01G47GV3EXJPATVN2ZZG84YSKB

Address             Name
192.168.69.1:9094   01G47GV3EXJPATVN2ZZG84YSKB  
192.168.69.42:9094  01G47GZP9CQZ6KMDFS0FKP3ZCZ  
192.168.69.43:9094  01G47H39ZFR3CJKTGJCNPN48DE 

7.3、验证告警

我先把node_exporter停掉以触发告警,然后再停掉主节点的ALertmanager,看看从节点反应。

从节点1收到告警

[sanxi@test1 alert]$ ./amtool alert query --alertmanager.url="http://192.168.69.42:9093"
Alertname  Starts At                Summary  State   
IfNodeUp   2022-05-29 15:59:42 UTC  节点异常     active 

从节点2收到告警

[sanxi@test2 alert]$ ./amtool alert query --alertmanager.url="http://192.168.69.43:9093"
Alertname  Starts At                Summary  State   
IfNodeUp   2022-05-29 15:59:42 UTC  节点异常     active 

再看看企业微信的告警通知

image20220530001629243.png

八、告警推送企业微信

参考企业微信官方文档:https://developer.work.weixin.qq.com/tutorial/robot

在第二大章节我们初学告警时,有展示过如何接入钉钉群机器人,但是比较麻烦,要使用第三方插件,现在最新版的Prometheus已经支持微信、telegram等10种告警通知方式,详情可见4.4小节

8.1、添加应用

8.1.1、自定义应用

登录企业微信管理后台-->应用管理-->应用,拉到最下面有个创建应用

image20220526182610469.png

logo、应用名称、应用介绍、可见范围都是必填的

image20220526210254598.png

8.1.2、查看信息

创建成功后,会得到应用的agentidsecret,这在后面发起POST请求是必须的。

image20220526210442870.png

8.2、获取信息

8.2.1、获取部门ID

在企业微信管理后台,点击通讯录,选择需要接收告警的部门,然后点击其右边的三个点,可以看到部门ID号。

image20220527091601054.png

8.2.2、获取企业ID

在企业微信管理后台,点击我的企业-->企业信息,在最下面可以看到企业ID,记录下来。

image20220527091923677.png

8.2.3、获取token

需要先发起GET请求来获取token,有效期只有两个小时,需要自己写脚本去定期获取。

curl "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=xxx&corpsecret=xxx"

返回格式如下所示:

{"errcode":0,"errmsg":"ok","access_token":"xxxxxx","expires_in":7200}

8.3、ALertmanager配置

alertmanager.yml添加wechat_api相关配置参数,如下所示:

global:
  resolve_timeout: 5m
  smtp_smarthost: 'smtp.qq.com:465'
  smtp_from: 'smtp.qq.com'
  smtp_auth_username: '88888888'
  smtp_auth_password: 'xxxxxxxx'
  wechat_api_url: "https://qyapi.weixin.qq.com/cgi-bin/"  # 默认API
  wechat_api_secret: "xxxxxx"  # 创建应用后获得密文
  # # 企业微信管理后台-企业信息-企业ID可以看到
  wechat_api_corp_id: "ww310339a0c471a2fe"

route:
  group_by: [ 'alertname', 'cluster' ]
  group_wait: 30s
  group_interval: 1m
  repeat_interval: 1h
  receiver: 'wechat'  # 接收器改为微信

templates:
  # 微信告警模板路径
  - "/usr/local/prometheus/alertmanager-0.24.0.linux-amd64/templates/wechat.tmpl"

receivers:
  - name: 'mail-receiver'
    email_configs:
      - to: 'imap.qq.com'
  - name: 'dingtalk'
    webhook_configs:
    - url: 'http://localhost:8060/dingtalk/webhook1/send'
      send_resolved: true
  - name: "wechat"  # 微信接收器
    wechat_configs:
      # 是否通知接收者告警已解除,建议开启。
      - send_resolved: true
        # 与wechat API对话时使用的API密钥
        api_secret: "xxxxxx"
        # WeChat API URL,不设置则默认使用全局设置。
        api_url: "https://qyapi.weixin.qq.com/cgi-bin/"
        # 用于身份验证的公司ID,不设置则默认使用全局
        corp_id: "ww310339a0c471a2fe"
        # 告警通知的内容,可使用模板。
        message: '{{ template "wechat.default.message" . }}'
        # 消息类型,支持text和markdown。
        message_type: 'text'
        agent_id: "1000003"
        # 指定通知接收者,不设置则默认@所有人
        #to_user: '{{ template "wechat.default.to_user" . }}
        to_party: '2'  # 部门ID号
        #to_tag: '{{ template "wechat.default.to_tag" . }}'

inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: [ 'alertname', 'dev', 'instance' ]

8.4、自定义告警模板

根据templates定义的模板文件,创建该文件并添加以下内容:

{{ define "wechat.default.message" }}
{{- if gt (len .Alerts.Firing) 0 -}}
{{- range $index, $alert := .Alerts -}}
{{- if eq $index 0 }}
========= 监控报警 =========
告警状态:{{   .Status }}
告警级别:{{ .Labels.severity }}
告警类型:{{ $alert.Labels.alertname }}
故障主机: {{ $alert.Labels.instance }}
告警主题: {{ $alert.Annotations.summary }}
告警详情: {{ $alert.Annotations.message }}{{ $alert.Annotations.description}};
触发阀值:{{ .Annotations.value }}
故障时间: {{ ($alert.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
========= = end =  =========
{{- end }}
{{- end }}
{{- end }}
{{- if gt (len .Alerts.Resolved) 0 -}}
{{- range $index, $alert := .Alerts -}}
{{- if eq $index 0 }}
========= 异常恢复 =========
告警类型:{{ .Labels.alertname }}
告警状态:{{   .Status }}
告警主题: {{ $alert.Annotations.summary }}
告警详情: {{ $alert.Annotations.message }}{{ $alert.Annotations.description}};
故障时间: {{ ($alert.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
恢复时间: {{ ($alert.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
{{- if gt (len $alert.Labels.instance) 0 }}
实例信息: {{ $alert.Labels.instance }}
{{- end }}
========= = end =  =========
{{- end }}
{{- end }}
{{- end }}
{{- end }}

8.5、重载配置文件

curl -X POST "http://localhost:9093/-/reload"

8.6、验证效果

我故意停掉node_exporter好触发告警,然后企业微信收到消息了。

image20220527151410057.png

世间微尘里 独爱茶酒中