执行机监控,就该这么轻:5MB 探针 + 零依赖 Server 的 Pulse 方案

给执行机集群把脉:Pulse 如何用两个 Go 单文件,替代重型监控栈

摘要:执行机集群裸跑,出了事只能等投诉。Pulse 用 Agent + Server 两个 Go 单文件撑起集群监控:全指标采集、10 秒上报、离线与阈值告警、可视化面板,零外部依赖,5 分钟接入。


一、问题:执行机集群的"可观测真空"

CI/CD 构建机、自动化测试机、批处理执行机------这类集群有个共同特征:机器多、生命周期短、资源波动大。

它们和线上服务集群不一样。服务集群有 APM、有链路追踪、有 P99 告警;而执行机往往只有一句话的交代:

"跑不动了就重启一下。"

于是所有的排查都靠人肉:任务挂了 → ssh 上去 → top / free / df -h / iostat 挨个看 → 猜。问题在于:

  1. 滞后:等你登上去,现场可能已经消失了
  2. 浪费:几十台机器逐个登,纯体力活
  3. 无感:机器"假死"(进程在、上报断)根本发现不了

兜底方案通常是上 Prometheus + Grafana。但这套栈本身有成本:exporter 要装、配置要写、面板要调、内存要吃。对于内网、无外网、机器量大的执行机场景,投入产出比常常不划算。

Pulse 的思路是:用尽可能小的东西,解决"看得见"这一个核心问题。


二、设计:Agent 上报 + Server 接收 + 面板展示

Pulse 由两个独立二进制构成,数据流是单向的(执行机 → Server),Server 不主动采集、不反向控制机器。

Agent(执行机侧)

  • Go 交叉编译的单文件二进制,几 MB,无任何运行时依赖
  • 每 10 秒采集一次指标,HTTP POST 上报
  • 通过 PULSE_SERVER 环境变量指定上报地址
  • 以 systemd 托管:开机自启、崩溃自动重启

Server(中心侧)

  • 纯 Go 标准库,零外部依赖
  • 内存态维护 hostname:ip → 最近一次指标
  • 对外提供四个接口:
接口 方法 用途
/ GET Web 监控面板(go:embed 内嵌,无需单独部署前端)
/report POST Agent 上报入口
/status GET 全量状态 JSON(面板轮询用)
/health GET 健康检查

关键设计:Dashboard 用 go:embed 直接编进 Server 二进制。 不需要 Nginx、不需要前端构建产物、不需要单独的静态目录------一个二进制跑起来,页面就能访问。这是"单文件交付"理念的延伸。


三、采集的指标:覆盖执行机日常巡检全维度

类别 指标 看点
CPU 使用率 任务是否把机器跑满
内存 总量 / 已用 / 使用率 是否接近 OOM
Swap 总量 / 已用 / 使用率 持续偏高 = 物理内存不足
磁盘 根分区总量 / 已用 / 使用率 构建产物是否把盘撑满
负载 1 / 5 / 15 分钟 Load 看趋势,不看瞬时
运行时长 Uptime 骤降 = 机器刚重启过
网络 累计流量 + 实时速率 是否在疯狂拉包
磁盘 I/O 累计量 + 实时速率 瓶颈是不是在磁盘
在途 IO 未完成的 I/O 请求数 磁盘"堵不堵"的直接指标
进程数 进程总数 异常突增可能是 fork 炸弹

几个指标的选择是刻意为之:

  • Swap 而非只看内存:内存 90% 在跑任务的机器上很常见,但 Swap 长期被频繁读写,说明物理内存真的不够了------这是更硬的信号。
  • Uptime:结合"最后上报时间",一眼判断机器是重启了还是网络断了。
  • 在途 IO + 进程数:这两个指标平时不动,异动往往就是故障前兆(I/O 积压、fork 炸弹)。

采集的健壮性 :任何单个指标采集失败,只记日志、保留零值,其余指标照常上报 。某台机器缺 /proc/loadavg,不会导致整台机器失联。部分可见,永远优于完全不可见。

速率的正确算法 :网络/磁盘 I/O 底层是单调递增的累计计数器。Agent 保存上一轮采样值与时间戳,求差除以间隔得到 bytes/s。首轮采样、或机器重启导致计数器归零回绕时,本轮速率记 0(只重建基线),绝不出现负值或天文数字。


四、告警:只在该响的时候响

