上个月,我的个人服务器毫无征兆地宕机了。磁盘写满,日志系统崩溃,等我发现时已经过了 47 小时。那台机器上跑着两个个人项目和一个定时备份脚本,损失的数据和重新配置的时间折算下来,大概值 800 块人民币——这还不包括我熬夜排查的怨气。

Day 1 我做了个决定:必须装一套监控,而且要免费、可自托管、能让我在手机上随时看。调研了两小时后,我选了 Grafana + Prometheus 这个组合,而不是现成的 Netdata 或 Uptime Kuma。为什么?往下看。

选择:为什么是 Prometheus 而不是 Zabbix

我在第 2 周把方案定了下来。Zabbix 我也试过,它是全功能的老牌监控,但配置流程长得像填税表:先建主机组,再配模板,再搞触发器,再用宏变量……我花了 3 个小时还没看到第一张图表。说实话,Zabbix 适合有专职运维的团队,我这种一个人管三台机器的场景,它属于杀鸡用牛刀。

Prometheus 的模型简单粗暴:每个被监控的机器或服务暴露一个 HTTP 端点,Prometheus 定时去拉取指标。你不需要装 agent(除非你想测更细的系统指标),只要目标服务有 /metrics 端点就行。Grafana 只管画图,数据全部来自 Prometheus 的查询语言 PromQL。

对比表如下:

工具 安装复杂度 资源占用(内存) 告警能力 适合场景
Prometheus + Grafana 中(两个二进制+配置) 约 150-250 MB 需额外配置 Alertmanager 个人服务器、微服务、K8s
Zabbix 高(服务端+前端+DB) 约 500 MB+ 内置完整,但配置繁琐 企业级多主机
Netdata 低(一键脚本) 约 100 MB 基础告警,需付费进阶 快速查看实时指标
Uptime Kuma 极低(Docker) 约 50 MB 简单 HTTP 探测 只关心网站是否在线

我选了 Prometheus,因为它的数据模型是时间序列,可以自由聚合、切片、画任意维度的趋势图。Netdata 虽然漂亮,但它的图表都是预设的,我想自定义一个“CPU 温度 vs 内存占用”的散点图,它做不到。Prometheus 配 Grafana,所有图表都是我从零画的,自由度极高。

不过呢,有个代价:Prometheus 的配置语法是 YAML,缩进错了就启动失败。我第一天就栽了两次跟头。

踩坑实录:三个让我抓狂的问题

坑 1:node_exporter 怎么都连不上

现象:prometheus.yml 里写了 scrape_configs,目标指向 localhost:9100,重启 Prometheus,/targets 页面显示 DOWN,红得刺眼。我检查了 node_exporter 进程,明明在跑。

困惑:端口也听了,防火墙也没拦(我甚至把 firewalld 临时关了),curl http://localhost:9100/metrics 能返回数据,但 Prometheus 就是抓不到。

解法:折腾了 2 小时,最后发现是 Prometheus 容器(我用 Docker 跑的)与 node_exporter 不在同一个网络命名空间。我的 Prometheus 容器用 -p 9090:9090 映射了端口,但容器内部的 localhost 是容器自己的回环地址,不是宿主机的。改成 127.0.0.1 为宿主机 IP(例如 192.168.1.5),或者把 Prometheus 和 node_exporter 放在同一个 Docker 网络,问题消失。

血泪教训:Docker 容器里的 localhost 不是你的 localhost。这个坑,新手必有。

坑 2:磁盘告警误报

现象:我配置了磁盘空间超过 80% 就告警。结果第 3 天,手机收到一堆告警,说 / 根分区使用率 93%。我登录服务器一看,df -h 显示只有 61%。

困惑:Prometheus 上的 node_filesystem_avail_bytes 和系统 df 显示的数值不一致。差那么多,数据源没错,但指标口径不同。

解法:查资料才知道,node_exporter 默认把某些挂载点过滤掉了,但更重要的是,df 显示的是文件系统块数,而 Prometheus 指标区分了 avail(非 root 可用的)和 free(包含 root 保留块)。另外,overlay 文件系统(Docker 常用)会带来额外的计算差异。最靠谱的方式是用 node_filesystem_size_bytes - node_filesystem_avail_bytes 来算使用率,且过滤掉 fstype="overlay" 和挂载点 / 之外的。我在 PromQL 里加了:

100 - (node_filesystem_avail_bytes{fstype!~"overlay|tmpfs",mountpoint="/"} * 100 / node_filesystem_size_bytes{fstype!~"overlay|tmpfs",mountpoint="/"})

从此告警准确了。但我也要承认,Prometheus 的指标命名和计算公式需要你自己理解,不像 Zabbix 内置了现成的模板,这点 Prometheus 的学习曲线比 Zabbix 陡。

坑 3:告警规则重复触发

现象:我写了一个规则,当内存使用率超过 90% 持续 5 分钟就告警。结果半夜里收到 12 条告警,每条时间间隔 1 分钟。我不胜其烦,把 Alertmanager 的 group_wait 设了 30 秒,repeat_interval 设了 4 小时,还是不行。

