这是一篇更新于 543 天前的文章,其中的信息可能已经有所发展或是发生改变。

今年年中,我开始把我自用的一个企业从企业微信迁移到飞书,历时两个半月,终于彻底迁移过来了,老企业微信也就注销掉了。ZABBIX 对接飞书也是迁移的最后一步。

其实关于 ZABBIX 对接国内各种企业 IM 已经是老生常谈的问题了。不过我在对接之前还是习惯性的搜了一下,发现使用 ZABBIX Javascript Runtime 实现的少之又少,所以我还是打算记录一下。

效果放前面

你可以通过飞书的群机器人,接收 ZABBIX 的告警信息,例如下面的是故障消息:
20231203163916
下面这个是故障恢复消息:
20231203163943

实现思路

使用 ZABBIX 6 以上版本提供的 Javascript Runtime,免去了在 ZABBIX 服务器底层放 Python 脚本的步骤,后续维护报警媒介,都可以通过 ZABBIX WEB 来实现。

扩展阅读:其它 Javascript 对象

技术实现

创建飞书机器人

我们需要先在飞书群里创建一个机器人。获取它的 Webhook 地址。创建飞书群机器人,请参考飞书帮助中心 - 如何在群组中使用机器人?,请按照文档操作,创建一个“自定义机器人”。

20231203175815

安全配置,按需启用就好了,或者干脆不启用。其中上图框选的部分,就是后文需要用到的 BOTKEY。

创建飞书消息卡片模板

创建一个飞书消息卡片模板,消息卡片模板可以很方便的帮助我们调整推送消息的样式,且消息卡片模板可以导出导入,方便复用。下面我提供了两个告警和告警恢复的消息卡片模板。

以下内容托管在 GitHub,如果看不到,请确认您是否有访问权限。

