某次生产环境突发规模级告警风暴,数千个节点同时报出 Zabbix agent on [Hostname] is unreachable for 5 minutes。排查结论很简单:业务侧在 Zabbix Agent 中下发了一个用于探测第三方 API 健康状态的 Python 脚本,未设置网络超时。当第三方 API 响应极度缓慢时,Zabbix Proxy 端的 Poller(轮询)进程被大面积同步阻塞。吞吐量断崖式下跌导致监控队列深度爆满,心跳数据无法及时上报,最终引发大面积“假死”误报。
在分布式监控架构中,将监控系统当成同步发包工具,是极其危险的做法。监控系统的核心是调度与指标采集,绝不能让被监控端的状态反向拖垮监控基础设施。
现场还原:被“卡死”的 Proxy
告警风暴发生时,第一直觉是网络割接或 Proxy 节点宕机。但在 Proxy 宿主机上执行 fping 测试 Target 节点,延时和丢包率均在正常范围(< 2ms)。Proxy 节点本身的 Load Average 也不高。
切到 Zabbix Proxy 内部指标一看,核心问题暴露无遗:
图表 Zabbix internal process busy % 中,Poller 进程使用率直接飙死在 100%。同时,Zabbix Server 上的 Queue over 10 minutes 指标从平时的 0 激增到 5000+。
查看 zabbix_proxy.log,满屏都是这类警告:
12450:2023xxxx:xxxxx Zabbix poller processes more than 75% busy
12450:2023xxxx:xxxxx Zabbix poller processes more than 90% busy
12450:2023xxxx:xxxxx Zabbix poller processes more than 100% busy
12453:2023xxxx:xxxxx item "api.health.check" on host "node-10.0.0.5" failed: first network error, wait for 15 seconds
随便挑一个满载的 poller 进程跑一下 strace:
# strace -p 12501
restart_syscall(<... resuming interrupted poll ...>)
进程全部阻塞在等待网络 IO 返回上。
愚蠢的 Root Cause:缺失的 Timeout
顺藤摸瓜,在 Target 节点的 /etc/zabbix/zabbix_agentd.d/ 目录下找到了惹祸的配置文件和脚本:
# custom_api_check.conf
UserParameter=api.health.check, python3 /opt/scripts/api_check.py
# api_check.py 核心代码片段
import requests
import sys
def check_api():
try:
# 典型的“防御性编程”缺失
response = requests.get("http://third-party-api.internal.local/health")
if response.status_code == 200:
return 1
except Exception:
pass
return 0
print(check_api())
这段代码看似人畜无害,却是一个经典的“定时炸弹”。requests.get() 默认是没有任何超时限制的(或者依赖系统底层的 TCP 超时,通常长达数分钟)。当 third-party-api 所在的服务端发生处理卡顿,或者半开连接不返回数据时,这个 Python 进程就会永远挂起。
有人可能会问:Zabbix zabbix_proxy.conf 和 zabbix_agentd.conf 里不是都有 Timeout=30 的全局配置吗?为什么还会卡死?
这里需要厘清一个底层逻辑:Zabbix 的 Timeout 机制(上限通常编译限制为 30 秒)确实会在 30 秒后强制掐断 Agent 连接,但 30 秒对一个高并发轮询系统来说,等于灾难。
我们来算一笔账:
假设 Proxy 配置了 StartPollers=100,你需要监控 3000 个节点,该 API 检查项的采集周期(Update interval)是 1 分钟。
正常情况下,API 响应时间为 0.1 秒,100 个 Poller 每秒的理论吞吐量是 100 / 0.1 = 1000 次/秒,完全碾压 3000次/分钟 的需求。
当 API 挂起时,每次请求都要硬扛满 Zabbix 强制断开的 30 秒。此时 100 个 Poller 每秒的吞吐量暴跌为 100 / 30 = 3.3 次/秒。
1 分钟只能处理 3.3 * 60 = 198 个请求。
剩下的 2802 个请求怎么办?全部堆积在 Zabbix 的内存队列里。更致命的是,由于 Poller 进程池(Pool)是没有根据监控项做物理隔离的,这 100 个 Poller 被自定义脚本耗尽后,基础的 CPU、内存、网卡等常规监控项也拿不到 Poller 资源去执行。
最终结果就是:所有的被动监控数据(Passive Checks)全部断流,Server 端迟迟收不到 agent.ping 的数据,触发 nodata 告警,制造出几千台机器同时失联的恐怖假象。
架构级避坑建议与重构方案
在生产环境中,把业务探活、长耗时 DB 查询、甚至外部网络请求挂在 Zabbix Passive Agent (UserParameter) 上,是新手最容易踩的坑。要彻底解决这类问题,除了在代码层面加上 requests.get(..., timeout=3) 之外,必须从架构机制上进行改造。
1. 禁用长时间阻塞的被动检查,改为 Zabbix Sender 异步推送
不要让 Zabbix 去“拉”(Pull),让业务节点自己去“推”(Push)。
采用 Zabbix Trapper(采集器)模式,在节点本地利用 Cron 或守护进程跑脚本,结果通过 zabbix_sender 工具异步推送给 Proxy。这样耗时的卡顿只会发生在本地,完全不占用 Proxy 的 Poller 进程。
# 将计算与上报解耦
zabbix_sender -z <proxy_ip> -s <hostname> -k api.health.check -o 1
2. 大规模场景强制推行 Active Agent 模式
对于超过 1000 台规模的集群,基础监控应全部切换为 Active Checks(主动模式)。在 Active 模式下,Agent 会主动向 Proxy/Server 索取监控配置项,并在本地执行后批量封包回传给 Trapper 进程。Trapper 的并发处理能力远超 Poller,彻底根除轮询等待的网络 RTT 损耗。
3. Proxy 进程池隔离与容量规划
修改 zabbix_proxy.conf,将不稳定的自定义采集降级。
在较新的 Zabbix 版本中,如果非要用被动模式,确保对进程进行合理分配。监控数据库等慢查询,不要和基础 OS 监控混用相同的 Poller。虽然 Zabbix 原生暂不支持非常细粒度的 Poller 队列隔离,但可以通过部署多个 Proxy 实例(例如:Proxy-OS-Base, Proxy-App-Slow)进行物理隔离,防止爆炸半径扩散。
同类问题速查排查清单
-
Poller 使用率确认:立即查看
zabbix[process,poller,avg,busy]指标,若持续 > 80%,说明轮询机制已存在严重瓶颈或阻塞。 -
定位毒瘤监控项:在 Proxy 侧开启慢查询日志
LogSlowQueries=3000(记录执行超过 3 秒的 Item),配合zabbix_server.log搜索wait for 15 seconds关键字,精准定位阻塞源。 -
Timeout 强制审查:不仅要检查
zabbix_proxy.conf和zabbix_agentd.conf的Timeout值(建议控制在 3-5 秒),更要 Review 所有UserParameter调用的 shell/python/go 脚本内部网络请求是否硬编码了超时。 -
Trapper/Poller 进程比调优:当大量转为 Zabbix Sender 或 Active 模式后,注意下调
StartPollers,并适当调高StartTrappers数量,匹配实际架构变更。