上周三凌晨两点半,我正被手机上一连串告警推送吵得半睡半醒,翻来覆去点开看,才发现最要命的那条“数据库主库连接数打满”已经被其他消息淹没在十分钟前了。邮件、短信、微信群其实一个都没漏发,问题出在“人”身上——手机不在手上、开着勿扰、或者被消息风暴炸到麻木。
也就是从那次之后,我开始认真琢磨怎么把告警从“数字通道”拉到“物理世界”来。花了一个下午,我把手头的 Zabbix 和 Prometheus 告警通过 HTTP API 接到了工位旁边的博灵语音通知终端上,实现了声光报警 + TTS 语音播报。这篇内容我就把当时的完整做法拆一遍,包括设备侧怎么调、Zabbix 侧怎么配、Alertmanager 侧怎么对接,以及我在里面踩过的几个非常典型的坑。
1. 告警送达的最后一公里:为什么说手机不是终点
1.1 手机告警的三个盲区
很多做运维的朋友觉得告警能推到手机就已经够了,但真实值班场景里,手机这个通道远没有我们想的那么可靠。我总结下来有三个特别典型的盲区:
第一个是勿扰模式。晚上睡觉大家都开着勿扰,重要告警推送到手机上的结果就是折叠进通知中心,第二天早上才看到,故障早就发生了。第二个是消息风暴。一次故障往往同时触发几十条关联告警,微信群、钉钉群、邮件列表一起刷,真正关键的“P0”信息反而被淹没在一堆“连接数升高”“响应时间变长”的次要告警里。第三个是信号盲区。机房、楼层电梯间、地下室这些地方,手机网络本身就差,你在现场处理故障时想掏出手机看一眼最新告警,结果界面一直在转圈。
这三个问题说白了就是:手机告警的触达率极度依赖“人愿意看手机 + 手机有网络 + 通知没被折叠”。任何一个环节掉了链子,告警就等于白发了。
1.2 声光终端的定位:物理存在感 + 语音播报
博灵语音通知终端的形态很像一个小型工控盒,自带喇叭、LED 灯带,有的型号还支持状态屏。把它放在值班室桌面或者机房门口,它干的事情很简单——通过 HTTP API 收到告警请求后,用内置 TTS 引擎把文本合成语音播报出来,同时控制灯光的颜色、闪烁频率和蜂鸣次数。
这玩意儿和手机推送最大的区别在于“物理存在感”。再响的手机铃声你也有可能下意识划掉,但房间里一个红色灯光在闪、喇叭在喊“严重告警,请尽快处理”,这种刺激你没法忽视。尤其是后半夜值班,手机放在桌上充着电,你躺着半梦半醒,声音一响整个人直接就清醒了。
TTS 播报还有一个好处是信息密度高。不用解锁手机去看推送内容,语音直接把告警名称、影响位置、故障级别念给你听,你闭着眼都知道是哪个系统出了问题。对于需要同时盯监控大屏和处理故障的人,这种“耳朵也能看告警”的方式真的很省事。
1.3 整体数据流与方案选型逻辑
这次方案的核心数据流是这样的:
Zabbix / Prometheus 产生告警 ↓ Zabbix 告警媒介脚本 / Alertmanager Webhook ↓ HTTP POST 请求 ↓ 博灵语音通知终端 ↓ 声光报警 + TTS 语音播报为什么选 HTTP API 而不是其他方式?我对比了几种:
| 对接方式 | 实时性 | 可控性 | 实施成本 | 结论 |
|---|---|---|---|---|
| 邮件推送 | 中 | 低 | 低 | 要配置邮箱解析,延迟不可控 |
| Modbus/SNMP 直接控制 Io | 高 | 高 | 高 | 需要工业网关,协议复杂 |
| HTTP API | 高 | 高 | 低 | 监控系统原生支持,最合适 |
HTTP API 对 Zabbix 和 Prometheus 都很友好。Zabbix 原生支持脚本媒介,脚本里 curl 一下就能发请求;Prometheus 生态里有 Alertmanager 的 webhook,也能直接 POST JSON。而且 HTTP 接口可以在请求体里带上灯光颜色、音量、播报次数这些参数,分级告警的差异化联动就很容易实现了。
还有人会问,为什么不用终端自带的邮件接收功能或者短信网关?实话实说,终端的 HTTP 接口是往监控系统这边靠的,邮件服务会出现解析失败、延迟、垃圾邮件误判等问题,短信网关还要额外买服务。HTTP API 是最短路径,链路越短越可靠。
2. 博灵终端侧的准备:先把硬件调通再联监控
2.1 终端上线与网络配置
拿到博灵终端之后,第一步先把设备本身调通,别一上来就折腾监控侧,不然出了问题你都不知道该查哪边。
我这边的情况是终端通过网线接入办公网,接通电源后,终端自带的小屏幕上会显示获取到的 IP 地址。你用浏览器访问这个 IP,进入终端的管理后台。如果设备支持屏幕操作,也可以直接在面板上配置静态 IP。这里有一个建议:最好给终端配置一个固定 IP,不要用 DHCP,否则 IP 变了,监控侧脚本里写的地址也要跟着改,值班室没有显示器的时候排查起来相当痛苦。
管理后台里需要重点设置三个东西:
- 管理员密码:出厂默认密码一定要改,终端挂在办公网里,被同事拿着 curl 瞎打一发,半夜播报一条“测试告警”可不是什么好体验。
- NTP 时间同步:终端日志和布防时段都依赖系统时间,时间不对,定时静默就会乱套。
- 对外 HTTP API 开关:有的型号默认关闭,需要在后台打开,并生成一个访问 Token。这个 Token 就相当于终端的钥匙,监控侧调用的时候要用。
终端摆放位置也有讲究。如果是放值班室,尽量放在工位对角线的墙面上,LED 灯带的余光能被眼睛余光扫到就行,不要直射眼睛。喇叭音量你先用管理后台的测试功能放一段,调到“能吵醒你但不至于炸耳”的程度,我一般放在中档偏上。
2.2 用 curl 实测 HTTP API 的两种调用方式
设备管理后台里通常会写接口路径和调用示例。我手头这台设备的对外接口是/api/v1/play,不同的固件版本可能不一样,建议你们以自己设备的官方文档为准,但调用逻辑是通用的。
我推荐统一用 POST + JSON 的方式,因为请求体里可以携带更丰富的参数,也不会像 GET 一样遇到中文 URL 编码的坑。可以用 curl 直接测:
curl -X POST "http://<终端IP>:<端口>/api/v1/play" \ -H "Content-Type: application/json" \ -H "X-Auth-Token: <你的Token>" \ -d '{ "text": "这是一条测试告警,请检查终端声音和灯光", "light": "red", "volume": 8, "times": 2, "tts": { "voice": "female", "speed": 1.0 } }'正常的话,终端应该立刻开始播报语音,并亮起红色灯光。如果没反应,先看管理后台的请求日志,日志里会记录收到请求的时间、请求体内容以及返回的响应状态。
还有一种方式是 GET 请求:
curl -G "http://<终端IP>:<端口>/api/v1/play" \ --data-urlencode "text=这是一条测试告警" \ --data-urlencode "light=red" \ -H "X-Auth-Token: <你的Token>"GET 方式胜在简单,但特殊字符转义麻烦。我的建议很直接:能用 POST 就别用 GET,减少一类编码问题。
关于灯光参数,不同型号可能支持不同的颜色枚举值,常见的有 red、yellow、blue、green。如果你传了一个设备不支持的枚举,设备会返回 500 错误,所以先看文档里支持哪些颜色,做好映射再上线。
2.3 我报错最多的 500 internal server error 排查
很多朋友在对接博灵终端时会遇到一个很典型的报错:
request returned 500 internal server error for api route and version http://...我第一次遇到这个错也懵了一下,排查了几轮之后发现,绝大多数 500 不是设备内部故障,而是请求本身有问题。我按概率排个序:
- URL 路径写错了,或者漏了 API 版本号。比如设备文档写的是
/api/v1/play,你写成了/api/play,或者端口从 8080 写成了 80。 - Token 没传对。有些型号对鉴权失败的响应也是 500,不会像标准 Web 服务那样返回 401/403,所以要先确认 Token 有没有复制错、Header 名字对不对。
- JSON 字段不在设备支持列表里。单词拼错、参数类型不对、灯光枚举值不支持,都会导致 500。
- 网络不通或端口被防火墙拦了,请求被重置,表现也可能被误判成 500。
排查的时候千万别对着报错硬看,用curl -v看完整交互,再打开终端管理后台的日志,两边一对比基本就能定位。我把几种常见情况和排查思路放进 5.3 的速查表里了,到时候对号入座就行。
3. Zabbix 告警接入实战:一个脚本加一次动作配置
3.1 准备告警媒介脚本
Zabbix 对接第三方告警通道最通用的方式,就是“脚本媒介”。原理很简单:告警触发时,Zabbix Server 会调用一个外部脚本,把发送目标、告警主题、告警内容三个参数传给脚本,脚本再通过 curl 把它们转成 HTTP 请求打到终端。
先找一下 Zabbix Server 的 AlertScriptsPath 目录,一般在配置/etc/zabbix/zabbix_server.conf里:
grep AlertScriptsPath /etc/zabbix/zabbix_server.conf # 常见路径 /usr/lib/zabbix/alertscripts 或 /etc/zabbix/alert.d在这个目录下新建脚本boling_alert.sh并授权:
chown zabbix:zabbix /usr/lib/zabbix/alertscripts/boling_alert.sh chmod +x /usr/lib/zabbix/alertscripts/boling_alert.sh脚本内容我用的版本如下,基于系统自带的 Bash 和 Python3,不需要额外装依赖:
#!/bin/bash # 博灵语音通知终端 - Zabbix 告警媒介脚本 # 标准输入传入三行:SendTo / Subject / Message IFS= read -r sendto IFS= read -r subject IFS= read -r message token="<你的终端Token>" severity="$subject" light="red" volume="8" times="1" case "$severity" in *Disaster*|*High*) light="red" volume="10" times="3" ;; *Warning*) light="yellow" volume="7" times="2" ;; esac # TTS 播报只取主题,避免念一长串详情 speak_text="${subject}" # 用 Python3 构造 JSON,中文不会被截断或转义错 payload=$(python3 -c ' import json, sys print(json.dumps({ "text": sys.argv[1], "light": sys.argv[2], "volume": int(sys.argv[3]), "times": int(sys.argv[4]) }, ensure_ascii=False))' "$speak_text" "$light" "$volume" "$times") curl -s -X POST "http://${sendto}/api/v1/play" \ -H "Content-Type: application/json" \ -H "X-Auth-Token: ${token}" \ -d "$payload" \ || echo "$(date '+%F %T') 博灵终端调用失败: $sendto $subject" >> /var/log/zabbix/boling_alert.log这里有个细节要提醒:Zabbix 的脚本媒介是从标准输入传参,和普通 shell 脚本的$1$2不一样,你如果写成用位置参数接收,会发现收不到任何内容。我先用IFS= read -r把三行分别读进来,这样最稳妥。
脚本里我只取了subject作为播报文本,message没有用。原因是 Zabbix 默认的告警消息非常长,直接 TTS 会念得头晕。播报文本的第一原则是“短”,详情留着看监控面板就好。
服务器的 curl 和 Python3 都确认一下装了没有,一般 Zabbix Server 上都有。没 curl 就apt install curl,没 Py3 就apt install python3,都是顺手的事。
3.2 创建媒体类型并绑定用户
脚本准备好之后,进入 Zabbix Web 管理界面,按这个路径配置:
- 左侧菜单:
报警媒介类型->创建媒体类型。 - 类型选“脚本”,脚本名称填
boling_alert.sh。 - 参数位置保持默认的三个框:
{ALERT.SENDTO}、{ALERT.SUBJECT}、{ALERT.MESSAGE}。如果界面只显示一个参数框,可以手动添加三个,依次填入上面的宏。 - 保存后,再进入
用户-> 找到你的值班用户 ->报警媒介->添加,媒介类型选择“博灵语音通知终端”,收件人填终端的IP:端口,比如192.168.1.50:8080。
千万注意这个收件人字段,它不是填手机号或邮箱,而是填终端地址。脚本收到的sendto变量就是这个值,拼 URL 的时候直接拼接在http://后面。
这里我推荐给值班组单独建一个 Zabbix 用户,比如叫ops-alert,然后把这个用户关联终端媒介。这样以后要调整声光策略,只需要改这一个用户,不需要动每个管理员个人账号的配置。
3.3 设置告警动作与分级模板
Zabbix 侧最后一步是配置动作,也就是“什么情况下,把告警发给谁”。
进入配置->动作->Trigger actions->创建动作:
- 名称:
博灵终端声光告警。 - 条件:故障级别大于等于“警告”。我的习惯是只把 Warn 及以上打到终端,
信息级别的低级告警量太大,全部打到终端会变成噪音。 - 操作步骤:发送消息给用户
ops-alert,媒介选择“博灵语音通知终端”。 - 消息主题模板我填的是:
[{TRIGGER.SEVERITY}] {TRIGGER.NAME} - 恢复操作同样配置一遍,这样告警恢复后终端会播报一条“恢复”消息,灯变绿色,值班员能直观看到故障闭环。
关于时间段布防:配置->动作里可以对步骤设置时间段,白天和夜间的播报策略可以拆成两个动作。比如晚上 22 点到早上 8 点,只允许“灾难”级别的告警触发终端播报,其余级别静默。这样既不会被低频告警吵醒,又保证最严重的问题能在深夜叫醒人。
Zabbix 侧配置完之后,可以手动在监控项上执行一次“测试”,或者直接在终端上 curl 一发,验证整条链路是否通了。如果你发现终端收到了请求但没声音,先检查脚本里的参数,特别是light值是否合法、音量是否设置成了 1。
4. Prometheus 接入实战:Alertmanager webhook 与中转服务
4.1 Alertmanager 的 webhook 配置
Prometheus 生态这边,告警统一由 Alertmanager 负责路由和发送。要让告警打到博灵终端,最直接的办法是在alertmanager.yml里配一个 webhook receiver。
我的配置片段如下:
route: receiver: boling group_by: ['alertname', 'instance'] group_wait: 10s group_interval: 2m repeat_interval: 30m receivers: - name: boling webhook_configs: - url: http://127.0.0.1:9021/alert send_resolved: true这里url指向的是我本地的一个中转服务,后面会讲。为什么不是直接指向博灵终端?因为 Alertmanager 发出来的 webhook 请求体是一个结构复杂的 JSON,里面包含 alerts 数组、labels、annotations、status、startsAt 等一堆字段,博灵终端解析这种工业 JSON 很容易翻车。用一个中转服务做“适配层”,把 Alertmanager 的复杂结构转成终端认识的简单字段,是最省事也最稳的做法。
group_wait和group_interval这两个参数建议认真调一下。默认值如果太小,同一批告警会被频繁重复推送到终端;repeat_interval如果太短,比如 5 分钟,那一个未恢复的告警每 5 分钟就会把终端喊一次,值班室里就跟开了复读机一样。我这边repeat_interval设在 30 分钟,从效果看,白天不至于太烦,晚上也足够敏感。
4.2 写一个 50 行的 Flask 中转服务
中转服务我直接用 Flask,代码量少,逻辑清晰,适合自己维护。你把它跑在监控服务器上,监听127.0.0.1:9021,只让本机的 Alertmanager 访问,不对外开放。
示例代码:
from flask import Flask, request, jsonify import urllib.request import json app = Flask(__name__) BOLING_HOST = "http://192.168.1.50:8080" BOLING_TOKEN = "<你的终端Token>" def speak(text, light="red", volume=8, times=1): data = json.dumps({ "text": text, "light": light, "volume": volume, "times": times, "tts": {"voice": "female", "speed": 1.0} }, ensure_ascii=False).encode("utf-8") req = urllib.request.Request( f"{BOLING_HOST}/api/v1/play", data=data, headers={"Content-Type": "application/json", "X-Auth-Token": BOLING_TOKEN} ) try: with urllib.request.urlopen(req, timeout=5) as resp: return resp.read().decode("utf-8", errors="ignore") except Exception as e: print("调用博灵接口失败:", e) return None @app.route("/alert", methods=["POST"]) def alert(): payload = request.json for a in payload.get("alerts", []): status = a.get("status") labels = a.get("labels", {}) annotations = a.get("annotations", {}) name = labels.get("alertname", "未知告警") instance = labels.get("instance", "unknown") severity = labels.get("severity", "warning") summary = annotations.get("summary", name) if status == "resolved": text = f"告警恢复,{name},{instance} 已恢复正常。" light = "green" times = 1 volume = 5 else: text = f"告警触发,{summary},位置{instance}。" if severity == "critical": light, volume, times = "red", 10, 3 else: light, volume, times = "yellow", 7, 2 speak(text, light=light, volume=volume, times=times) return jsonify({"code": 0}) if __name__ == "__main__": app.run(host="127.0.0.1", port=9021)启动方式很简单:
pip install flask python3 boling_webhook.py跑起来之后,先在本地用 Alertmanager 的 webhook 测试工具或者手工 POST 一个测试 JSON,确认终端有响应。生产环境建议用一个 systemd service 托管,这里不展开说,反正别直接挂在终端窗口下,服务器重启一下就没了。
这个服务还能扩展出很实用的能力。比如在/alert路径里按当前时间判断是否处于免打扰时段,凌晨两点只放行 critical,其他级别直接丢弃;再比如记录调用日志,把每次告警播报的文本写入文件,方便后续复盘。这些都是几行代码的事情,但能帮你省掉一大半半夜被吵醒的痛苦。
4.3 把 Grafana 告警也捎带上
如果你在用 Grafana 展示监控面板,它的告警功能(Alerting)同样可以接入这个中转服务。Grafana 在 v9 及以上版本里支持配置自定义 webhook 联系人点,只需要把 URL 指向同一个中转服务的/grafana/alert路由。
Grafana 的请求体和 Alertmanager 不一样,字段主要是title、message、state(alerting/resolved)和labels。所以在中转服务里加一个分支:
@app.route("/grafana/alert", methods=["POST"]) def grafana_alert(): payload = request.json state = payload.get("state") title = payload.get("title", "Grafana告警") message = payload.get("message", title) labels = payload.get("labels", {}) severity = labels.get("severity", "warning") if state == "alerting": text = f"Grafana告警,{title}。" light, volume, times = "red", 9, 3 elif state == "resolved": text = f"Grafana告警恢复,{title}。" light, volume, times = "green", 5, 1 else: return jsonify({"code": 0}) speak(text, light=light, volume=volume, times=times) return jsonify({"code": 0})这样同一个中转服务可以同时服务 Prometheus、Zabbix 和 Grafana 三套系统,统一管理声光策略。维护的时候只需要改这一个服务的代码,监控侧不用动,非常清爽。
5. 声光联动、TTS 播报细节与问题速查
5.1 声光布防时段与分级映射
终端接入之后,最重要的就是做好级别映射。如果不管什么告警都用红色灯、最大音量,那用不了两天值班同事就会把终端电源拔了。
我这边实际用的映射策略是这样的:
| 告警级别 | 灯光颜色 | 音量 | 播报次数 | 适用场景 |
|---|---|---|---|---|
| Critical / Disaster | 红色常亮或闪烁 | 9-10 | 3 次,间隔 5 秒 | 主库宕机、业务大面积不可用 |
| Warning / High | 黄色闪烁 | 6-7 | 2 次,间隔 15 秒 | 磁盘使用率过高、实例重启 |
| Info / 信息 | 不亮灯或蓝色呼吸 | 不播报 | 0 | 只记录,不打扰 |
| Recovery / 恢复 | 绿色亮 3 秒 | 4-5 | 1 次 | 值班员确认故障闭环 |
布防时段我会分成工作日白天、工作日夜间、周末全天三段。工作日白天所有 Warning 以上都播,夜间和周末只播 Critical。实现方式有两种:Zabbix 动作里按时间段配置,Alertmanager 路由里配静默,或者直接在中转服务里用 Python 的datetime.now().hour判断。我实际更推荐在中转服务里做,因为所有系统共用一套逻辑,改一处就全局生效。
5.2 TTS 播报文本怎么写才不“翻车”
TTS 播报文本这个坑,估计很多没做过的人想不到。同样是中文,不同写法播出来的效果差别巨大。
第一,播报文本一定要短。终端念一条告警,30 个字以内是最舒服的,超过 80 个字就会听得非常疲惫。我的做法是只播报“级别 + 告警名称 + 关键实例”,例如:“严重告警,主数据库连接数达到百分之九十五。”把%直接写成“百分之”,TTS 就不容易吞字。
第二,特殊符号要处理。&、/、=、\`` 这些符号在 TTS 引擎里很容易被逐字念出来,比如node_exporter中的下划线会变成“下划线”或者被忽略,听起来特别别扭。所以在脚本或中转服务里,要对文本做一次清洗,把下划线替换成空格,把百分比符号替换成文字,把http://` 这种前缀去掉。
第三,主机名和 IP 的朗读问题。像web-01这种名字,有的 TTS 会念成“web 零一”,有的会念“web 杠零一”。我建议在播报前给主机做一层别名映射,直接把web-01改成“生产网站服务器一”,或者干脆用中文报警名,少用具体机器名。TTS 的意义是让人快速理解“哪里出了问题”,而不是报出准确的内部主机名。
第四,语速和音色。博灵终端一般支持多音色和语速调节。我的建议是:普通告警用标准女声、语速 1.0,严重告警用男声、语速 1.2。更换音色本身就是一个很强的“级别差异”,值班员一听声音就知道事情多大了。
5.3 常见问题速查表
我把对接过程中最容易遇到的一批问题整理成了速查表,按“症状 -> 可能原因 -> 解决办法”的顺序列出来,供你直接对照排查:
| 症状 | 可能原因 | 解决办法 |
|---|---|---|
| 终端收不到任何请求 | 网络不通、端口被防火墙拦 | 先在监控服务器上 curl 终端地址,确认通不通 |
| curl 返回 500 internal server error | URL 路径错、Token 错、JSON 字段不合法 | 用curl -v看响应体,核对路径和字段是否与文档一致 |
| 请求成功但终端没声音 | 音量设置成了 0、播报文本为空、TTS 音色参数非法 | 检查 JSON 里的 volume、text、tts.voice 参数 |
| 灯光不亮 | light 参数值设备不支持 | 确认设备支持的灯光枚举值,不要传自定义颜色 |
| Zabbix 动作显示已发送但终端没反应 | 脚本没有执行权限、curl 未安装、脚本报错 | 看/var/log/zabbix/boling_alert.log,手动运行脚本测试 |
| 同一个告警反复播报 | repeat_interval 太小、Alertmanager 分组参数不合理 | 调大 repeat_interval 和 group_interval |
| 半夜被低频告警吵醒 | 没有设置布防时段 | 在中转服务或 Zabbix 动作里增加时间判断 |
| TTS 播报出现乱码或吞字 | 文本包含特殊符号、HostName 带下划线 | 清洗文本后再播报,去掉特殊符号并做中文替换 |
网上搜索这件设备时经常会看到那条“request returned 500 internal server error for api route and version http://”的报错,我在这件事上消耗的时间最多。回头看,本质上就是接口路径和 Token 没对齐。大家在对接任何设备时,先花十分钟把设备文档里的 API 示例跑通,再接入监控系统,能帮你省下数小时的排查时间。
最后再分享一个小技巧:我在中转服务里加了一个/test路由,调用它就能向终端发一条固定测试消息。每次调整完告警模板或者改完声光参数,我都会先 curl 一下这个接口,30 秒内就能确认链路是否还正常,不用去翻监控页面等真实告警触发。这个习惯帮我避免了好几次配置改坏后才发现问题的尴尬。
监控告警这件事,说到底拼的不是工具数量,而是信息能不能在正确的时机、以正确的方式到达正确的人。手机推送我照常用,但那些真正重要的 P0/P1 告警,现在一定会同步打到声光终端上。几次深夜的系统故障,最后都是被那一声清晰的女声叫醒的——手机上几十条推送,你根本没力气一条一条去翻。用 HTTP API 把 Zabbix 和 Prometheus 的告警接到博灵终端,是我最近半年做的最值钱的运维改造之一。