prometheus配置告警模块AlertManager
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)