Prometheus钉钉或微信报警
用来说明Prometheus使用Alertmanager报警到钉钉或微信的实现过程,相关export的安装不再进行说明。
这是一个我根据自己的理解画的流程图,后面我会根据这些来说明:
Prometheus
prometheus.yml
这是一个示例文件,基于此说明Prometheus的配置:
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
#- "localhost:9093"
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "rules/node.yml"
#- "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: "ENV_TEST_HOST"
file_sd_configs:
- files:
- "/opt/prometheus/target/host_status.json"
refresh_interval: 1m
relabel_configs:
- source_labels:
- "__address__"
regex: "(.*):9119"
target_label: "instance"
action: replace
replacement: "$1"
配置主要分为下面四个部分:
global:
全局配置文件,默认配置了两项- scrape_interval:Prometheus从目标抓取指标的频率
- evaluation_interval:评估规则的频率
alerting:
配置报警器,对于alertmanager,指定地址即可rule_files:
Prometheus 基于此文件进行判断是否报警,可以指定多个,可以使用通配符scrape_configs:
对Prometheus抓取数据进行配置,基本分为两类file_sd_configs:定义发现规则,可以是静态定义或者基于文件或者服务或者DNS,不同的发现方式对应不同的关键字,比如静态定义的static_configs
relabel_configs:将抓取到的label进行重新配置,label是Prometheus非常重要的特性,这个label来自exporter和target中的配置。可以在
Discovered Labels
处查看发现到的lables,relabels之后在Target Labels
处,此处的label即可被其他程序使用
就我目前所知,采集的指标也就是metric中的信息无法提取添加到labels
target
下面是一个示例配置:
[
{
"targets": [
"192.168.4.247:9119",
"192.168.5.176:9119"
],
"labels": {
"job": "env_test_host",
"app": "a",
"service": "test_service"
}
},
{
"targets": [
"192.168.3.21:9119",
"192.168.4.172:9119"
],
"labels": {
"job": "env_test_host",
"app": "cjdropship",
"service": "test_service"
}
}
]
配置是一个JSON格式的文件,主要分为三部分:
targets:
Prometheus会通过这里配置的地址发现所要监控的对象,对象可以是exports暴露的端口,也可以是集成Prometheus的程序提供的地址labels:
这里是自定义labels的地方,一般用来标识targets的属性。targets中定义的target越少,labels的定义粒度越细。这里此处的定义会在Prometheus抓取的时候添加到Discovered Labels
metrics_path:
通常是不需要定义的,默认会去/metrics
这个URI去查找,如果有不同URI,可以在此处定义
rules
此处是配置报警规则的地方,Prometheus根据这里的定义判断是否需要发送报警信息。下面是一个示例:
groups:
- name: Node_alert
rules:
- alert: "实例丢失"
expr: up == 0
for: 60s
labels:
severity: page
annotations:
summary: "Prometheus中断了与 {{ $labels.instance }} 的连接"
description: "{{ $labels.instance }} 已经失联至少 1 分钟了"
- alert: "可用内存不足10%"
expr: ((node_memory_MemTotal_bytes - node_memory_MemFree_bytes - node_memory_Buffers_bytes - node_memory_Cached_bytes) / (node_memory_MemTotal_bytes )) * 100 > 90
for: 300s
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} 可用内存不足"
description: "{{ $labels.instance }} 内存资源已不足10%"
- groups:
给报警信息分组,根据此信息可以在Alertmanager中定义不同的路由发送给不同的接收者 - rules: 具体的规则信息主要有下面几项
- alert:报警名称,这个信息会随警告发出
- expr:表达式,通过此语句判断是否需要发出告警
- for:持续此时间之后发出告警,在此之前告警信息会处于pending状态
- labels:
- severity:一般使用此项表示告警的紧急程度,在需要配置告警抑制的时候,此项至关重要,此项也会随报警发送
- annotations:这是一个自定义选项,甚至可以没有。这里配置的信息会随着告警信息发送,至于summary和description,自定义的,想写点啥就写点啥。
关于变量,在annotations中使用了
{{ $labels.instance }}
之类的变量,常用的还有{{ $value }}
,这个是模版语言的引用方式,其中labels
中可用的变量来自于target中配置和自动抓取到的label,即在Prometheus页面中通过Target labels
查看到的那些。至于value
这个来自expr表达式查询出来的结果。
Alertmanager
Prometheus自身并没有报警这个功能,它只能把信息发出来,至于怎么发,这个就需要通过告警组件来实现,对于Prometheus来说,最常用的就是Alertmanager。
alertmanager.yml
这里有一个比较完善的Alertmanager的配置:
global:
resolve_timeout: 5m
templates:
- '/etc/alertmanager/template/*.tmpl'
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 3h
receiver: web.hook
routes:
- match_re:
service: ^(foo1|foo2|baz)$
receiver: web.hook
routes:
- match:
severity: critical
receiver: wechat
# - match:
# service: files
# receiver: team-Y-mails
# routes:
# - match:
# severity: critical
# receiver: team-Y-pager
#
# - match:
# service: database
# receiver: team-DB-pager
# group_by: [alertname, cluster, database]
# routes:
# - match:
# owner: team-X
# receiver: team-X-pager
# continue: true
# - match:
# owner: team-Y
# receiver: team-Y-pager
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'cluster', 'service']
receivers:
- name: 'web.hook'
webhook_configs:
# - url: 'http://127.0.0.1:8060/dingtalk/ops_dingding/send'
- url: 'http://192.168.3.53:5000/'
send_resolved: true
- name: 'wechat'
wechat_configs:
- send_resolved: false
to_user: ''
to_party: '1'
agent_id: '1000002'
corp_id: ''
api_secret: ''
message: '{{ template "wechat.default.message" . }}'
这个配置主要包含了下面的五个配置(这个配置文件不可以直接使用):
global:
全局配置,通常只有一项配置- resolve_timeout:经过此时间后,如果尚未更新告警,则将告警声明为已恢复
templates:
指定发送信息的模版,模版使用的是模版语言编写的tmpl格式的文件,可以使用通配符匹配文件,也可以在一个文件中编写多个模版,在配置receivers的时候,可以指定使用哪个模版route:
这里注释了多条路由,定义了多条路由,通常不会又这么麻烦的路由信息。多数情况,发到一两个群。- group_by:分组,同一个分组下的告警将聚合为一条信息发送以避免频繁的消息提醒,如果不想使用分组,可以这样写
group_by: [...]
- group_wait:第一组告警发送通知需要等待的时间,这种方式可以确保有足够的时间为同一分组获取多个告警,然后一起触发这个告警信息。
- group_interval:发送第一个告警后,等待”group_interval”发送一组新告警。
- repeat_interval:相同的消息在此时间间隔之后再次发送(使用分组这条大概率没啥卵用)
- receiver:默认接收者
- routes:子路由配置
- match_re:匹配出标签含有service=foo1或service=foo2或service=baz的告警
- receiver:指定接收器
- routes:二级子路由配置,字段相同
- group_by:分组,同一个分组下的告警将聚合为一条信息发送以避免频繁的消息提醒,如果不想使用分组,可以这样写
inhibit_rules:
告警抑制规则,主要用来减少关联报警消息,比如主机A挂了,则只发送A挂了的报警,运行在其上的程序及服务的报警不再发送- source_match:源告警,通常配置告警级别
- target_match:被抑制的告警,通常也是配置告警级别
- equal:必须在源告警和目标告警中具有相同的等值标签才会生效
以配置中的示例,其含义是,告警中
alertname、cluster、service
相同的告警,忽略登记为warning
级别的告警,只发送critical
级别的。
receivers:
不同的receivers配置方式略有不同,对于微信来说,这里配置的两个都可以,其中一个是通过Webhook发送到群机器人,里一个直接发送到一个或者多个联系人。对于钉钉来说,它只支持Webhook的方式发送。方式是一样的。其中对于wechat定义中的message即使用的模版。关于模版的定义在下边。template
模版语言这个东西,我也不太会,好像在用Python或者go语言用web框架开发的时候填充前段页面会用这玩意儿,简单解释下:
第一行,定义了一个模版,在alertmanager.yaml中关于wechat的receiver中可以看到对其的引用
第二行是一个循环,将Prometheus发来的消息循环出来
其他的部分就很好理解了,就是将循环出来的变量展示出来,不是变量的,就是纯文本。不过有几个变量我觉得有必要说一下怎么来的,知道之后能更好的自定义:
- .Status:这个是Prometheus发过来的状态信息,分别是firing和resolved,分别代表正在报警和报警已解除
- Labels:这个就是target文件和自动发现经过relabel后出现在Prometheus
Target labels
位置的信息,你定义了什么就可以在这里使用什么 - Annotations:还记得告警规则里关于Annotaions的定义吗,这就是从那儿来的,同样取决于你怎么定义
- 时间:故障和恢复时间是报警时Prometheus附加上去的,这里进行了一些转换,就是那个Format,因为它默认使用的不是北京时间
- end:表示模版结束,同一个文件中似乎可以定义多个这东西,只要在receiver那里正确引用就好了
{{ define "wechat.default.message" }}
{{ range .Alerts }}
========Start=========
告警环境: {{ .Labels.job }}
告警类别: {{ .Labels.app }}
告警状态:{{ .Status }}
告警名称: {{ .Labels.alertname }}
故障主机: {{ .Labels.instance }}
告警详情: {{ .Annotations.description }}
故障时间: {{ (.StartsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
恢复时间: {{ (.EndsAt.Add 28800e9).Format "2006-01-02 15:04:05" }}
=========End===========
{{ end }}
{{ end }}
发送脚本
发送脚本针对的是Webhook的告警方式,网上这种脚本还挺多的,你也可以自己写,原理就是启动一个web接口,接收来自Alertmanager发送来的报警数据,然后将数据格式化为钉钉或微信要求的数据格式之后发送即可。下面是脚本内容:
import os
import json
import requests
import arrow
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/', methods=['POST', 'GET'])
def send():
if request.method == 'POST':
post_data = request.get_data()
send_alert(bytes2json(post_data))
return 'success'
else:
return 'weclome to use prometheus alertmanager wechat webhook server!'
def bytes2json(data_bytes):
data = data_bytes.decode('utf8').replace("'", '"')
return json.loads(data)
def send_alert(data):
headers = {"Content-Type": "application/json; charset=utf-8"}
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
for output in data['alerts'][:]:
try:
title = output['labels']['alertname']
except KeyError:
title = 'Prometheus Alert'
try:
message = output['annotations']['message']
except KeyError:
try:
message = output['annotations']['description']
except KeyError:
message = 'null'
send_data = {
"msgtype": "markdown",
"markdown": {
"content": "# <font color=\"comment\">通知类型:</font> <font color=\"warning\">**%s** </font>" % output['status'] + "\n\n" +
"> **告警名称**: %s \n" % output['labels']['alertname'] +
# "**告警级别**: %s \n\n" % output['labels']['severity'] +
"> **告警状态**: %s \n" % output['status'] +
"> **告警类别**: %s \n" % output['labels']['app'] +
"> **告警主机**: %s \n" % output['labels']['instance'] +
"> **告警详情**: %s \n" % message +
"> **触发时间**: %s \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')
}
}
req = requests.post(url, json=send_data, headers = headers)
result = req.json()
if result['errcode'] != 0:
print('notify dingtalk error: %s' % result['errcode'])
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
脚本很简单,使用Flask启动了一个web程序,程序接收到来自Alertmanager的数据之后转换为Json,然后遍历该Json数据,将其构造为想要的格式,然后发送。需要注意下面的东西:
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!