困惑:我以为 Alertmanager 会按我的配置去分组和去重,但规则里的 for: 5m 加上评估频率 evaluation_interval: 15s,导致同一个状态在满足条件期间每 15 秒触发一次,而 Alertmanager 的 repeat_interval 是从第一次告警开始算,不是从沉默期间算。

解法:我发现 repeat_interval 是用于已经解决的告警,如果告警一直处于 Firing 状态,它会按照 repeat_interval 重新发送,但前提是 Alertmanager 认为这个告警是“新的”。解决方法是把 Prometheus 规则里的 for 延长到 10 分钟,并且把 Alertmanager 的 group_wait 设为 1 分钟,group_interval 设为 5 分钟,repeat_interval 设为 6 小时。同时我加了路由匹配,按 severity=critical 和 severity=warning 分别通知。折腾了一个下午,最终告警数量稳定在每天最多 2-3 条。

说实话,告警配置是所有监控系统里最难啃的部分,Prometheus 的告警链路由 Prometheus 规则 + Alertmanager 两个组件协同,比 Zabbix 的单一触发器模式复杂,但灵活度也高。

搭建步骤:从零到第一张图表(附时间锚)

整个搭建过程我分成了四个半天,总计约 6 小时有效时间。以下是我在 Day 4 完成后的总结步骤:

  1. 下载 Prometheus 和 node_exporter 二进制(v2.53.0 和 v1.8.2),解压到 /opt/prometheus 和 /opt/node_exporter。我用 systemd 管理,启动参数加了 --web.enable-lifecycle 以便热重载配置。
  2. 创建 /etc/prometheus/prometheus.yml,基础配置包括 global.scrape_interval: 15s,scrape_configs 里加一个 job 叫 node,静态目标指向本机 IP:9100。
  3. 用 curl localhost:9100/metrics 确认输出有 node_cpu_seconds_total 等指标。
  4. 下载 Grafana(v11.1.0),直接解压运行,默认端口 3000。首次登录 admin/admin,强制改密码。
  5. 在 Grafana 里添加数据源,类型选 Prometheus,URL 填 http://localhost:9090,保存并测试。
  6. 开始建 Dashboard。我用 Grafana 的“Import”功能,输入官方 dashboard ID 1860(node_exporter 全指标模板),一键导入。但我又自定义了几个面板:CPU 使用率、内存、磁盘 I/O、网络流量,外加一个“按进程数排序”的表格。
  • 关键点:不要直接用官方模板完事。模板默认展示很多指标,但你不一定关心每项。我删了 60% 的 panel,只留 8 个,看起来清爽。
  • 关键点:Grafana 的告警功能也内置了,但为了用上 Alertmanager,我单独装了 Alertmanager v0.27.0,并用 webhook 推到我的微信(用 Server酱 免费版)。
  • 关键点:所有配置都要加注释。第 2 周末我改了一次 scrape_interval,没注释掉旧的,后来排查的时候差点疯掉。

数据效果:监控到底给了我什么

在跑到第 2 周结束时,我的监控面板已经能准确反映每天 24 小时的内存和负载变化。最直观的收获有三条:

  • 我发现了自己定时任务的一个死循环:某个 Python 脚本每个小时跑一次,但没加锁,导致两个实例互相覆盖写文件,CPU 每隔一小时飙升 40%。监控图上的锯齿形告诉了我这个规律,修复后 CPU 曲线平稳。
  • 磁盘增长趋势被我抓到了:原先 80 GB 的根分区,每天按 0.7 GB 的速度增长。我定位到是 Nginx 日志没有切割,加上容器日志无限累积。加了 logrotate 和 Docker 的 --log-opt max-size=10m 之后,日增长降到 0.1 GB。
  • 告警成功率:配置好规则后 7 天里,共触发 5 次告警,全部有效(2 次是内存使用率超过阈值,3 次是磁盘 I/O 尖峰)。没有误报。

我们团队(其实就我和我女朋友)现在每天打开 Grafana 看几眼,像看鱼缸一样。手机上也装了 Grafana 移动端,虽然不是原生 app 但网页适配不错。

结论与局限

回过头来看,这套监控系统确实值回了时间成本。我用到的工具全是开源免费,硬件成本为零,维护成本大概是每月 30 分钟的检查。比起那次 800 块的损失,它救了我至少两次。

但我也要承认 Prometheus 不如 Zabbix 的地方:Zabbix 的自动发现和一键告警集成(邮件、短信、脚本)做得更完善,Prometheus 需要你自己写 webhook 和路由规则;Zabbix 有内置的 SLA 报表、历史记录透视,Prometheus 的 UI 只能看实时和趋势,想导出报表要自己用 SQL 或者 Grafana 的 CSV 功能。

另外,Prometheus 的存储是本地 TSDB,默认只保留 15 天。我的是个人服务器,无所谓,但如果你要长期保留指标做年度趋势分析,需要配置远程存储或加 Thanos,又是一笔学习成本。

不过最后,我还是会推荐个人开发者用 Prometheus + Grafana。理由只有一个:当你终于理解 PromQL 的 rate 和 increase 区别时,那种掌控感,比用现成工具爽太多。

你的服务器现在裸奔着吗?看看它的 /metrics,然后决定今天动手不?