{"templateId":"ctp_AAVPnuaz6F8I","content":{"elements":[{"tag":"column_set","flex_mode":"stretch","background_style":"default","columns":[{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"**🟢 所属设备:**\n[${host_ip}] ${host_name} "}]},{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"**⚠️ 严重性等级:**\n${event_severity}"}]}]},{"tag":"column_set","flex_mode":"none","background_style":"default","columns":[{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"**🕐 发生时间:**\n${event_date} ${event_time}"}]}]},{"tag":"markdown","content":"**🔦 持续时间:**\n${event_duration}"},{"tag":"hr"},{"tag":"div","text":{"content":"** 👀 问题详情 **","tag":"lark_md"}},{"tag":"column_set","flex_mode":"none","background_style":"default","columns":[{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"${problem_item_name} : ${problem_item_value}"}]},{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[]}]},{"tag":"column_set","flex_mode":"trisect","background_style":"default","columns":[]},{"tag":"column_set","flex_mode":"bisect","background_style":"default","columns":[]},{"tag":"hr"},{"tag":"div","text":{"content":"告警来源:${from}","tag":"plain_text"}},{"actions":[{"tag":"button","text":{"content":"转运维中心","tag":"plain_text"},"type":"danger","multi_url":{"url":"${message_url}","pc_url":"","android_url":"","ios_url":""}}],"tag":"action"}],"header":{"template":"green","title":{"content":"【${event_id}】故障恢复:${event_name}","tag":"plain_text"}}},"mock_data":"{\"event_name\":\"存储设备失效\",\"event_id\":\"114514\",\"event_date\":\"2023-12-03\",\"event_time\":\"10:36:00\",\"host_name\":\"Huawei\",\"event_severity\":\"严重\",\"message_url\":\"\",\"trigger_name\":\"\",\"problem_item_name\":\"Disk0\",\"problem_item_value\":\"处于临界状态0/0\",\"host_ip\":\"10.10.10.10\",\"from\":\"\",\"event_duration\":\"30分钟\"}","variables":[{"variable_id":"7307557781883142145","type":"Text","name":"event_date","description":"事件日期","config":"{}","create_time":1701583781,"update_time":1701583781},{"type":"Text","name":"event_id","description":"事件ID","config":"{}","create_time":1701583781,"update_time":1701583781,"variable_id":"7307557781883125761"},{"config":"{}","create_time":1701583781,"update_time":1701583781,"variable_id":"7307557781883109377","type":"Text","name":"event_name","description":"事件名称"},{"update_time":1701583781,"variable_id":"7307557781883191297","type":"Text","name":"event_severity","description":"严重性等级","config":"{}","create_time":1701583781},{"update_time":1701583781,"variable_id":"7307557781883158529","type":"Text","name":"event_time","description":"事件时间","config":"{}","create_time":1701583781},{"variable_id":"7307557781883289601","type":"Text","name":"from","description":"告警来源","config":"{}","create_time":1701583781,"update_time":1701583781},{"description":"设备IP","config":"{}","create_time":1701583781,"update_time":1701583781,"variable_id":"7307557781883273217","type":"Text","name":"host_ip"},{"config":"{}","create_time":1701583781,"update_time":1701583781,"variable_id":"7307557781883174913","type":"Text","name":"host_name","description":"设备名称"},{"name":"message_url","description":"消息链接","config":"{\"is_multi_url\":false}","create_time":1701583781,"update_time":1701583781,"variable_id":"7307557781883207681","type":"Link"},{"variable_id":"7307557781883240449","type":"Text","name":"problem_item_name","description":"问题详情名称","config":"{}","create_time":1701583781,"update_time":1701583781},{"variable_id":"7307557781883256833","type":"Text","name":"problem_item_value","description":"问题详情值","config":"{}","create_time":1701583781,"update_time":1701583781},{"variable_id":"7307557781883224065","type":"Text","name":"trigger_name","description":"触发器","config":"{}","create_time":1701583781,"update_time":1701583781},{"type":"Text","name":"event_duration","description":"事件持续时间","variable_id":0,"config":"{}"}]}
{"templateId":"ctp_AAVTQ2mY3Fsz","content":{"elements":[{"tag":"column_set","flex_mode":"stretch","background_style":"default","columns":[{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"**🔴 所属设备:**\n[${host_ip}] ${host_name} "}]},{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"**⚠️ 严重性等级:**\n${event_severity}"}]}]},{"tag":"hr"},{"tag":"markdown","content":"**🕐 发生时间:**\n${event_date} ${event_time}"},{"tag":"div","text":{"content":"** 👀 诊断信息 **","tag":"lark_md"}},{"tag":"column_set","flex_mode":"none","background_style":"default","columns":[{"tag":"column","width":"weighted","weight":1,"vertical_align":"top","elements":[{"tag":"markdown","content":"${problem_item_name} : ${problem_item_value}"}]}]},{"tag":"hr"},{"tag":"div","text":{"content":"告警来源:${from}","tag":"plain_text"}},{"tag":"action","actions":[{"tag":"button","text":{"tag":"plain_text","content":"跳转到运维中心"},"type":"danger","multi_url":{"url":"${message_url}","pc_url":"","android_url":"","ios_url":""}}]},{"tag":"column_set","flex_mode":"trisect","background_style":"default","columns":[]},{"tag":"column_set","flex_mode":"bisect","background_style":"default","columns":[]}],"header":{"template":"red","title":{"content":"【${event_id}】故障:${event_name}","tag":"plain_text"}}},"mock_data":"{\"event_name\":\"存储设备失效\",\"event_id\":\"114514\",\"event_date\":\"2023-12-03\",\"event_time\":\"10:36:00\",\"host_name\":\"Huawei\",\"event_severity\":\"严重\",\"message_url\":\"\",\"trigger_name\":\"\",\"problem_item_name\":\"Disk0\",\"problem_item_value\":\"处于临界状态0/0\",\"host_ip\":\"10.10.10.10\",\"from\":\"\"}","variables":[{"type":"Text","name":"event_name","description":"事件名称","variable_id":0,"config":"{}"},{"type":"Text","name":"event_id","description":"事件ID","variable_id":0,"config":"{}"},{"type":"Text","name":"event_date","description":"事件日期","variable_id":0,"config":"{}"},{"type":"Text","name":"event_time","description":"事件时间","variable_id":0,"config":"{}"},{"type":"Text","name":"host_name","description":"设备名称","variable_id":0,"config":"{}"},{"type":"Text","name":"event_severity","description":"严重性等级","variable_id":0,"config":"{}"},{"type":"Link","name":"message_url","description":"消息链接","variable_id":0,"config":"{\"is_multi_url\":false}"},{"type":"Text","name":"trigger_name","description":"触发器","variable_id":0,"config":"{}"},{"type":"Text","name":"problem_item_name","description":"问题详情名称","variable_id":0,"config":"{}"},{"type":"Text","name":"problem_item_value","description":"问题详情值","variable_id":0,"config":"{}"},{"type":"Text","name":"host_ip","description":"设备IP","variable_id":0,"config":"{}"},{"type":"Text","name":"from","description":"告警来源","variable_id":0,"config":"{}"}]}

你可以直接下载另存为 json 文件,登录 飞书开放平台 - 消息卡片搭建工具,选择导入卡片,导入刚才下载的卡片配置文件。

20231203181845

导入之后,你可以对卡片结构或者内容进行微调,调整完毕后,点击“保存并发布”即可。

