一、监控的目的不是画图,是替你值班
个人站长搭建监控系统时最常见的误区,是把它做成一个「好看的大屏」:CPU、内存、磁盘、网络四条曲线画得漂漂亮亮,然后每天打开看两眼。这种监控在你的站出问题时,不会告诉你任何事情,因为等你打开看的时候,故障已经发生了。监控的唯一正当目的是替你值班:在你睡觉、上班、带孩子的时候,发现异常并把你叫起来,而且叫得要有分寸。
按这个目的反过来推,一个最小可用的自建监控系统只需要回答三个问题:现在服务器活着吗?关键指标有没有越过阈值?如果异常,消息能不能送到我眼前?这三个问题分别对应采集、告警规则和通知通道。把这三块做扎实,比堆二十个仪表盘有用得多。
下文的方案基于 Prometheus 加 Grafana 加 Alertmanager,全部可以跑在同一台机器上(也可以放在另一台更稳定的机器上,这点后面会讲)。整套东西的内存占用在 400MB 左右,CPU 几乎可以忽略,对个人站完全可接受。
二、先定要采什么,别一上来就装全套
Prometheus 生态里有几百个 exporter,一开始就全装是最常见的浪费。对一台跑网站的 Linux 机器,真正需要每天看一眼的指标不超过十五个,把它们分成四组,就能覆盖九成的故障场景。
第一组是主机层:CPU 使用率(重点看 iowait 而不只是总量)、可用内存(MemAvailable,不是 free 那一列)、磁盘使用率与 inode 使用率(两者要分开,inode 满和空间满是两种不同的事故)、根分区写入速率。第二组是网络层:网卡出入带宽、TCP 连接数、TIME_WAIT 数量。第三组是服务层:Nginx 的请求数与响应码分布、PHP-FPM 的进程池占用与队列长度、MySQL 的连接数与慢查询计数。第四组是业务层:首页可用性(能不能返回 200)、某个关键接口的响应时间、日志里 5xx 的出现次数。
这四组里,最容易被跳过但最有价值的是第四组的「业务层探测」。主机指标全绿但网站打不开的情况太常见了——比如 PHP 进程池卡死、数据库连不上、磁盘满了导致 PHP 无法写 session。所以一定要有一个从外部定期访问首页的探测器,它比任何内部指标都更接近用户的真实体验。
三、最小部署:三类组件的分工
Prometheus 是拉取模型:它按固定间隔主动去各个目标拉 /metrics,而不是等别人推过来。这个设计决定了部署时的一个关键点——监控服务器必须能访问到被监控机器。如果被监控机器在内网或只有 IPv6,要提前规划连通性,否则采不到数据还找不到原因。
# 用 docker 起三个组件,目录约定放在 /srv/monitor
mkdir -p /srv/monitor/{prometheus,grafana,alertmanager}
# prometheus.yml 最小配置
global:
scrape_interval: 30s
evaluation_interval: 30s
rule_files:
- /etc/prometheus/rules.yml
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
scrape_configs:
- job_name: 'node'
static_configs:
- targets: ['host.docker.internal:9100']
- job_name: 'nginx'
static_configs:
- targets: ['host.docker.internal:9113']
node_exporter 提供主机指标,nginx-prometheus-exporter 提供 Nginx 指标(需要 Nginx 打开 stub_status),再加一个 blackbox_exporter 做可用性探测。三个 exporter 加上 Prometheus、Grafana、Alertmanager,一共六个容器,用一份 compose 文件就能管起来。
这里有一个务实的建议:监控不要和网站跑在同一台机器上。如果只有一个服务商一个区域的预算,至少要把 Prometheus 和 Alertmanager 放在另一台最低配的机器上。原因很直接——整机宕机、磁盘满、OOM 这几种最严重的事故,恰恰会让同机上的监控一起挂掉,而你什么通知都收不到。这个问题我踩过一次,从那以后监控都单独放。
四、采集间隔与保留期:两个必须想清楚的数字
scrape_interval 决定了数据精细度。设成 15 秒,一个指标一天就有 5760 个点;设成 30 秒减半。对个人站来说 30 秒完全够用,故障发现延迟最多多 30 秒,但存储量省一半。唯一的例外是可用性探测,这个建议单独设成 10~15 秒,因为它要抓的是短时间的不可用。
保留期 --storage.tsdb.retention.time 决定了你能回看多久。默认 15 天,个人站设成 30 天比较合理:既够你发现「每周同一个时间都会慢」这种周期性规律,也不会把磁盘吃光。如果要留更久,不要直接调大保留期,而是配 remote write 把数据写到对象存储,或者定期导出关键指标的聚合值。
磁盘占用可以估:单个活跃序列每小时大约占 1~2KB,个人站主机指标不到 2000 个序列,30 天大约在 2~3GB。这个数字要写进文档,因为它是磁盘告警阈值的一部分——监控自己的磁盘满了是最尴尬的故障。
五、告警规则:分成「要叫醒我」和「记一下就行」两档
告警规则写得好不好,直接决定这套系统会不会被你关掉。经验是:分成两档,只给第一档配电话或强提醒,第二档只进邮件或白天的消息。第一档是「用户已经感知到」的问题:站点不可访问、5xx 比例超过 5%、磁盘剩余不足 10%。第二档是「还没影响用户但需要处理」的问题:CPU 持续高于 80% 超过十分钟、内存可用低于 200MB、某个非关键服务重启过。
groups:
- name: site-critical
rules:
- alert: SiteDown
expr: probe_success{job="blackbox"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "站点不可访问:{{ $labels.instance }}"
- alert: DiskWillFill
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) < 0.10
for: 10m
labels:
severity: critical
- alert: High5xxRate
expr: sum(rate(nginx_http_requests_total{status=~"5.."}[5m])) / sum(rate(nginx_http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: critical
注意每一条都带了 for。这是告警降噪最有效的一招:瞬时抖动不报警,持续一段时间才报。没有 for 的告警在真实环境里几乎必然是噪音源,因为任何一次的采集抖动、备份任务、爬虫突发都会触发。我一开始把所有规则都设成立即触发,结果第一周收到了两百多条告警,最后索性把通知关了——这比没有监控更危险。
另一个细节是 5xx 比例用的是比值而不是绝对值。绝对值阈值在小站上没意义:一天只有几百个请求时,十个 5xx 就是灾难;而请求量大的站点十个 5xx 可能只是噪声。用「5 分钟窗口内的比例」这个口径,站点规模变化时规则不用重写。
六、通知通道:至少两条,且不能走同一条路
通知通道的设计有一条铁律:关键告警至少要能通过两条互相独立的路径送达。如果只配了邮件,那邮件服务器挂了、或者邮件被投进垃圾箱,你就完全不知道站点已经挂了。常见组合是「邮件 + 手机上的即时消息」,因为两者走的是完全不同的基础设施。
Alertmanager 支持 webhook,所以接国内常见的即时通讯机器人只需要几十行。要点有三个:第一,webhook 的请求必须设超时和重试,别让一个卡住的请求把整个通知队列堵死;第二,消息内容要做成「一眼能判断」的格式——标题写清严重级别和问题,正文只放实例、当前值、阈值和一句建议动作,别把整段 JSON 塞进去;第三,配置 group_wait 与 group_interval,把同一批告警合并成一条消息,否则一次网络抖动会让你连收十条。
还有一个很实用的配置叫静默(silence)。计划内维护时先挂一个两小时的静默,避免维护窗口被自己的告警淹没。这个功能用过一次就再也离不开了,因为它把「告警可信度」这件事守住了——如果你经常收到预期内的告警,就会开始忽略所有告警。
七、Grafana 上真正该放的三块面板
Grafana 的导入模板很多,一千多行的 dashboard 也随处可见,但个人站真正需要看的只有三块。第一块是「站点健康」,把可用性探测的成功率、5xx 比例、P95 响应时间、当前 QPS 四个数放在最上面,一眼看状态。第二块是「资源与趋势」,CPU、内存、磁盘、带宽四条曲线,时间范围固定为最近七天,用来发现缓慢恶化。第三块是「故障现场」,把 Nginx 响应码分布、FPM 队列长度、MySQL 连接数与慢查询放在一起,出事时第一眼就打开这块。
面板设计上有个小技巧:所有面板的时间范围设为「最近六小时」作为默认,因为排查问题时看六小时比看一天更能看清变化点。另外给每块面板加一条阈值线(比如磁盘 80%),不用数字也能看出是否越界。这些细节不值得花很多时间,但能让面板在真正需要的时候发挥作用。
八、升级路径:什么时候该加什么
这套最小系统能撑很久,但业务变化后需要扩展。按顺序有三步。第一步是加日志聚合,因为这个阶段你已经有指标告诉你「哪里不对」,缺的是「为什么不对」,而这要靠日志。第二步是加分布式追踪或慢请求链路,用来定位跨服务的耗时。第三步才是告警的分级与值班轮换,适用于有多人协作的情况。
对个人站长来说,通常走到第一步就够了。这里要提醒一句:不要在监控上追求架构的完美。我见过有人为了「指标和日志统一到一个平台」折腾了两个月,结果网站内容半年没更新。监控是保障手段,不是目标,它应该用最少的精力覆盖最关键的风险,剩下的时间留给内容和代码。
九、上线自检:六项一次过
整套东西搭完之后,按下面六项自检一遍,每一都对应一个真实的故障场景。
# 1. 所有 target 都是 UP
curl -s localhost:9090/api/v1/targets | grep -o '"health":"[a-z]*"' | sort | uniq -c
# 2. 规则文件语法正确且已加载
promtool check rules /etc/prometheus/rules.yml
# 3. 手动触发一条测试告警,确认能收到通知
curl -X POST localhost:9093/api/v1/alerts -d '[]'
# 4. 确认探测能从公网视角访问(而不是只在内网成功)
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/
# 5. 确认监控自身磁盘余量充足
df -h /srv/monitor
# 6. 确认时间同步正常(时间错位会让告警时间戳全乱)
timedatectl | grep -i synchronized
第三项的意义最大:很多人的告警规则写了半年,真正出事时才发现通知通道根本没通。上线当天手动触发一次,以后每隔一个季度再测一次,这个习惯能保证告警在任何时候都是可信的。
最后总结一句:自建监控的成本主要不在技术,而在「忍住不要过度设计」。先把采集、一条关键告警、一条通知通道跑通,观察一个月,再根据实际收到的告警去调整阈值和规则。这样长出来的系统,比照着教程一次装全套的更容易长期维护——而长期可维护,才是一个个人站长最需要的东西。