1. 前言
Zabbix5.0在监控方面还是做了不小改进的,现在可以允许在Web UI 的报警媒介类型里面编写JavaScript脚本来完成指定的动作,就不用写一个脚本扔在服务器上面执行了。
用JavaScript就可以实现消息整理成Markdown、将消息post到钉钉机器人的接口。
事不宜迟,我们马上开始吧。
2. 域名规划
域名 作用
zabbix.example.com Zabbix Web UI, 用于配置/展示告警信息
img-proxy.example.com 告警图片展示地址, 后面的章节会有详细说明
3. 准备钉钉机器人
选择自定义 通过WebHook接入自定义服务 类型的机器人
安全设置,我比较懒,就随便选择了IP地址(段)
最后把webhook地址拷出来就好
4. 配置报警媒介
新建报警媒介类型 [钉钉机器人-SRE-肥水]
类型: WebHook
添加参数:
access_token: 填写刚才生成的钉钉机器人Token
message: {ALERT.MESSAGE}
subject: {ALERT.SUBJECT}
脚本: 填入如下JavaScript脚本
try {
Zabbix.Log(4, 'params= '+value);
params = JSON.parse(value);
req = new CurlHttpRequest();
timest = Date.now()
data = {};
result = {};
req.AddHeader('Content-Type: application/json');
data.msgtype = "markdown";
// 对应 message参数
data.markdown = {"title" : params.subject, "text" : params.message.replace(/\\n/g, "\n").replace(/awesometimestamp/g, timest)};
Zabbix.Log(4, 'markdown= '+JSON.stringify(data.markdown));
// 对应 user参数
data.at = {"atMobiles": [], "isAtAll": "false"};
// 钉钉机器人
resp = req.Post('https://oapi.dingtalk.com/robot/send?access_token=' + params.access_token,
JSON.stringify(data)
);
} catch (error) {
result = {};
}
`return JSON.stringify(result);
`
5. 配置告警模板
告警媒介-告警模板 Message templates(消息模板) 增加两个消息的模板
问题
##### {HOST.NAME} \n
#### <font face='微软雅黑' color=#FF0000>故障</font> \n
> 故障项目:[{EVENT.NAME}](https://zabbix.example.com/history.php?action=showgraph&itemids[]={ITEM.ID}) {ITEM.VALUE} \n
> 故障等级:{EVENT.SEVERITY} \n
> 主机地址:{HOST.IP} \n
> 告警时间:{EVENT.DATE} {EVENT.TIME}
![screenshot](https://img-proxy.example.com/zbximg/{ITEM.ID}-awesometimestamp)
Problem recovery
##### {HOST.NAME} \n
#### <font face='微软雅黑' color=#00FF00>恢复</font>
> 恢复项目:[{EVENT.NAME}](https://zabbix.example.com/history.php?action=showgraph&itemids[]={ITEM.ID}) {ITEM.VALUE} \n
> 故障等级:{EVENT.SEVERITY} \n
> 主机地址:{HOST.IP} \n
> 恢复时间:{EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME}
![screenshot](https://img-proxy.example.com/zbximg/{ITEM.ID}-awesometimestamp)
这里的两个域名请看第2章域名规划
这里的两个域名的作用 请看 第2章域名规划
6. 配置告警专用用户, 关联告警媒介
别名: zabbix_tool
群组: Zabbix administrators
我懒得调整权限,所以配置了超管,处于安全考虑建议配置最小权限
然后是设置报警媒介,关联刚才配置的报警媒介 [钉钉机器人-SRE-肥水]
至此,Zabbix Web部分就已经配置成功了!
- 编写图片代理API (无需登录即可展示告警图片)
我最终的目标是要在钉钉告警信息上,显示告警相关的图片
因为Zabbix的监控图片需要登录之后才能查看到,而钉钉机器人又不支持登录功能,所以才需要图片代理。
为什么我要做一个图片代理,而不是直接将图片上传到CDN呢?主要有以下个原因:
我不想额外增加一笔CDN费用的开销;
机房带宽充裕,直接用一个普通的Nginx展示静态图片即可满足;
Zabbix5 可以在Web UI上面维护JavaScript脚本,基本就可以实现我的需求,我只需要解决Zabbix登录的问题就可以了,完全可以用一个代理API来实现;
设计规划如下图
配置文件 nginx_vhost.conf
# 限流、防刷配置,各位可以根据需要选择
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=6r/s;
server {
同时支持http/https
==============
listen 80;
listen 443 ssl http2;
server_name img-proxy.example.com;
access_log /var/log/nginx/access_img-proxy.example.com.log;
ssl相关参数
=======
ssl_certificate ssl_ca/ca.pem;
ssl_certificate_key ssl_ca/ca.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_dhparam ssl_ca/dhparam;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
限流、防刷配置,各位可以根据需要选择
==================
limit_req zone=mylimit burst=20 nodelay;
limit_req_status 499;
使用tryfile功能尝试找本地图片, 找不到则扔给下面的proxy_pass
=======================================
location /zbximg {
try_files $uri @zabbix_img_proxy;
default_type image/png; # 注意设置格式,否则客户端识别不出来这是图片
alias /data/zabbix_img_proxy/zbximg/;
}
proxy_pass 到图片缓存API
===================
location @zabbix_img_proxy {
# 长连接
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://127.0.0.1:8888;
}
安全设置, 其余不访问zbximg的都404掉,
========================
`location / {
return 404;
}
}
`
图片缓存API app.py
注意! 这个API要与nginx放在同一台机器
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# --------------------
# File: app.py
# Project: zabbix_img_proxy
# Purpose: zabbix 图片代理,实现无需登录的图片返回
# Author: Jan Lam (honos628@foxmail.com)
# Last Modified: 2020-07-27 17:46:17
# --------------------
import uvicorn
import settings as st
from datetime import datetime, timedelta
from fastapi import FastAPI
from io import BytesIO
from pathlib import Path
from requests import Session
from starlette.responses import StreamingResponse
from settings import img_path
app = FastAPI()
def make_connect():
'''
登录Zabbix 建立Session连接
'''
login_url = f'{st.zabbix_server}/index.php'
conn = Session()
payload = {
'name': st.zabbix_user,
'password': st.zabbix_passwd,
'autologin': 1,
'enter': 'Sign in'
}
conn.post(url=login_url, data=payload)
return conn
async def save_image(content: bytes, file_path: str):
'''
保存图片
'''
with open(file_path, 'wb') as f:
f.write(content)
async def load_image(file_path: str):
'''
读取本地图片
'''
with open(file_path, 'rb') as f:
img_byte = f.read()
return img_byte
@app.get("/zbximg/{itemid}-{timestamp}", response_class=StreamingResponse)
async def read_zbximg(itemid: int = 0, timestamp: int = 0):
'''
页面视图, 返回StreamingResponse 流式数据
url example: /zbximg/{ITEM.ID}-{时间戳}
ITEM.ID: 监控项目的ID,图片唯一标识
时间戳: 加上这一个时间戳主要是为了防止重复
图片缓存文件名: {ITEM.ID}-{时间戳}
时间范围: 默认是 \[时间戳 30分钟前\] 到 \[时间戳\]
'''
file_path = img_path / f'{itemid}-{timestamp}'
now = datetime.utcfromtimestamp(int(timestamp / 1000))
now_30m_ago = now - timedelta(minutes=30)
try:
img_byte = await load_image(file_path=file_path)
return StreamingResponse(BytesIO(img_byte), media_type="image/png")
except Exception:
url = f'{st.zabbix_server}/chart.php'
params = {
"from": now_30m_ago.strftime(r'%Y-%m-%d %H:%M:%S'), # 时间范围
"to": now.strftime(r'%Y-%m-%d %H:%M:%S'),
"itemids[0]": itemid,
"width": "400",
"height": "150",
"profileIdx": "web.item.graph.filter",
}
response = conn.get(url, params=params, timeout=5)
await save_image(content=response.content, file_path=file_path)
return StreamingResponse(BytesIO(response.content), media_type="image/png")
conn = make_connect() # 程序启动的时候就建立登录,减少登录次数。
img_path = Path(st.img_path)
img_path.mkdir(parents=True, exist_ok=True)
`if `name` == "`main`":
uvicorn.run(app, host="0.0.0.0", port=8888)
`
图片缓存API配置文件 config.py
注意: 与app.py放在同一级目录
zabbix_server = 'https://zabbix.example.com' # Zabbix Web UI 的地址
zabbix_user = 'your_username'
zabbix_passwd = 'your_password'
img_path = './zbximg/' # 缓存图片的路径
图片缓存API 的安装方式 pipenv
这里使用了pipenv来安装,我就简单贴一下Pipfile吧
# 把这个文件放在 app.py 同一级目录
`[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
ipython = "`"
fastapi = "`"
uvicorn = "`"
requests = "`"
fake-useragent = "`"
gunicorn = "`"
uvloop = "*"
python_version = "3.8"`
执行安装命令
# 执行同步命令
pipenv sync
`提示成功就可以了。`
守护进程 supervisord 配置文件
我使用linux来跑服务的,那最适合的守护进程就是supervisord了
安装步骤就不写了,简单贴一下配置文件
# 配置文件路径 /etc/supervisord.d/zabbix_img_proxy.ini
`[program:zabbix_img_proxy]
directory=/data/zabbix_img_proxy # app.py所在的目录
autostart=true
autorestart=true
user=root # 你在哪个用户执行pipenv sync的,就写哪个用户,生产不建议用root
#command=/usr/local/python3.8.3/bin/pipenv run python app.py # Debug
command=/usr/local/python3.8.3/bin/pipenv run gunicorn app:app -w 2 -b 127.0.0.1:8888 -k uvicorn.workers.UvicornWorker # 使用gunicorn + uvicorn的方式运行 效率会比较高`
至此 图片代理API就完工了!
- 验证钉钉消息
随便关一下节点的zabbix-agent 或者直接在 Zabbix Web UI 调试就可以了
反正你有很多方式触发告警的,我这里就不多写了
最后贴个告警的图片
故障发生时的钉钉
故障恢复后的钉钉告警