20231203182527

这个卡片 ID 很重要,记得存一下,一会要用的。

创建一个飞书告警媒介

在 ZABBIX 中创建一个新的报警媒介,类型选择 Webhook,参数有三个,参考下表。

cmake
1
2
3
Botkey:    飞书Webhook地址后面那段
HTTPProxy: 是否需要代理,如果需要代理,就加代理就好了
Message: {ALERT.MESSAGE}

20231203192512

脚本从下面这个 Gist 中获取。以下内容托管在 GitHub,如果看不到,请确认您是否有访问权限。

// Botkey : FeishuBot Key
// Message : {ALERT.MESSAGE}
// HTTPProxy
try {
var params = JSON.parse(value),
req = new HttpRequest(),
response;
if (params.HTTPProxy) {
req.setProxy(params.HTTPProxy);
}
if (params.Message === 'test') {
var data = {
"msg_type": "interactive",
"card": {
"type": "template",
"data": {
"template_id": "",
"template_variable": {
"event_name": "{EVENT.NAME}",
"event_id": "{EVENT.ID}",
"event_date": "{EVENT.DATE}",
"event_time": "{EVENT.TIME}",
"host_name": "{HOST.NAME}",
"event_severity": "{TRIGGER.SEVERITY}",
"message_url": "",
"trigger_name": "{TRIGGER.NAME}",
"problem_item_name": "{ITEM.NAME}",
"problem_item_value": "{ITEM.VALUE}",
"host_ip": "{HOST.IP}",
"from": "ZABBIX"
}
}
}
};
} else {
var data = JSON.parse(params.Message);
}
req.addHeader('Content-Type: application/json');
Zabbix.log(4, '[ FeishuBot WebHook ] Webhook request with value=' + value);
response = req.post('https://open.feishu.cn/open-apis/bot/v2/hook/' + params.Botkey, JSON.stringify(data));
Zabbix.log(4, '[ FeishuBot WebHook ] Responded with code: ' + req.getStatus() + '. Response: ' + response);
try {
response = JSON.parse(response);
}
catch (error) {
if (req.getStatus() < 200 || req.getStatus() >= 300) {
throw 'Request failed with status code ' + req.getStatus();
}
else {
throw 'Request success, but response parsing failed.';
}
}
if (req.getStatus() !== 200 || response.msg !== 'success') {
throw response.msg;
}
return 'OK';
}
catch (error) {
Zabbix.log(3, '[ FeishuBot WebHook ] Sending failed. Error: ' + error);
throw 'Failed with error: ' + error;
}
view raw feishubot.js hosted with ❤ by GitHub

配置消息模板,下面这个是告警模板,你需要替换【】中的内容,替换后,不需要加【】。

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"msg_type": "interactive",
"card": {
"type": "template",
"data": {
"template_id": "【消息卡片模板 ID】",
"template_variable": {
"event_name": "{EVENT.NAME}",
"event_id": "{EVENT.ID}",
"event_date": "{EVENT.DATE}",
"event_time": "{EVENT.TIME}",
"host_name": "{HOST.NAME}",
"event_severity": "{TRIGGER.SEVERITY}",
"message_url": "【这里替换成跳转到监控平台的 URL】",
"trigger_name": "{TRIGGER.NAME}",
"problem_item_name": "{ITEM.NAME}",
"problem_item_value": "{ITEM.VALUE}",
"host_ip": "{HOST.IP}",
"from": "【消息来源,可做为安全校验字段使用】"
}
}
}
}

这个是告警恢复模板。

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"msg_type": "interactive",
"card": {
"type": "template",
"data": {
"template_id": "【消息卡片模板 ID】",
"template_variable": {
"event_name": "{EVENT.NAME}",
"event_id": "{EVENT.ID}",
"event_date": "{EVENT.DATE}",
"event_time": "{EVENT.TIME}",
"host_name": "{HOST.NAME}",
"event_severity": "{TRIGGER.SEVERITY}",
"message_url": "【这里替换成跳转到监控平台的 URL】",
"trigger_name": "{TRIGGER.NAME}",
"problem_item_name": "{ITEM.NAME}",
"problem_item_value": "{ITEM.VALUE}",
"host_ip": "{HOST.IP}",
"from": "【消息来源,可做为安全校验字段使用】",
"event_duration": "{EVENT.DURATION}"
}
}
}
}

添加完是这个样子的。保存即可。

20231203192527

用户关联报警媒介

对用户关联一下刚才创建的报警媒介。

20231203193400

结语

好了,现在就可以愉快的接收 ZABBIX 飞书告警了。