prometheus配置告警模块AlertManager

Alertmanager 主要用于接收 Prometheus 发送的告警信息,它支持丰富的告警通知渠道,而且很容易做到告警信息进行去重,降噪,分组等,是一款前卫的告警通知系统。

Prometheus会根据配置的参数周期性的对警报规则进行计算, 如果满足警报条件,生产一条警报信息,将其推送到 Alertmanager 组件,Alertmanager 收到警报信息之后,会对警告信息进行处理,进行 分组 Group 并将它们通过定义好的路由 Routing 规则转到 正确的接收器 receiver, 比如 Email、 钉钉、企业微信(webhook)等。

prometheus触发一条告警的过程: prometheus—>触发阈值—>超出持续时间—>alertmanager—>分组|抑制|静默—>媒体类型—>邮件|钉钉|微信等。

二进制部署 Alertmanager

wget https://github.com/prometheus/alertmanager/releases/download/v0.25.0/alertmanager-0.25.0.linux-amd64.tar.gz

tar zxvf alertmanager-0.25.0.linux-amd64.tar.gz

mv alertmanager-0.25.0.linux-amd64/ alertmanager

设置为系统服务

系统service编写系统service编写

vim /etc/systemd/system/alertmanager.service
#创建系统服务
[Unit]
Description=alertmanager
Documentation=https://prometheus.io/
After=network.target

[Service]
User=root
Type=simple
ExecStart=/usr/local/prometheus/alertmanager/alertmanager --config.file=/usr/local/prometheus/alertmanager/alertmanager.yml --storage.path=/usr/local/prometheus/alertmanager/data --web.listen-address=:9093 --cluster.listen-address=0.0.0.0:9094 --web.external-url=http://127.0.0.1:9093
Restart=on-failure

[Install]
WantedBy=multi-user.target

#重载系统服务并启动
systemctl daemon-reload
systemctl enable --now alertmanager
systemctl status alertmanager

添加告警规则路径,并创建告警规则

#添加告警路径、添加alertmanager配置
vim  prometheus.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ['127.0.0.1:9093']
rule_files:
  - "/usr/local/prometheus/rules/*.yml"



#添加告警规则
vim  rules/iwork_rule.yml

#例:
groups:
 - name:  iwork服务器异常
   rules:
   - alert: 应用程序池运行状态
     expr: windows_iis_current_application_pool_state{app!~".NET v4.5|.NET v4.5 Classic",node_name='iwork',state="Running"} == 0
     for: 30s
     labels:
       severity: warning
     annotations:
       message: "iwork服务器{{ $labels.instance }},当前应用程序池  {{ $labels.app }}  异常超过30秒!"

配置alertmanager

global:
  # resolve_timeout:解析超时时间
  resolve_timeout: 5m
  # smtp_smarthost: 使用email打开服务配置
  smtp_smarthost: 'smtp.126.com:465'
  # smtp_from:指定通知报警的邮箱
  smtp_from: 'laowang@126.com'
  # smtp_auth_username:邮箱用户名
  smtp_auth_username: 'laowang@126.com'
  # smtp_auth_password:授权密码
  smtp_auth_password: '123456'
  # smtp_require_tls:是否启用tls
  smtp_require_tls: false
 
# route标记:告警如何发送分配
route:
  # group_by:采用哪个标签作为分组的依据
  group_by: ['alertname']
  # group_wait:分组等待的时间
  group_wait: 10s
  # group_interval:上下两组发送告警的间隔时间
  group_interval: 10s
  # repeat_interval:重复发送告警时间。默认1h
  repeat_interval: 1m
  # receiver 定义谁来通知报警
  receiver: 'mail'
 
# receiver标记:告警接受者 
receivers:
# name:报警来源自定义名称
- name: 'mail'
  # email_configs:通过邮箱发送报警
  email_configs:
    # to:指定接收端email
    - to: 'laowang@126.com'
