prometheus 的幾種告警方式
prometheus 我們都知道它是最近幾年特別火的一個(gè)開(kāi)源的監(jiān)控工具良价,原生支持 kubernetes辣吃,如果你使用的是 kubernetes 集群,那么使用 prometheus 將會(huì)是非常方便的仰猖,而且 prometheus 也提供了報(bào)警工具alertmanager
捏肢,實(shí)際上在 prometheus 的架構(gòu)中,告警能力是單獨(dú)的一部分饥侵,主要是通過(guò)自定義一堆的rule
即告警規(guī)則鸵赫,來(lái)周期性的對(duì)告警規(guī)則進(jìn)行計(jì)算,并且會(huì)根據(jù)設(shè)置的報(bào)警觸發(fā)條件躏升,如果滿足辩棒,就會(huì)進(jìn)行告警,也就是會(huì)向alertmanager
發(fā)送告警信息膨疏,進(jìn)而由alertmanager
進(jìn)行告警一睁。
那么,alertmanager
告警又是通過(guò)何種途徑呢佃却?其實(shí)有很多種方式者吁,例如:
- 郵件告警
- 企業(yè)微信告警
- 釘釘告警
- slack 告警
- webhook 接口方式告警
其實(shí)還有一些,但這些都不重要饲帅,這些只是工具复凳,重要的是如何運(yùn)用,下面就介紹下使用 webhook 的方式來(lái)讓 alertmanager 調(diào)用接口洒闸,發(fā)送POST
請(qǐng)求完成告警消息的推送染坯,而這個(gè)推送可以是郵件,也可以是微信丘逸,釘釘?shù)取?/p>
調(diào)用接口以郵件形式告警
大體流程是這樣的单鹿,首先在我們定義好一堆告警規(guī)則之后,如果觸發(fā)條件深纲,alertmanager 會(huì)將報(bào)警信息推送給接口仲锄,然后我們的這個(gè)接口會(huì)做一些類似與聚合劲妙、匯總、優(yōu)化的一些操作儒喊,然后將處理過(guò)的報(bào)警信息再以郵件的形式發(fā)送給指定的人或者組镣奋。也就是下面這個(gè)圖:
我們這里的重點(diǎn)主要是如何寫(xiě)這個(gè) webhook,以及寫(xiě) webhook 的時(shí)候需要注意什么怀愧?下面將一一講解
假設(shè)你有一個(gè) prometheus 監(jiān)控系統(tǒng)侨颈,并且告警規(guī)則都已配置完成
配置 alertmanager
首先得先配置 alertmanager,讓其可以調(diào)用接口芯义,配置方式很簡(jiǎn)單哈垢,只需要指定一下接口地址即可,如下:
receivers:
- webhook_configs:
url: http://10.127.34.107:5000/webhook
send_resolved: true
這就完了扛拨!當(dāng)然可以指定多種告警方式
這樣配置完成后耘分,alertmanger 就會(huì)把告警信息以 POST 請(qǐng)求方式調(diào)用接口
編寫(xiě)一個(gè)最簡(jiǎn)單的接口
既然是用 python 來(lái)編寫(xiě)一個(gè)接口,那么肯定是用 flask 的绑警,代碼也非常簡(jiǎn)單求泰,如下:
import json
from flask import Flask, request
from gevent.pywsgi import WSGIServer
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
prometheus_data = json.loads(request.data)
print(prometheus_data)
return "test"
if __name__ == '__main__':
WSGIServer(('0.0.0.0', 5000), app).serve_forever()
上面導(dǎo)入的一些模塊,記得要去下載哦
pip install flask
pip install gevent
這樣的話计盒,我們直接運(yùn)行此段代碼渴频,此時(shí)機(jī)器上會(huì)監(jiān)聽(tīng) 5000 端口,如果此時(shí) prometheus 有告警北启,那么我們就會(huì)看到 prometheus 傳過(guò)來(lái)的數(shù)據(jù)格式是什么樣的了枉氮,這里我貼一個(gè)示例:
{
'receiver': 'webhook',
'status': 'firing',
'alerts': [{
'status': 'firing',
'labels': {
'alertname': '內(nèi)存使用率',
'instance': '10.127.92.100',
'job': 'sentry',
'severity': 'warning',
'team': 'ops'
},
'annotations': {
'description': '內(nèi)存使用率已超過(guò)55%,內(nèi)存使用率:58%',
'summary': '內(nèi)存使用率'
},
'startsAt': '2020-12-30T07:20:08.775177336Z',
'endsAt': '0001-01-01T00:00:00Z',
'generatorURL': 'http://prometheus-server:9090/graph?g0.expr=round%28%281+-+%28node_memory_MemAvailable_bytes%7Bjob%3D%22sentry%22%7D+%2F+%28node_memory_MemTotal_bytes%7Bjob%3D%22sentry%22%7D%29%29%29+%2A+100%29+%3E+55&g0.tab=1',
'fingerprint': '09f94bd1aa7da54f'
}, {
'status': 'firing',
'labels': {
'alertname': '內(nèi)存使用率',
'instance': '10.127.92.101',
'job': 'sentry',
'severity': 'warning',
'team': 'ops'
},
'annotations': {
'description': '內(nèi)存使用率已超過(guò)55%暖庄,內(nèi)存使用率:58%',
'summary': '內(nèi)存使用率'
},
'startsAt': '2020-12-30T07:20:08.775177336Z',
'endsAt': '0001-01-01T00:00:00Z',
'generatorURL': 'http://prometheus-server:9090/graph?g0.expr=round%28%281+-+%28node_memory_MemAvailable_bytes%7Bjob%3D%22sentry%22%7D+%2F+%28node_memory_MemTotal_bytes%7Bjob%3D%22sentry%22%7D%29%29%29+%2A+100%29+%3E+55&g0.tab=1',
'fingerprint': '8a972e4907cf2c60'
}],
'groupLabels': {
'alertname': '內(nèi)存使用率'
},
'commonLabels': {
'alertname': '內(nèi)存使用率',
'job': 'sentry',
'severity': 'warning',
'team': 'ops'
},
'commonAnnotations': {
'summary': '內(nèi)存使用率'
},
'externalURL': 'http://alertmanager-server:9093',
'version': '4',
'groupKey': '{}:{alertname="內(nèi)存使用率"}',
'truncatedAlerts': 0
}
通過(guò) prometheus 傳過(guò)來(lái)的告警信息,可以看到是一個(gè)標(biāo)準(zhǔn)的json
楼肪,我們?cè)谑褂?code>python在做處理時(shí)培廓,需要先將json
字符串轉(zhuǎn)換成python
的字典,可以用json
這個(gè)模塊來(lái)實(shí)現(xiàn)春叫,通過(guò)這個(gè)json
我們可以得到以下信息(非常重要):
- 每次發(fā)出的
json
數(shù)據(jù)流中的報(bào)警信息是同一個(gè)類型的報(bào)警肩钠,比如這里都是關(guān)于內(nèi)存的 -
status
:表示告警的狀態(tài),兩種:firing
和resolved
-
alerts
:是一個(gè)列表暂殖,里面的元素是由字典組成价匠,每一個(gè)元素都是一條具體的告警信息 -
commonLabels
:這里面就是一些公共的信息
剩下的幾個(gè) key 都比較好理解,就不一一說(shuō)了呛每,下面結(jié)合 prometheus 的一些 rule 來(lái)看下這個(gè)告警是憑什么這樣發(fā)的踩窖。
# cat system-rule.yaml #文件名隨意設(shè)置,因?yàn)閜rometheus的配置里配置的是: *.yaml
groups:
- name: sentry
rules:
- alert: "Memory Usage"
expr: round((1-(node_memory_MemAvailable_bytes{job='sentry'} / (node_memory_MemTotal_bytes{job='sentry'})))* 100) > 85
for: 5m
labels:
team: ops
severity: warning
cloud: yizhuang
annotations:
summary: "Memory usage is too high and over 85% for 5min"
description: "The current host {{$labels.instance}}' memory usage is {{ $value }}%"
這里就是配置的告警規(guī)則晨横,告訴 prometheus 應(yīng)該按照什么方式進(jìn)行告警洋腮,配置完成后箫柳,要在 prometheus 的配置里引用下,如下所示:
# cat prometheus.yml
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: ['10.10.10.111:9093']
# 就是這里啥供,看這里
rule_files:
- "/alertmanager/rule/*.yaml" #文件目錄隨意設(shè)置
...
...
...
此處省略一堆配置
到這里應(yīng)該就知道告警規(guī)則是什么發(fā)出來(lái)的了吧悯恍,然后也應(yīng)該知道告警內(nèi)容為什么是這樣的了吧,嗯伙狐,下面看下最關(guān)鍵的地方
處理原始告警信息并進(jìn)行郵件告警
原始的告警信息看起來(lái)還挺規(guī)則的涮毫,只需要拼接下就可以了,但是有一個(gè)問(wèn)題就是alerts
里面的startsAt
和endsAt
這倆時(shí)間格式有些問(wèn)題贷屎,是 UTC 時(shí)區(qū)的時(shí)間罢防,需要轉(zhuǎn)換下。還有一個(gè)地方需要注意的豫尽,最外層的status
如果是firing
狀態(tài)篙梢,就不代表alerts
中的status
就一定都是firing
,還有可能是resolved
,如下json
所示:
{
'receiver': 'webhook',
'status': 'firing',
'alerts': [{
'status': 'resolved', # 這里就是resolved狀態(tài)美旧,所以處理時(shí)需要注意下
'labels': {
'alertname': 'CPU使用率',
'instance': '10.127.91.26',
'severity': 'warning',
'team': 'ops'
},
'annotations': {
'description': 'CPU使用率已超過(guò)35%渤滞,CPU使用率:38%',
'summary': 'CPU使用率'
},
'startsAt': '2020-12-30T07:38:38.775177336Z',
'endsAt': '2020-12-30T07:38:53.775177336Z',
'generatorURL': 'http://prometheus-server:9090/graph?g0.expr=round%28100+-+%28avg+by%28instance%29+%28irate%28node_cpu_seconds_total%7Bjob%3D%22sentry%22%2Cmode%3D%22idle%22%7D%5B5m%5D%29%29+%2A+100%29%29+%3E+35&g0.tab=1',
'fingerprint': '58393b2abd2c6987'
}, {
'status': 'resolved',
'labels': {
'alertname': 'CPU使用率',
'instance': '10.127.92.101',
'severity': 'warning',
'team': 'ops'
},
'annotations': {
'description': 'CPU使用率已超過(guò)35%,CPU使用率:38%',
'summary': 'CPU使用率'
},
'startsAt': '2020-12-30T07:42:08.775177336Z',
'endsAt': '2020-12-30T07:42:38.775177336Z',
'generatorURL': 'http://prometheus-server:9090/graph?g0.expr=round%28100+-+%28avg+by%28instance%29+%28irate%28node_cpu_seconds_total%7Bjob%3D%22sentry%22%2Cmode%3D%22idle%22%7D%5B5m%5D%29%29+%2A+100%29%29+%3E+35&g0.tab=1',
'fingerprint': 'eaca600142f9716c'
}],
'groupLabels': {
'alertname': 'CPU使用率'
},
'commonLabels': {
'alertname': 'CPU使用率',
'severity': 'warning',
'team': 'ops'
},
'commonAnnotations': {
'summary': 'CPU使用率'
},
'externalURL': 'http://alertmanager-server:9093',
'version': '4',
'groupKey': '{}:{alertname="CPU使用率"}',
'truncatedAlerts': 0
}
那既然該注意的都注意了榴嗅,就開(kāi)始干吧妄呕,首先說(shuō)下我要實(shí)現(xiàn)的一個(gè)最終結(jié)果:
- 時(shí)區(qū)轉(zhuǎn)換
- 不同類型的告警信息推送給不同的人
- 告警內(nèi)容以表格的形式展示,通過(guò) html 實(shí)現(xiàn)
時(shí)區(qū)轉(zhuǎn)換
先看下時(shí)區(qū)轉(zhuǎn)換嗽测,這個(gè)比較好解決绪励,代碼如下:
import datetime
from dateutil import parser
def time_zone_conversion(utctime):
format_time = parser.parse(utctime).strftime('%Y-%m-%dT%H:%M:%SZ')
time_format = datetime.datetime.strptime(format_time, "%Y-%m-%dT%H:%M:%SZ")
return str(time_format + datetime.timedelta(hours=8))
發(fā)送郵件
再來(lái)看下郵件發(fā)送,也很簡(jiǎn)單唠粥,代碼如下:
import smtplib
from email.mime.text import MIMEText
def sendEmail(title, content, receivers=None):
if receivers is None:
receivers = ['chenf-o@glodon.com']
mail_host = "xxx"
mail_user = "xxx"
mail_pass = "xxx"
sender = "xxx"
msg = MIMEText(content, 'html', 'utf-8')
msg['From'] = "{}".format(sender)
msg['To'] = ",".join(receivers)
msg['Subject'] = title
try:
smtpObj = smtplib.SMTP_SSL(mail_host, 465)
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, msg.as_string())
print('mail send successful.')
except smtplib.SMTPException as e:
print(e)
告警模板生成
下面就是告警推送的形式了疏魏,上面說(shuō)了,使用表格的形式晤愧,如果用 html 來(lái)生成表格大莫,還是比較簡(jiǎn)單的,但是這個(gè)表格是不停的變化的官份,所以為了支持這個(gè)動(dòng)態(tài)變化只厘,肯定是得用到模板語(yǔ)言:jinja
了,如果是搞運(yùn)維的肯定知道ansible
舅巷,ansible 里的 template 用的也是jinja
模板語(yǔ)言羔味,所以比較好理解,這里就不再單獨(dú)說(shuō)了钠右,后面會(huì)詳細(xì)說(shuō)一下 python 中如何使用這個(gè)jinja
模板語(yǔ)言赋元,不明白的可以先看下官方文檔,比較簡(jiǎn)單: http://docs.jinkan.org/docs/jinja2/
那么我這個(gè) html 就長(zhǎng)成了這個(gè)樣子,由于本人對(duì)前端一點(diǎn)都不懂们陆,所以能實(shí)現(xiàn)我的需求就行了寒瓦。
<meta http-equiv="Content-Type"content="text/html;charset=utf-8">
<html align='left'>
<body>
<h2 style="font-size: x-large;">{{ prometheus_monitor_info['commonLabels']['cloud'] }}--監(jiān)控告警通知</h2><br/>
<br>
<table border="1" width = "70%" cellspacing='0' cellpadding='0' align='left'>
<tr>
<!--監(jiān)控類型:系統(tǒng)層級(jí),業(yè)務(wù)層級(jí)坪仇,服務(wù)層級(jí)等等-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">監(jiān)控類別</th>
<!--狀態(tài):報(bào)警通知還是恢復(fù)通知-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">狀態(tài)</th>
<!--狀態(tài):級(jí)別:報(bào)警級(jí)別-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">級(jí)別</th>
<!--狀態(tài):實(shí)例:機(jī)器地址-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">實(shí)例</th>
<!--狀態(tài):描述:報(bào)警描述-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">描述</th>
<!--狀態(tài):詳細(xì)描述:報(bào)警詳細(xì)描述-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">詳細(xì)描述</th>
<!--狀態(tài):開(kāi)始時(shí)間:報(bào)警開(kāi)始時(shí)間-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">開(kāi)始時(shí)間</th>
<!--狀態(tài):開(kāi)始時(shí)間:報(bào)警結(jié)束時(shí)間-->
<th style="font-size: 20px; padding: 5px; background-color: #F3AE60">結(jié)束時(shí)間</th>
</tr>
{% for items in prometheus_monitor_info['alerts'] %}
<tr align='center'>
{% if loop.first %}
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #F3AE60" rowspan="{{ loop.length }}">{{ prometheus_monitor_info['commonLabels']['alertname'] }}</td>
{% endif %}
{% if items['status'] == 'firing' %}
<td style="font-size: 16px; padding: 3px; background-color: red; word-wrap: break-word">告警</td>
{% else %}
<td style="font-size: 16px; padding: 3px; background-color: green; word-wrap: break-word">恢復(fù)</td>
{% endif %}
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #EBE4D3">{{ items['labels']['severity'] }}</td>
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #EBE4D3">{{ items['labels']['instance'] }}</td>
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #EBE4D3">{{ items['annotations']['summary'] }}</td>
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #EBE4D3">{{ items['annotations']['description'] }}</td>
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #EBE4D3">{{ items['startsAt'] }}</td>
{% if items['endsAt'] == '0001-01-01T00:00:00Z' %}
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #EBE4D3">00:00:00:00</td>
{% else %}
<td style="font-size: 16px; padding: 3px; word-wrap: break-word; background-color: #3DE869">{{ items['endsAt'] }}</td>
{% endif %}
</tr>
{% endfor %}
</table>
</body>
</html>
en杂腰。。椅文。喂很。仔細(xì)一看好像也挺簡(jiǎn)單的,就是一堆 for 循環(huán)皆刺,if 判斷啥的少辣,比較不好弄的就是這個(gè)表格的合并單元格,對(duì)我來(lái)說(shuō)有點(diǎn)費(fèi)勁羡蛾,我就簡(jiǎn)單把監(jiān)控類別給合并成一個(gè)單元格了漓帅,其他的就沒(méi)再歸類了
<tr>...</tr>
這里設(shè)置的是表格的表頭信息,我這里都有詳細(xì)的注釋痴怨,就不介紹了忙干。
<td>...</td>
里是一行一行的告警信息,里面有一個(gè)判斷浪藻,是判斷這一條告警信息里到底是報(bào)警還是已恢復(fù)捐迫,然后根據(jù)不同來(lái)設(shè)置一個(gè)不同的顏色展示,這樣的話領(lǐng)導(dǎo)看了肯定會(huì)覺(jué)著真貼心爱葵。
然后我就說(shuō)一個(gè)比較重要的地方
{% for items in prometheus_monitor_info['alerts'] %}
這里面是最關(guān)鍵的告警信息施戴,其中prometheus_monitor_info這個(gè)是一個(gè)變量吧,代表的是把prometheus推過(guò)來(lái)的json字符串轉(zhuǎn)換成python的一個(gè)字典萌丈,注意這是一個(gè)字典赞哗,然后這個(gè)字典做了一個(gè)時(shí)區(qū)轉(zhuǎn)換的操作。
嗯辆雾,那prometheus_monitor_info['alerts']這里就是取得alerts這個(gè)列表了懈玻,然后用for循環(huán)迭代這個(gè)列表,items這里就是每一條具體的告警信息乾颁,它是一個(gè)字典,嗯艺栈,然后就是把字典里的value取出來(lái)了英岭,嗯。仔細(xì)想想也很簡(jiǎn)單湿右。
{% endfor %}
這樣的話诅妹,我這個(gè) html 的模板就寫(xiě)好了,然后我怎么使用這個(gè)模板呢?這里我又寫(xiě)了一個(gè)方法來(lái)解析這個(gè)模板吭狡,并傳入對(duì)應(yīng)的參數(shù)
from jinja2 import Environment, FileSystemLoader
class ParseingTemplate:
def __init__(self, templatefile):
self.templatefile = templatefile
def template(self, **kwargs):
try:
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template(self.templatefile)
template_content = template.render(kwargs)
return template_content
except Exception as error:
raise error
簡(jiǎn)單說(shuō)下這個(gè)類的作用尖殃,就是為了傳入告警信息,然后再讀取 html 模板划煮,最后把解析好的 html 內(nèi)容返回出來(lái)送丰,最后通過(guò)郵件,把這個(gè)內(nèi)容發(fā)出去弛秋,就完事了器躏。
精準(zhǔn)告警,對(duì)應(yīng)到具體的人
這里其實(shí)比較簡(jiǎn)單蟹略,只需要解析原始 json 里的commonLabels
下的team
登失,如果你仔細(xì)看我上面貼的那個(gè) rule 報(bào)警規(guī)則的話,你肯定注意到里面有一個(gè)自定義的 key-value:
groups:
- name: sentry # 這個(gè)名字可以理解為一個(gè)分類挖炬,做一個(gè)區(qū)分
rules:
- alert: "Memory Usage"
expr: round((1-(node_memory_MemAvailable_bytes{job='sentry'} / (node_memory_MemTotal_bytes{job='sentry'})))* 100) > 85
for: 5m
labels:
team: ops # 就是這里揽浙,我定義了一個(gè)組,用來(lái)給這個(gè)組發(fā)消息
severity: warning
cloud: yizhuang
......
......
然后我再解析原始 json 的時(shí)候意敛,我把這個(gè)team
的值獲取出來(lái)馅巷,根據(jù)這個(gè)值,去取這個(gè)組里的具體郵件地址空闲,最后發(fā)給這些人就好了令杈。
具體的郵件地址,我是取出來(lái)了碴倾,但是我怎么知道區(qū)分這些人應(yīng)該對(duì)應(yīng)哪個(gè)環(huán)境或者哪個(gè)應(yīng)用呢逗噩,那就是下面這個(gè):
groups:
- name: sentry
......
......
這里的 name 肯定和 prometheus 中指定的 job_name 對(duì)應(yīng),那么 prometheus 中相應(yīng)的配置就是:
# cat prometheus.yml
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: ['10.127.92.105:9093']
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "/alertmanager/rule/*.yaml"
# 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: ['10.127.92.105:9090']
- job_name: 'cadvisor-app'
file_sd_configs:
- refresh_interval: 1m
files:
- /etc/prometheus/file-sd-configs/cadvisor-metrics.json
- job_name: 'sentry'
file_sd_configs:
- refresh_interval: 1m
files:
- /etc/prometheus/file-sd-configs/system-metrics.json
- job_name: 'kafka-monitor'
file_sd_configs:
- refresh_interval: 1m
files:
- /etc/prometheus/file-sd-configs/kafka-metrics.json
是不是串起來(lái)了呢跌榔?可以回想下异雁,然后再參考我最終完整的代碼
完整代碼參考
代碼參考
from flask import Flask, request
from dateutil import parser
import json
import yaml
import datetime
import smtplib
from email.mime.text import MIMEText
from jinja2 import Environment, FileSystemLoader
from gevent.pywsgi import WSGIServer
def time_zone_conversion(utctime):
format_time = parser.parse(utctime).strftime('%Y-%m-%dT%H:%M:%SZ')
time_format = datetime.datetime.strptime(format_time, "%Y-%m-%dT%H:%M:%SZ")
return str(time_format + datetime.timedelta(hours=8))
def get_email_conf(file, email_name=None, action=0):
"""
:param file: yaml格式的文件類型
:param email_name: 發(fā)送的郵件列表名
:param action: 操作類型,0: 查詢收件人的郵件地址列表, 1: 查詢收件人的列表名稱, 2: 獲取郵件賬號(hào)信息
:return: 根據(jù)action的值僧须,返回不通的數(shù)據(jù)結(jié)構(gòu)
"""
try:
with open(file, 'r', encoding='utf-8') as fr:
read_conf = yaml.safe_load(fr)
if action == 0:
for email in read_conf['email']:
if email['name'] == email_name:
return email['receive_addr']
else:
print("%s does not match for %s" % (email_name, file))
else:
print("No recipient address configured")
elif action == 1:
return [items['name'] for items in read_conf['email']]
elif action == 2:
return read_conf['send']
except KeyError:
print("%s not exist" % email_name)
exit(-1)
except FileNotFoundError:
print("%s file not found" % file)
exit(-2)
except Exception as e:
raise e
def sendEmail(title, content, receivers=None):
if receivers is None:
receivers = ['chenf-o@glodon.com']
send_dict = get_email_conf('email.yaml', action=2)
mail_host = send_dict['smtp_host']
mail_user = send_dict['send_user']
mail_pass = send_dict['send_pass']
sender = send_dict['send_addr']
msg = MIMEText(content, 'html', 'utf-8')
msg['From'] = "{}".format(sender)
msg['To'] = ",".join(receivers)
msg['Subject'] = title
try:
smtpObj = smtplib.SMTP_SSL(mail_host, 465)
smtpObj.login(mail_user, mail_pass)
smtpObj.sendmail(sender, receivers, msg.as_string())
print('mail send successful.')
except smtplib.SMTPException as e:
print(e)
class ParseingTemplate:
def __init__(self, templatefile):
self.templatefile = templatefile
def template(self, **kwargs):
try:
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template(self.templatefile)
template_content = template.render(kwargs)
return template_content
except Exception as error:
raise error
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
try:
prometheus_data = json.loads(request.data)
# 時(shí)間轉(zhuǎn)換纲刀,轉(zhuǎn)換成東八區(qū)時(shí)間
for k, v in prometheus_data.items():
if k == 'alerts':
for items in v:
if items['status'] == 'firing':
items['startsAt'] = time_zone_conversion(items['startsAt'])
else:
items['startsAt'] = time_zone_conversion(items['startsAt'])
items['endsAt'] = time_zone_conversion(items['endsAt'])
team_name = prometheus_data["commonLabels"]["team"]
generate_html_template_subj = ParseingTemplate('email_template_firing.html')
html_template_content = generate_html_template_subj.template(
prometheus_monitor_info=prometheus_data
)
# 獲取收件人郵件列表
email_list = get_email_conf('email.yaml', email_name=team_name, action=0)
sendEmail(
'Prometheus Monitor',
html_template_content,
receivers=email_list
)
return "prometheus monitor"
except Exception as e:
raise e
if __name__ == '__main__':
WSGIServer(('0.0.0.0', 5000), app).serve_forever()
配置文件參考
send:
smtp_host: smtp.163.com
send_user: warxxxxgs@163.com
send_addr: warxxxs@163.com
send_pass: BRxxxxxxxZPUZEK
email:
- name: kafka-monitor # 要和team對(duì)應(yīng)
receive_addr:
- 郵件地址1
- 郵件地址2
- 郵件地址3
- name: ops
receive_addr:
- 郵件地址1
- 郵件地址2
最終效果圖
1)全是告警的
2)既有告警又有恢復(fù)的
3)都是恢復(fù)的