去年入秋那阵,我自己的云服务器出了个很尴尬的事故:一台只跑了博客和几个小脚本的机器,突然被一条数据导出脚本打满了磁盘。用户没流失是万幸,但整个过程完全靠“有人登录后台才发现”,实在太被动。那次之后我想明白了——个人项目也需要一套监控,不是给领导看的,是给自己夜里能睡得着觉的。选型阶段我没怎么纠结,最后锁定了 Prometheus + Grafana + Node Exporter 这组标配。这套组合解决的核心问题就是:让一台 Linux 服务器上的 CPU、内存、磁盘、网络、进程等系统状态,变成可视化面板上一目了然的变化曲线,并在异常时发出告警。
本文会按我当时从零搭建的顺序,讲清楚为什么这么选、组件之间怎么配合、每一步怎么操作、常见的坑怎么避。适合第一次接触监控、想给个人服务器或小项目搭一套轻量监控的人。如果你已经在生产环境摸爬滚打过,也可以直接跳到配置拆解和踩坑部分,应该能有点共鸣。
1. 为什么个人项目也需要一套正经监控:我的选型过程
1.1 从“服务挂了都不知道”说起
个人服务器或者小项目,最常见的状态是:东西跑着,但没人盯着。网站访问慢、接口超时、磁盘空间耗尽、内存泄漏,这些问题通常不会主动通知你,只有等到用户来骂,或者自己哪天登录上去才发现。我那次磁盘告警就是典型——系统日志和导出任务把 / 分区写到 99%,网站还能勉强响应,但后台已经写不进任何东西了。
没有监控的时候,排查问题全靠“感觉”:先看 free -h,再看 df -h,然后 top 看一眼,运气好能找到原因,运气不好就瞎折腾半天。这种模式对付一次两次还行,反复出现就很折磨人。我当时想要的东西其实很简单:第一,能自动记录服务器关键指标的历史曲线;第二,指标异常时能主动通知我;第三,部署和维护成本不能太高,毕竟还有很多自己的事要做。
这个需求听起来简单,但真去选型时会发现选项多得吓人:Zabbix、Nagios、Cacti、Grafana + Telegraf、netdata、Glances,还有 Prometheus 全家桶。我花了一个下午在几套方案里来回横跳,最后选了 Prometheus 这条路,不是因为它最酷,而是因为它在“可维护性”和“生态成熟度”之间平衡得最好。
1.2 对比过 Zabbix、Prometheus 和轻量方案之后
当时的对比逻辑我写在下面这张表里,核心就三条:安装配置够不够简单、指标采集够不够灵活、把数据画成图够不够方便。
| 方案 | 部署复杂度 | 指标采集方式 | 可视化 | 生态和社区 | 适合场景 |
|---|---|---|---|---|---|
| Zabbix | 中等,需要维护 Server + Agent + 数据库 | Agent 主动上报,也支持被动拉取 | 自带,但界面偏传统 | 老牌,企业运维用得多,模板丰富 | 机房设备、传统 IT 基础设施 |
| Prometheus + Exporters | 低,二进制或容器一拉就能跑 | Pull 主动拉取,天然适合探测故障 | 借 Grafana,图表效果极好 | 云原生事实标准,社区 exporter 非常丰富 | 云服务器、K8s、应用自定义指标 |
| netdata / Glances | 极低,装完就有页面 | 本机采集、本机展示 | 有现成页面,但扩展性弱 | 轻量工具,偏单机即时观测 | 只想临时看看、不想做长期监控 |
| Grafana + Telegraf + InfluxDB | 中等,依赖时序数据库 | Telegraf 主动上报 | Grafana 同样优秀 | 生态不错,但多一个数据库要管 | 已经习惯 TICK 栈的团队 |
Zabbix 确实很强大,尤其是网络设备和企业级监控场景,但对个人项目来说,它的 Agent、Server、数据库、前端几件套装下来,维护成本明显偏高。netdata 和 Glances 是“快糙猛”的典型,装完立刻有曲线,可一旦你想自定义指标、做跨机器聚合、写复杂告警,它们就捉襟见肘了。
Prometheus 的优势在于它把“采集”和“存储”做成了两个非常标准的动作:目标方只要暴露一个 HTTP 接口,Prometheus 就按固定节奏去拉取一次数据,存进自带的时间序列数据库。配合 Grafana,一套监控环境从零到跑通,熟练的话半小时以内能搞定。这个投入产出比,对个人服务器来说非常划算。
1.3 Node Exporter 在其中的角色定位
整套链路里,Node Exporter 常被误认为是“监控软件本身”,其实它只是个翻译官。它的职责是读取 Linux 系统暴露出来的 /proc、/sys 等虚拟文件系统,把里面零散的 CPU、内存、磁盘、网络、文件系统信息翻译成 Prometheus 能理解的指标格式,然后通过 HTTP 接口对外提供。
Prometheus 是核心大脑,负责按照你配置的时间间隔,去 Node Exporter 的接口上把这些指标抓回来,存进本地时序数据库,同时周期性地评估告警规则。Grafana 则是门面,它本身不存储监控数据,只是通过 PromQL 从 Prometheus 里查数据,然后画成面板。
用一个生活化的比喻:Node Exporter 是贴在机器上的传感器,它在小黑板上写当前温度、转速、剩余油量;Prometheus 是值班员,每 15 秒去看一次小黑板,把数字抄进台账;Grafana 是驾驶舱仪表盘,你坐在里面看着台账画出来的曲线,就能知道整台机器是否健康。这三者各管一摊,职责非常清晰。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 监控链路的工作原理:指标从哪来、怎么流到面板
2.1 Pull 模型和 Push 模型的区别
Prometheus 默认采用 Pull 模型:它自己作为客户端,定时向目标发起 HTTP 请求,拉取指标。这套思路和传统 Push 模型的最大区别在于,监控端永远掌握主动权。目标机器不需要知道监控平台在哪,也不需要专门安装上报插件,只要在某个端口上把 /metrics 接口暴露出来就够了。
Pull 模型好处很多。目标突然失联时,Prometheus 会立刻表现为“抓取失败”,这个状态本身就是很好的故障信号。同时也方便做权限控制,你可以让采集端只访问目标端口,而目标机器的其他系统资源不直接暴露给外界。相比之下,Push 模型(比如 Telegraf 上报到 InfluxDB)需要在目标端装上上报组件,写清楚该往哪发、多长时间发一次,一旦目标端配置错了,数据就丢了,而且监控端很难立刻感知到目标已经死亡。
当然,Pull 模型也有短板:如果监控端和被监控端之间被防火墙隔开,或者目标机器在公网、没有固定端口,主动拉取就会困难。所以后来的 Prometheus 也有 Pushgateway 这样的补充组件处理短生命周期任务,但个人服务器这种长期稳定的节点,直接用 Pull 就好,没必要引入额外组件。
2.2 Node Exporter 是如何暴露指标的
Node Exporter 启动后,默认监听 9100 端口,把系统指标以纯文本形式通过 /metrics 路径暴露。你在浏览器里打开 http://服务器IP:9100/metrics,会看到一大片类似这样的内容:
text复制# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 1.243042e+06
node_cpu_seconds_total{cpu="0",mode="system"} 2.801234e+04
node_cpu_seconds_total{cpu="0",mode="user"} 5.237894e+04
这种文本格式叫 Exporter 文本格式,每一行表示一个时间序列。node_cpu_seconds_total 是指标名,大括号里的是标签,比如对应的 CPU 核、运行模式 idle、system、user,最后是当前累计值。Prometheus 抓取时就是解析这些文本,把指标名、标签、采样值一并存入自己的时序数据库。
Node Exporter 收集的指标非常全面,官方文档里列了几百个,常用的大致分几类:CPU 使用时间、内存使用量、磁盘空间和读写字节数、文件系统可用空间、网络收发字节数、进程状态、文件描述符数,还有系统负载平均值。对初次接触的人来说,不必全部看懂,先抓住 CPU、内存、磁盘、网络这几个基础大类就够了。
2.3 Prometheus 存储和 PromQL 的基本逻辑
Prometheus 把抓到的数据以时间序列的形式存储,每个时间序列由“指标名 + 标签集合”唯一标识。还是拿上面的 node_cpu_seconds_total{cpu="0",mode="idle"} 举例,这个序列在不同时间点上会有不同的采样值,比如 12:00:00 是 1243042,到 12:00:15 变成 1243048,Prometheus 就把这些 (时间戳, 值) 成对存下来。
查询时,Prometheus 使用的是 PromQL。它和 SQL 不一样,更像是一种专门面向时序数据的函数式语言。比如你想看最近 5 分钟 CPU 使用率,一条典型的 PromQL 是:
promql复制100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
含义是:取出 mode="idle" 的 CPU 累计时间序列,计算最近 5 分钟内的每秒变化率(rate),求平均值(avg),然后用 100 减去它,得到 CPU 使用率百分比。第一次接触时可能会觉得这语言有点绕,但用熟练之后会发现它表达能力极强——过滤、聚合、计算在一条语句里就能完成。
3. 完整部署实操:从空机器到三个服务正常跑起来
3.1 用 Docker Compose 把三个服务编排起来
我推荐用 Docker Compose 部署,因为三件套的依赖关系很固定,一条 docker compose up -d 就能全部拉起,比手工管理三个 systemd 服务省心得多。下面是我实际用的 docker-compose.yml,直接复制到服务器上的 /opt/monitor 目录里。
yaml复制version: '3.8'
services:
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
network_mode: host
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.rootfs=/host'
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
network_mode: host
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./alert.rules.yml:/etc/prometheus/alert.rules.yml:ro
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
- '--web.listen-address=:9090'
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
network_mode: host
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
volumes:
prometheus_data:
grafana_data:
这套配置里我特意把三个容器全部放到 host 网络而不是默认 bridge 网络。原因是 Node Exporter 要采集宿主机的 /proc 和 /sys 信息,用了 pid: host 和根目录挂载之后才算真正“看到”宿主机;三个服务都监听宿主机的端口,Prometheus 通过 localhost:9100 就能访问 Node Exporter,Grafana 通过 localhost:9090 就能访问 Prometheus,省掉了容器间网络别名的问题,排查起来也更直观。
3.2 服务启动后的健康检查
执行启动命令:
bash复制cd /opt/monitor
docker compose up -d
等待几十秒让镜像拉取和容器初始化完成,然后逐个检查。先看 Node Exporter 的指标接口:
bash复制curl localhost:9100/metrics | head -20
如果能看到 node_cpu_seconds_total 这样的文本输出,说明指标暴露正常。再看 Prometheus 自检接口:
bash复制curl localhost:9090/-/healthy
返回 Prometheus Server is Healthy. 就说明服务活着。Grafana 的健康检查走的是另一个接口:
bash复制curl localhost:3000/api/health
看到 "database": "ok" 的时候,说明 Grafana 也能正常工作了。这时候打开浏览器访问 http://服务器IP:3000,默认账号密码是 admin / admin,第一次登录它会强制要求改密码。改完后,整个监控环境的“骨架”已经跑起来了。
3.3 数据源配置:让 Grafana 认识 Prometheus
Grafana 刚装好时是不知道 Prometheus 在哪的,必须先把数据源接上。登录 Grafana 后,鼠标移到左侧齿轮图标,进入 Configuration > Data Sources,点击 Add data source,选择 Prometheus。
在配置页里,最关键的一项是 URL。因为我们用的是 host 网络模式,Prometheus 直接监听了宿主机的 9090 端口,所以这里填 http://localhost:9090 即可。如果你改成 Docker 默认的 bridge 网络,那这里就要填 http://prometheus:9090。填完后点击页面底部的 Save & Test,如果一切正常,会看到一个绿色的提示,告诉你数据源连接成功并且能查询到数据。
到这一步,监控数据链路已经通了:Node Exporter 采集系统指标 -> Prometheus 拉取存储 -> Grafana 查询展示。接下来要做的,是把配置细节搞清楚,不然这个环境只能算“能跑”,还没到“好用”的程度。
4. 关键配置项逐个拆解:别照抄,看懂再改
4.1 prometheus.yml 里 scrape_configs 的写法
很多人拿到别人的配置直接复制,出了问题也不知道从哪改。这里把一份最小可用的 prometheus.yml 拆开讲:
yaml复制global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- alert.rules.yml
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
labels:
env: 'prod'
global 里设置的是全局默认值:scrape_interval 表示 Prometheus 每 15 秒去抓一次指标,evaluation_interval 表示每 15 秒评估一次告警规则。rule_files 用来加载告警规则文件,后续加告警时要用到。
scrape_configs 是整个配置的核心。job_name 是给这一组采集目标起个名字,比如这里叫 node,以后在 Grafana 里筛选数据时会看到这个标签;static_configs.targets 填写要抓取的目标地址,格式是 IP或域名:端口;labels 是额外给这组目标打上的标签,比如 env="prod" 可以区分生产环境、测试环境。
如果以后监控多台服务器,就在 targets 里加多个地址,写成 ['localhost:9100', '192.168.1.101:9100'],Prometheus 会自动采集并给这些指标带上 instance 标签区分来源。
4.2 采集频率、超时和基础认证
scrape_interval 和 scrape_timeout 要配套。默认的抓取超时是 10 秒,如果采集间隔是 15 秒而某次抓取卡了 12 秒,下一次抓取就会推迟,长期下来数据会出现乱序。一个稳妥的做法是让超时时间明显小于采集间隔,比如 scrape_interval: 15s 配 scrape_timeout: 10s,或者 scrape_interval: 30s 配 scrape_timeout: 15s。
Node Exporter 默认没有鉴权,谁拿到 9100 端口都能访问。对个人服务器来说,最省心的方式不是给它加密码,而是不要让这个端口暴露到公网。云服务器安全组里只放行 80/443/22 这些必要端口,9100、9090、3000 都只允许本机访问,或者在测试时用 SSH 隧道访问。如果场景特殊必须暴露,再说用 --web.config.file 给 Exporter 配置 Basic Auth,一般个人环境用不到。
Grafana 的 3000 端口同理。个人服务器务必把默认密码改掉,或者再套一层 Nginx 反向代理加上访问认证,否则公开到公网会很容易被人扫到。
4.3 Docker 网络模式下的 target 地址怎么填
这是新手最容易踩坑的地方。target 地址怎么写,完全取决于你用什么网络模式:
- 如果 Prometheus 和 Node Exporter 都用
host网络,它们就在同一个网络栈里,target 写localhost:9100最直接。 - 如果两个容器都在同一个 Docker Compose bridge 网络里,target 写服务名
node-exporter:9100,Docker 内部 DNS 会自动解析到对应容器。 - 如果 Node Exporter 是裸进程跑在宿主机上,而 Prometheus 在 Docker 容器里,target 要写宿主机在容器网络里的网关地址,通常是
172.17.0.1:9100,或者直接写服务器内网 IP。
判断依据就是一句话:Prometheus 所在的网络栈里,用哪个地址能访问到目标。你可以先在 Prometheus 容器里执行 curl localhost:9100/metrics 试试,通不通一目了然。好在我上面给的 Compose 方案全用 host 网络,把这个问题规避掉了。
5. 机器健康指标这么看才不慌
5.1 CPU 和负载:node_load1/5/15
很多人一上来就盯着 CPU 使用率,其实系统负载可能更重要。node_load1、node_load5、node_load15 分别对应 1 分钟、5 分钟、15 分钟的平均负载。对这个值的解读,不能直接说“超过 1 就是满”,要看 CPU 核数。比如一台 4 核机器,负载长期在 2-3 是正常的;如果持续超过 4,说明 CPU 资源已经不够用了。
CPU 使用率反而要用变化率来计算,因为 node_cpu_seconds_total 是一个不断增长的累计计数器。直接看它没有意义,必须套上 rate 函数看它的增长速度:
promql复制100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
这里 [5m] 表示取最近 5 分钟窗口内的变化,得到的是平滑过的使用率。如果看瞬时峰值,可以把窗口缩短到 [1m],但曲线会抖动得比较厉害,个人监控我更倾向看 5 分钟平滑值。
5.2 内存与磁盘空间:available、filesystem
内存指标推荐用 node_memory_MemAvailable_bytes,这个值已经把内核的缓冲区、缓存算进去了,比单纯看 MemFree 准确得多。内存使用率查询可以写成:
promql复制100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
磁盘空间是从文件系统角度看的,指标名是 node_filesystem_avail_bytes 和 node_filesystem_size_bytes。注意要过滤掉 tmpfs、overlay 这类虚拟文件系统,否则会出现一堆用不上、值又没意义的分区:
promql复制100 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100
这条查询会返回每个真实磁盘分区的使用率。如果你机器上有多个数据盘,可以通过 instance 和 mountpoint 标签区分。
5.3 网络和进程:node_network、node_processes
网络指标是两个典型的计数器:node_network_receive_bytes_total 表示累计接收字节数,node_network_transmit_bytes_total 表示累计发送字节数。要画带宽曲线,同样要套 rate:
promql复制rate(node_network_receive_bytes_total{device="eth0"}[5m]) * 8
乘以 8 是把字节转成比特,eth0 换成你自己的网卡名。个人服务器上这个指标特别有用,能一眼看出是不是有进程在偷偷跑上传任务,或者被爬虫刷爆了带宽。
进程相关的指标,最常用的是 node_processes_state,它按进程状态(运行、睡眠、僵死等)分组统计。如果 state="Z" 的值长期大于 0,说明系统里有僵尸进程。不过对个人项目来说,进程状态一般不太需要盯着,反而是磁盘、内存这种和硬件资源强相关的指标更紧急。
5.4 几个能直接用的 PromQL 查询
我把日常最常用的几组查询列成表格,用的时候直接复制,改改标签就能用:
| 监控项 | PromQL |
|---|---|
| CPU 使用率 | 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 |
| 系统负载 | node_load1 |
| 内存使用率 | 100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 |
| 磁盘使用率(过滤虚拟文件系统) | 100 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100 |
| 入网带宽(bytes -> bit) | rate(node_network_receive_bytes_total{device="eth0"}[5m]) * 8 |
| 出网带宽 | rate(node_network_transmit_bytes_total{device="eth0"}[5m]) * 8 |
| 所有抓取目标是否在线 | up |
最后一条 up 是 Prometheus 自带指标,值为 1 表示目标抓取成功,值为 0 表示目标失联。告警规则里判断“服务挂了”,也是基于 up 来写的。
6. 给面板做减法:只留最关键的图表
6.1 导入现成面板与手动创建面板的取舍
Grafana 社区有大量现成面板可以直接导入。Node Exporter 最常用的面板 ID 是 1860(Node Exporter Full)和 8919(Node Exporter for Prometheus Dashboard)。导入方式是在 Grafana 左侧菜单点 Dashboards,然后 Import,填 ID,等待自动加载。
但我的建议是:个人服务器不要一上来就导入 1860 这种大而全的面板。它图表非常多,CPU、内存、磁盘、网络、文件系统、进程全部拆成一个个小块,还带各种聚合视图。信息密度极高,但对你日常巡检来说反而是负担——你根本不会每天看二十多个图表。不如花二十分钟手动建一个包含 4-6 个关键图表的面板,每个图表都是你自己写出来的 PromQL,理解更透彻,以后加指标也方便。
6.2 我自己的一张极简仪表盘
我的个人监控面板只有四个图表:CPU 使用率、内存使用率、磁盘使用率、入站/出站带宽。创建步骤很简单:Dashboards -> New dashboard -> Add visualization,数据源选 Prometheus,然后在 Query 编辑框里粘入上面对应的 PromQL。面板右上角可以改标题、单位、配色。
带宽图我建议用时间序列图,单位设为 bps;CPU、内存、磁盘使用率用百分比,记得在面板右侧的 Standard options 里把 Unit 改成 Percent (0-100),这样 Y 轴不会显示小数。我还会把时间范围默认设为最近 6 小时,平时扫一眼就能看出有没有异常波动。
6.3 Grafana 的变量和 sum by 用法
如果你以后监控多台机器,Grafana 的变量功能很值得学。在面板设置里创建一个变量,类型选择 Query,比如 instance,数据源选 Prometheus,查询语句写:
promql复制label_values(node_load1, instance)
这样面板顶部会出现一个下拉框,列出当前所有实例。随后你在面板查询里把 instance 变成变量引用,写成:
promql复制100 - avg(rate(node_cpu_seconds_total{instance="$instance", mode="idle"}[5m])) * 100
切换实例时,整个面板的数据都会跟着切换。和 sum by 结合使用,还能做跨实例聚合。比如多台机器汇总 CPU 使用率时,可以这样写:
promql复制100 - avg(sum by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) * 100
这个写法里,sum by (instance) 的作用是把每台机器内部的 CPU 核先聚合,再对实例做平均,得到的是一条只看得到实例差异的曲线。热搜词里提到的 grafana sum by,基本都在这个场景下使用。
7. 告警规则:监控不告警等于白搭
7.1 Prometheus 内部的 record 和 alert 规则
监控看起来很美,但如果没有告警,它只是一个事后翻看的记录本。真正让人省心的是“磁盘超过 85% 时,手机收到一条通知”。这个能力来自 Prometheus 的告警规则体系。
Prometheus 有两种规则文件:一种是 record 规则,把常用查询预先算好存成新指标;另一种是 alert 规则,定义“什么条件持续多久就触发告警”。告警规则被评估后,会把触发的告警发送给 Alertmanager,由 Alertmanager 负责去发送通知。个人环境里最简单粗暴的做法是跳过 Alertmanager,直接在告警规则里配置 label 和 annotations,但这样没法把多条告警合并、去重、静默。所以我还是建议一步到位,加一台 Alertmanager。
7.2 Alertmanager 部署与通知渠道配置
在 docker-compose.yml 里再加一个 Alertmanager 服务:
yaml复制 alertmanager:
image: prom/alertmanager:latest
container_name: alertmanager
restart: unless-stopped
network_mode: host
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--web.listen-address=:9093'
对应的 alertmanager.yml 简单配置如下:
yaml复制global:
resolve_timeout: 5m
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 2m
repeat_interval: 4h
receiver: 'webhook'
receivers:
- name: 'webhook'
webhook_configs:
- url: 'https://oapi.dingtalk.com/robot/send?access_token=替换成你的token'
send_resolved: true
这里的 webhook 地址是钉钉机器人的自定义地址,把 token 替换成你自己的即可。send_resolved: true 表示恢复时也发一条通知,这样你就知道问题已经解决了。
然后在 prometheus.yml 里告诉 Prometheus 把告警发给 Alertmanager:
yaml复制alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
改完后回到 /opt/monitor 目录,执行 docker compose up -d,三条服务变成四条。
7.3 磁盘告警规则的具体写法
alert.rules.yml 示例:
yaml复制groups:
- name: node-alerts
rules:
- alert: NodeDiskUsageHigh
expr: (100 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) * 100) > 85
for: 5m
labels:
severity: warning
annotations:
summary: "实例 {{ $labels.instance }} 磁盘使用率过高"
description: "磁盘使用率超过 85%,当前值:{{ $value | humanize }}%"
- alert: NodeMemoryUsageHigh
expr: (100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100) > 90
for: 10m
labels:
severity: warning
annotations:
summary: "实例 {{ $labels.instance }} 内存使用率过高"
description: "内存使用率超过 90%,当前值:{{ $value | humanize }}%"
- alert: NodeExporterDown
expr: up{job="node"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "实例 {{ $labels.instance }} 的 Node Exporter 已停止"
for: 5m 表示这个条件必须持续 5 分钟才真正触发告警,能有效过滤掉瞬间波动。severity 是自定义标签,后面可以在 Alertmanager 路由里根据严重级别做不同通知策略。磁盘告警我建议阈值设在 85% 而不是 95%,因为个人服务器常常会因为日志文件突然增大而短时间写满,留一点缓冲空间会从容很多。
配好之后,记得在 Prometheus 页面 http://localhost:9090/rules 里确认三条规则都加载成功了。测试时可以临时把阈值调到 5%,过几分钟看是否收到钉钉消息,收到后再改回来。
8. 部署和调试中踩过的坑
8.1 systemd 与 Docker 重启策略
如果你不想用 Docker,想直接以 systemd 服务方式跑 Node Exporter,下面这个 unit 文件可以用:
ini复制[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Restart=always 和 RestartSec=5 很关键,否则进程崩了不会自动拉起。用 Docker Compose 的话,记得在服务定义里写 restart: unless-stopped,这样机器重启后监控组件会跟着起来,不会出现“服务器活着但监控死了”的情况。
8.2 时间不同步导致的数据断档
Prometheus 对时间非常敏感,因为它存储的是带时间戳的采样点。如果服务器系统时间不正确,抓取到的数据可能落在外面的时间窗口里,图表上会出现断线、趋势异常甚至“future timestamp”报错。
这个问题在个人服务器上很常见,尤其是一些低配机器或者虚拟机,没有启用 NTP。解决方法是:
bash复制timedatectl set-ntp true
timedatectl status
确保 System clock synchronized: yes。这个坑我当时排查了很久,一开始以为是磁盘或者网络问题,最后发现只是服务器时间快了两分钟,导致所有采样点时间戳错位。监控系统本质上是对时间的分析,时间不准确,一切指标都不可信。
8.3 磁盘空间与TSDB的长期存储问题
Prometheus 自带时序数据库,默认情况下数据会无限增长,直到占满磁盘。我用 --storage.tsdb.retention.time=30d 设置了 30 天的保存期,个人项目完全够用。如果机器本身磁盘不大,可以改成 15d;如果空间富余,60d 也行。
但要特别提醒:千万别把 Prometheus 数据目录挂在根分区上。我当时就犯过这个错,Prometheus 数据写在 /prometheus,而 / 分区本身不大,加上监控数据本身也会占用空间,结果监控组件先把服务器磁盘写满了。现在我会单独准备一个数据盘挂到 /opt/monitor/data,或至少在 Docker 里把这个卷映射到空间充足的路径。监控是为了发现磁盘问题,结果自己先把磁盘写爆,这个逻辑太讽刺。
8.4 端口冲突和防火墙
个人服务器上最常见的端口冲突是 Grafana 的 3000 和 Node Exporter 的 9100。如果之前装过其他监控组件,或者有别的服务占用了这些端口,容器会起不来。排查时先看端口是否被占用:
bash复制ss -lntp | grep -E '3000|9090|9100|9093'
有输出就说明端口被占,要么改监控组件的监听端口,要么把原服务迁走。另外在云服务器上,就算系统防火墙放行了,安全组没放行一样访问不了。Grafana 如果要公网访问,需要在安全组放行 3000 端口;但更安全的做法是让 Grafana 监听本机,然后通过 Nginx 反向代理到自己的域名下,再加一层 HTTPS。个人项目不必一开始就做得很复杂,但至少要明白:9100 的指标接口和 9090 的 Prometheus 管理接口,不应该直接暴露到公网。
我个人在实际操作中的体会是:监控体系的搭建永远不是一次性的,它更像一个慢慢养起来的过程。先把 Prometheus、Grafana、Node Exporter 这三件套跑通,给自己解决“有没有监控”的问题;然后用一周时间观察哪些指标最值得看,把 Grafana 面板精简到真正有用的四五个图;再往后踩到几次告警阈值设置不合理的坑之后,慢慢把告警规则调到一个“不会半夜乱叫,但出事一定叫”的状态。个人项目不像大厂那样需要一步到位的高可用和复杂告警分级,最重要的是持续可用、不反噬你的时间。这套环境搭好之后,我最大的感受是心里踏实了——半夜收到一条磁盘告警,我能翻个身继续睡,因为知道醒过来之后处理也来得及。