# inhibit_rules标记:
#inhibit_rules:
#  - source_match:
#      severity: 'critical'
#    target_match:
#      severity: 'warning'
#    equal: ['alertname', 'dev', 'instance']

主要配置的作用:

global: 全局配置,包括报警解决后的超时时间、SMTP 相关配置、各种渠道通知的 API 地址等等。

route: 用来设置报警的分发策略,它是一个树状结构,按照深度优先从左向右的顺序进行匹配。

receivers: 配置告警消息接受者信息,例如常用的 email、wechat、slack、webhook 等消息通知方式。

inhibit_rules: 抑制规则配置,当存在与另一组匹配的警报(源)时,抑制规则将禁用与一组匹配的警报(目标)。

smtp_smarthost: 这里为 QQ 邮箱 SMTP 服务地址,官方地址 smtp.qq.com 端口为 465 或 587,同时设置开启 POP3/SMTP 服务。

smtp_auth_password: 这里为第三方登录 QQ 邮箱的授权码,非 QQ 账户登录密码,否则会报错,获取方式在 QQ 邮箱服务端设置开启 POP3/SMTP 服务时会提示。

检查alertmanager配置文件

./amtool check-config alertmanager.yml

对接企业微信机器人

告警的流程: prometheus-server采集到的各种exporter的指标,然后根据规则去判断是不是达到规则的阈值,如果达到就判断要告警,将告警的内容发送给altermanager, altermanager是用来处理告警的,比如接收prometheus-server发来的告警信息,然后做一些整合,然后发送给消息发送组件,整合具体做了什么呢,就是告警信息的聚合、告警信息的沉默、告警分组等,altermanager将告警消息的发出,可以通过altermanager的smtp发出,也可以通过消息发送组件webhook集成,这些组件一般是自己开发或者借鉴别人开发的。

接下来要介绍一下webhook与钉钉或企业微信机器人结合实现告警消息的推送。

企业微信版webhook组件

altermanager将告警消息,通过webhook协议发送到 企业微信版webhook组件。企业微信版webhook组件 再把告警转发到企微官方api。

这里使用的企业微信版webhook,使用了flask开发的,并定制消息的样式。

发送企业微信版告警消息,是调用的群机器人,所以需要将接受告警消息的人拉到一个群里,然后添加一个告警机器人,就能看到群机器人webhook地址:https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=XXXXXX-YYYYYYYY-666666-88888888, 此地址可以分成两部分, 一部分key=(包含key=)之前的部分,是api接口地址。 一部分是key=之后的部分。相当于机器人身份证号,唯一的。暂时赋予一个名称叫 ROBOT_TOKEN 吧,后面要用到。

示例环境:centos7

#安装python3,centos7系统自带的是python2。
yum install -y python3
#安装需要的python的包
pip3 install requests
pip3 install arrow
pip3 install flask


#在Centos中设置系统变量,app.py中需要使用
export ROBOT_TOKEN=XXXXXX-YYYYYYYY-666666-88888888
# vim /etc/profile 设置系统环境变量,修改文件后,刷新生效命令
source /etc/profile




新建flask框架的app程序应用如下

mkdir /opt/monitor/webhook && cd /opt/monitor/webhook
vim app.py

# -*- coding: utf-8 -*-
import os
import json
import requests
import arrow
from flask import Flask
from flask import request
app = Flask(__name__)
def bytes2json(data_bytes):
    data = data_bytes.decode('utf8').replace("'", '"')
    return json.loads(data)