告警机制的设计目标很明确:信息量最大化,噪音最小化。

离线判定 :超过 30 秒未上报 → 标记 offline。

阈值告警(全部可用环境变量覆盖,无需改代码):

类型 默认阈值 含义
CPU 90% 持续跑满
内存 90% 物理内存吃紧
磁盘 85% 根分区接近打满
Swap 50% 频繁换页,物理内存不足
进程数 2000 异常突增,疑似 fork 炸弹

告警去重------这是体验上差异最大的一点。

常规做法是"每轮检查,超阈值就打日志",结果是一台机器超阈值 10 分钟,日志被刷几百行,真正的异常反而被淹没。

Pulse 的做法是维护一个告警状态机 :按「主机 + 告警类型」记录当前状态,只在两个状态跳变的时刻输出:

text 复制代码
⚠️  ALERT [worker-01]: CPU CPU过高: 92.3%
⚠️  ALERT [worker-02]: OFFLINE (last seen 45s ago)
✅ RECOVER [worker-01]: CPU 已恢复正常

一台机器长期超阈值 → 只在你需要知道的时候告诉你一次;恢复了 → 明确通知。"静默"本身就是一种信息:日志里没有新 ALERT,就说明一切照旧。


五、几个工程细节

1. 上报 IP 以 TCP 来源为准

Agent 自报的 IP 不采信,Server 从 r.RemoteAddr 解析真实来源地址。多网卡、NAT、容器网络下都更准确,安全上也更稳------Agent 被篡改也伪造不了来源。

2. 上报失败指数退避重试

网络抖动、Server 重启,Agent 按 1s → 2s → 4s 退避重试,最多 3 次。避免一次网络瞬断丢掉一批数据。

3. 阈值判定的单一实现

/status 查询和告警协程共用同一个阈值判定函数。这看着不起眼,但踩过坑的人都懂:判定逻辑写两遍,改阈值时漏改一处,就会出现"面板显示正常、告警在疯狂刷"的诡异现象。

4. 单请求 panic 隔离

Server 的 handler 统一包了一层 recover,单个请求 panic 不会带崩整个服务。


六、怎么用起来

Server(1 台)

bash 复制代码
# 交叉编译 Agent
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o pulse-agent ./agent

# 编译并启动 Server
go build -ldflags "-s -w" -o pulse-server ./server
export CPU_THRESHOLD=90 DISK_THRESHOLD=85   # 按需覆盖阈值
./pulse-server

Agent(每台执行机,一行命令)

bash 复制代码
curl -fsSL http://<server-ip>:8123/resources/install-pulse.sh | bash -s -- \
    "http://<server-ip>:8123/report"

脚本自动完成:下载二进制 → 写 systemd 服务 → 启动 → 验证 is-active。失败会提示去看 journalctl -u pulse-agent。

装完之后:开机自启、崩溃 5 秒自愈、上报每 10 秒一次。 然后就可以忘掉它了。

看板 :浏览器打开 http://<server-ip>:8080/,首页是集群总览(在线/离线/告警数 + 列表),支持按主机名/IP 实时搜索、表头排序、分页;点任意一行"详情",展开该机器的全部指标卡片。


七、适用边界:什么时候该用它

Pulse 适合:

  • 执行机 / 构建机 / 测试机集群的日常健康巡检
  • 内网、无外网、无法引入重型监控栈的环境
  • 只想解决"机器状态可见性"、不想背监控运维负担的团队

Pulse 不适合:

  • 需要长期指标存储和复杂查询(Pulse 是内存态,重启即清空)
  • 需要多租户 / RBAC / 权限体系
  • 需要业务指标(QPS、延迟分布、错误率)------Pulse 只管机器,不管业务

如果你的需求落在下面这栏,Prometheus + Grafana 依然是更正确的选择。Pulse 的定位是**"够用就好"的轻量可观测**,而不是"大而全"的监控平台。


八、小结

两个 Go 单文件 + 10 秒上报 + 智能告警去重 + 内嵌可视化面板 = 执行机集群的脉搏。

它不试图解决所有监控问题,只把"执行机现在怎么样"这一件事做扎实:部署轻、开销小、告警不吵、状态一眼可见。

对一片正在"裸奔"的执行机集群来说,这已经是从 0 到 1 的一步。


监控 #执行机 #Golang #轻量可观测 #健康巡检 #CICD