def makealertdata(data):
    for output in data['alerts'][:]:
        try:
            pod_name = output['labels']['pod']
        except KeyError:
            try:
                pod_name = output['labels']['pod_name']
            except KeyError:
                pod_name = 'null'
        try:
            namespace = output['labels']['namespace']
        except KeyError:
            namespace = 'null'
        try:
            message = output['annotations']['message']
        except KeyError:
            try:
                message = output['annotations']['description']
            except KeyError:
                message = 'null'
        if output['status'] == 'firing':
            status_zh = '报警'
            title = '【%s】xxxx环境 %s 有新的报警' % (status_zh, output['labels']['alertname'])
            send_data = {
                "msgtype": "markdown",
                "markdown": {
                    "content": "## %s \n\n" %title +
                            ">**告警级别**: %s \n\n" % output['labels']['severity'] +
                            ">**告警类型**: %s \n\n" % output['labels']['alertname'] +
                            ">**告警主机**: %s \n\n" % output['labels']['node_name'] +
                            ">**告警详情**: %s \n\n" % message +
                            ">**告警状态**: %s \n\n" % output['status'] +
                            ">**触发时间**: %s \n\n" % arrow.get(output['startsAt']).to('Asia/Shanghai').format(
                        'YYYY-MM-DD HH:mm:ss ZZ')
                }
            }
        elif output['status'] == 'resolved':
            status_zh = '恢复'
            title = '【%s】xxxx环境 %s 有报警恢复' % (status_zh, output['labels']['alertname'])
            send_data = {
                "msgtype": "markdown",
                "markdown": {
                    "content": "## %s \n\n" %title +
                            ">**告警级别**: %s \n\n" % output['labels']['severity'] +
                            ">**告警类型**: %s \n\n" % output['labels']['alertname'] +
                            ">**告警主机**: %s \n\n" % output['labels']['node_name'] +
                            ">**告警详情**: %s \n\n" % message +
                            ">**告警状态**: %s \n\n" % output['status'] +
                            ">**触发时间**: %s \n\n" % arrow.get(output['startsAt']).to('Asia/Shanghai').format(
                        'YYYY-MM-DD HH:mm:ss ZZ') +
                            ">**触发结束时间**: %s \n" % arrow.get(output['endsAt']).to('Asia/Shanghai').format(
                        'YYYY-MM-DD HH:mm:ss ZZ')
                }
            }
        return send_data
def send_alert(data):
  #此处获取环境变量“ROBOT_TOKEN”,会在docker-compose的配置文件中配置,docker-compose启动docker时向docker容器注入环境变量
    #print(data)
    token = os.getenv('ROBOT_TOKEN')
    if not token:
        print('you must set ROBOT_TOKEN env')
        return
    url = 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s' % token
    send_data = makealertdata(data)
    print(send_data)
    req = requests.post(url, json=send_data)
    print(req)
    result = req.json()
    if result['errcode'] != 0:
        print('notify dingtalk error: %s' % result['errcode'])
@app.route('/', methods=['POST', 'GET'])
def send():
    if request.method == 'POST':
        post_data = request.get_data()
        #print(post_data)
        send_alert(bytes2json(post_data))
        return 'success'
    else:
        return 'weclome to use prometheus alertmanager dingtalk webhook server!'
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

启动企业微信版webhook组件

python3 app.py

#或者后台启动
nohup python3 -u app.py > app.log 2>&1 &

配置重启altermanager

global:
  resolve_timeout: 5m

route:
  group_by: ['instance']
  group_wait: 10s
  group_interval: 5m
  repeat_interval: 1h
  receiver: 'ops_notify'

  routes:
  - receiver: ops_notify
    group_wait: 10s
    match_re:
      alertname: 'NodeStatsAlert'
receivers:
  - name: 'ops_notify'
    webhook_configs:
      - url: 'http://10.210.11.12:5000'
        send_resolved: true
inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['alertname', 'dev', 'instance']

重启

systemctl restart alertmanager

状态说明 Prometheus Alert 告警状态有三种状态:Inactive、Pending、Firing。

Inactive:非活动状态,表示正在监控,但是还未有任何警报触发。 Pending:表示这个警报必须被触发。由于警报可以被分组、压抑/抑制或静默/静音,所 以等待验证,一旦所有的验证都通过,则将转到 Firing 状态。 Firing:将警报发送到 AlertManager,它将按照配置将警报的发送给所有接收者。一旦警 报解除,则将状态转到 Inactive,如此循环。 当从Pending状态,转变为Firing状态,即触达到ALertManager,推送邮件信息。

企微群机器人 发送的内容示例:

【报警】xxxx环境 NodeExporterDown 有新的报警
 告警级别: warning 
 告警类型: NodeExporterDown 
 告警主机: 115 
 告警详情: 服务器115端口9100探测失败,请尽快检查node_exporter是否出现异常! 
 告警状态: firing 
 触发时间: 2023-03-02 14:43:28 +08:00 
 

钉钉版webhook组件

# -*- coding: utf-8 -*-
import os
import json
import requests
import arrow
from flask import Flask
from flask import request
app = Flask(__name__)
def bytes2json(data_bytes):
    data = data_bytes.decode('utf8').replace("'", '"')
    return json.loads(data)
def makealertdata(data):
    for output in data['alerts'][:]:
        try:
            pod_name = output['labels']['pod']
        except KeyError:
            try:
                pod_name = output['labels']['pod_name']
            except KeyError:
                pod_name = 'null'
        try:
            namespace = output['labels']['namespace']
        except KeyError:
            namespace = 'null'
        try:
            message = output['annotations']['message']
        except KeyError:
            try:
                message = output['annotations']['description']
            except KeyError:
                message = 'null'
        if output['status'] == 'firing':
            status_zh = '报警'
            title = '【%s】 %s 有新的报警' % (status_zh, output['labels']['alertname'])
            send_data = {
                "msgtype": "markdown",
                "markdown": {
                    "title": title,
                    "text": "## %s \n\n" %title +
                            ">**告警级别**: %s \n\n" % output['labels']['severity'] +
                            ">**告警类型**: %s \n\n" % output['labels']['alertname'] +
                            ">**告警主机**: %s \n\n" % output['labels']['node_name'] +
                            ">**告警详情**: %s \n\n" % message +
                            ">**告警状态**: %s \n\n" % output['status'] +
                            ">**触发时间**: %s \n\n" % arrow.get(output['startsAt']).to('Asia/Shanghai').format(
                        'YYYY-MM-DD HH:mm:ss ZZ')
                }
            }
        elif output['status'] == 'resolved':
            status_zh = '恢复'
            title = '【%s】 %s 有新的报警' % (status_zh, output['labels']['alertname'])
            send_data = {
                "msgtype": "markdown",
                "markdown": {
                    "title": title,
                    "text": "## %s \n\n" %title +
                            ">**告警级别**: %s \n\n" % output['labels']['severity'] +
                            ">**告警类型**: %s \n\n" % output['labels']['alertname'] +
                            ">**告警主机**: %s \n\n" % output['labels']['node_name'] +
                            ">**告警详情**: %s \n\n" % message +
                            ">**告警状态**: %s \n\n" % output['status'] +
                            ">**触发时间**: %s \n\n" % arrow.get(output['startsAt']).to('Asia/Shanghai').format(
                        'YYYY-MM-DD HH:mm:ss ZZ') +
                            "  **触发结束时间**: %s \n" % arrow.get(output['endsAt']).to('Asia/Shanghai').format(
                        'YYYY-MM-DD HH:mm:ss ZZ')
                }
            }
        return send_data
def send_alert(data):
    token = os.getenv('ROBOT_TOKEN')
    if not token:
        print('you must set ROBOT_TOKEN env')
        return
    url = 'https://oapi.dingtalk.com/robot/send?access_token=%s' % token
    send_data = makealertdata(data)
    req = requests.post(url, json=send_data)
    result = req.json()
    if result['errcode'] != 0:
        print('notify dingtalk error: %s' % result['errcode'])
@app.route('/', methods=['POST', 'GET'])
def send():
    if request.method == 'POST':
        post_data = request.get_data()
        print(post_data)
        send_alert(bytes2json(post_data))
        return 'success'
    else:
        return 'weclome to use prometheus alertmanager dingtalk webhook server!'
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
文章作者: Stars
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Stars
默认分类 network monitor
喜欢就支持一下吧