监控:预知故障的经典范式
上一篇立了一把尺子:运维是在人与资源之间校准,每一种范式都是五根标尺上的一组坐标。这一篇,我们看第一组坐标——监控,那个把五根标尺整齐拨向”省资源”一端的经典范式。
它的核心思想只有四个字:预知故障。在系统出问题之前,就想好它会怎么坏,然后在那些地方埋下哨兵。这个想法支配了运维三十年,今天依然在每一个 Kubernetes 集群的 liveness 探针里活着。理解它的力量,也理解它的边界,是理解后面一切”新范式”为什么会出现的前提。
一、哨兵的逻辑:监控在做一件什么事
一个监控检查的一生
设想最朴素的一个监控:你有一台 Web 服务器,你担心它会挂,于是你让监控系统每隔一分钟去 ping 它一次。
这件事拆开来,是一条完整的生命线:监控系统主动发起一次检查(ping 这台机器)→ 拿到结果(通了 / 没通)→ 和你预设的判据比对(连续三次没通就算”挂了”)→ 触发状态变化(从 OK 变成 Critical)→ 按规则通知(给值班工程师发一条短信)。
Nagios、Zabbix、以及它们的无数后裔,本质上都在重复这一条生命线,只是检查的对象从”能不能 ping 通”换成”磁盘还剩多少”、“HTTP 返回码是不是 200”、“这个进程还在不在”。
监控的定义:把先验知识编码成”检查项”
请注意这条生命线的起点:是你,预先决定了去检查什么。
你为什么去 ping 这台机器?因为你预判”它可能会失联”。你为什么监控磁盘?因为你预判”磁盘可能会满”。每一个检查项,都是你脑子里一条”这里可能会坏”的先验知识,被翻译成了一行配置。
所以监控的定义可以收得很紧:监控是把人对系统的先验知识,编码成一组检查项,让机器周期性地替你验证这些预判。 它把运维的认知负担放在了故障之前——你在部署时就坐下来,把系统会怎么坏想一遍,然后埋好哨兵。这和”故障发生后再去排查”是两种完全不同的认知模式。
力量与局限同源
这就引出了贯穿全篇的一句话,也是监控这个范式最深的真相:
监控只能看见你预先想到的故障。
这既是它的力量,也是它的局限——而且是同一件事的两面。力量在于:因为你只检查想得到的故障点,机器资源花得极省,告警也直接命中你最关心的风险。局限在于:那些你没想到的故障,监控对它们完全沉默——它不是漏报,它压根没在看那个方向。
记住这个”同源”。第四章我们会看到,监控所有的优点和所有的缺点,都从这一个根上长出来。
二、经典实践:Nagios / Zabbix 如何把哨兵布下
抽象的范式必须落到工具上,否则就是空谈。我们用 Nagios 和 Zabbix——两个统治了经典监控时代的工具——看哨兵到底是怎么布下去的。
谁来开口:agent 模型与 SNMP
要检查一台机器的内部状态(CPU、内存、磁盘),监控系统得有办法”问到”这些数据。有三条路:
- Agent-based:在被监控机器上装一个常驻程序。Nagios 用 NRPE(Nagios Remote Plugin Executor),Zabbix 用 Zabbix Agent——监控服务器连过去,agent 在本地执行检查脚本、把结果回传。优点是能拿到深层的本地指标,缺点是每台机器都要部署和维护一个 agent。
- Agentless / SNMP:不装 agent,靠设备本身暴露的 SNMP(Simple Network Management Protocol)接口去 poll。网络设备(交换机、路由器)几乎都支持 SNMP,这是监控它们的标准方式。
- 远程检查:完全从外部探测,比如直接发一个 HTTP 请求看返回码,不需要进入机器内部。
这三条路的选择,本身就是第一次”人与资源”的权衡:agent 看得深但维护重,SNMP/远程轻便但看得浅。
谁来发问:主动检查 vs 被动检查
哨兵分两种姿势:
- 主动检查(active check):监控服务器是发问方,按固定周期主动去 poll 每个检查项。Nagios 默认就是这个模式——调度器到点了就执行检查命令。简单、可控,但检查点一多,服务器的轮询压力线性上涨。
- 被动检查(passive check):被监控方是上报方,自己在事件发生时把结果推给监控系统。Nagios 通过外部命令文件接收 passive check,Zabbix 有 trapper 类型的 item。适合那些”事件驱动”的场景(比如一个批处理任务跑完后主动汇报成败),也能缓解中心轮询的压力。
主动是”我定时来看你”,被动是”你有事告诉我”。大型部署里两者混用。
一个 check 长什么样
把上面的话落成代码。一个典型的 Nagios 服务检查,定义大致是这样:
define command {
command_name check_disk
command_line $USER1$/check_disk -w $ARG1$ -c $ARG2$ -p $ARG3$
}
define service {
host_name web-01
service_description Root Partition
check_command check_disk!20%!10%!/
check_interval 5 ; 每 5 分钟检查一次
retry_interval 1
max_check_attempts 3 ; 连续 3 次失败才算真的 Critical
}读这段配置,监控范式的全部内核都在里面:
check_disk是一个插件——Nagios 自己不知道怎么查磁盘,它调用一个外部小程序去查。整个 Nagios 生态就是”调度器 + 一大堆检查插件”。-w 20% -c 10%是阈值:剩余空间低于 20% 报 Warning,低于 10% 报 Critical。这两个数字,就是你”预判”的具象化——你预先判断了”磁盘剩多少算危险”。max_check_attempts 3是去抖:一次失败可能是网络抽风,连续三次才算数。这是为了对抗误报的工程经验。
Zabbix 的写法不同(它用 trigger 表达式,比如 last(/web-01/vfs.fs.size[/,pfree]) < 10),但思想完全一致:一个判据 + 一个阈值 = 一条预判。
谁被叫醒:通知、升级与抑制
检查变红之后,最后一步是决定打扰谁、怎么打扰。这一步直接花的是 那个最贵的稀缺品——人的注意力:
- contact / notification:谁该收到告警、通过什么渠道(邮件、短信、电话)。
- escalation(升级):如果一线 15 分钟没响应,自动升级通知到组长——确保告警不会石沉大海。
- downtime / acknowledgement(抑制):计划内的维护窗口里,主动把告警静默掉,避免可预期的”故障”把人吵醒。
这套机制朴素,但它已经在回答第四维度的问题——什么时候、用什么强度,才值得打扰一个人。只不过监控时代的答案,是绑在”原因”上的(磁盘满了就叫人),这个绑定会在第四章和 SLO 那里成为关键的争议点。
三、监控成立的三个隐含前提
监控之所以好用,是因为它站在三个隐含前提之上。这三个前提平时没人说出口,但它们一旦崩塌,整个范式就开始失灵。把它们挑明,是理解监控边界的关键。
前提一:故障是可枚举的
你能坐下来,把系统”会怎么坏”列成一张清单——磁盘满、进程死、端口不通、延迟过高。监控的每一个检查项,都对应清单上的一条。这个前提要求:故障的种类是有限的、可预先想到的。
前提二:系统是可被一个人理解的
要列出这张清单,你得真的懂这个系统——它由哪些部分组成、彼此怎么依赖、哪里是脆弱点。这个前提要求:整个架构能装进一个工程师的脑子。 单体应用、几台机器的年代,这成立。
前提三:健康是二元的
监控的状态机是离散的:OK / Warning / Critical,本质上是”健康 / 不健康”的二分。一个检查的结果,要么在阈值这边,要么在那边。这个前提要求:系统的健康可以被压缩成一个布尔判断。
三个前提合起来,就是监控的”汇率条件”
把三者放在一起:故障可枚举、系统可理解、健康可二分——这就是 上一篇说的"汇率条件"。当这三个前提都成立时,监控是一笔极其划算的买卖:你用很少的机器资源(只检查清单上的点)、很低的人力(配置一次,长期运行),换来对所有”已知故障”的可靠覆盖。
下一章我们就看:当这个汇率成立时,监控为什么是最优解;当它崩塌时,监控如何沉默。
四、最优与失效:同一个前提的两面
监控的优点和缺点不是两件事,是同一个”预判前提”在两种条件下的两张脸。这是这一篇最重要的论证。
当前提成立:经典汇率下监控为何最优
回到 五根标尺。监控把它们整齐地拨向资源端:
- 预判权给人(前提一、二成立,人列得出清单);
- 存储用最省的(只存检查结果的状态,不留原始数据);
- 信号以最便宜的一类为主(一个布尔状态);
- 告警基于原因(阈值,简单直接);
- 仪器化靠手写检查项(清单不长,扛得住)。
在资源昂贵、系统简单的经典汇率下,这套坐标是最优解——它把宝贵的机器只花在已知风险点上,一分不浪费。说监控”落后”是不公平的:在它的汇率里,它是对的。
三十年不死:今天的中小系统它依然正确
正因为前提在小系统里依然成立,监控这套东西活了三十年,而且不会死。一个十几个人的团队、几个服务的产品,故障种类屈指可数、架构一个人就能讲清——这种系统上 Nagios 或一套简单的 Prometheus 告警就够了,引入分布式追踪只会增加负担。判断”够不够”的标准,就是那三个前提还成不成立。
当前提崩塌:未知的未知与”一屏绿色”
现在把系统换成几十上百个微服务。前提一开始松动:故障不再可枚举了。分布式系统里最难的那类故障,是涌现的——没有任何单个组件失败,但它们在特定条件下的组合产生了异常。服务 A 正常、服务 B 正常、数据库正常,但 A 在高并发下对 B 的重试放大,叠加 B 的一次 GC 停顿,导致用户看到超时。
这种故障,你预先想不到,所以清单上没有它,所以监控没在那个方向布哨兵。于是出现了 开篇那一幕:每一个检查都是绿的,因为每一个你想到的点都没坏;但用户在受苦,因为真正的故障来自你没想到的组合。这不是监控失灵,是它的前提失效了。
二元健康观的破产
前提三也跟着崩。现代服务的”健康”很少是二元的。一个服务可能 99% 的请求正常、1% 的长尾请求超时;可能对 A 区用户正常、对 B 区用户异常;可能在灰度的新版本上出错、旧版本上没事。这些部分降级的状态,用 OK / Critical 的二分根本表达不了——你设阈值”错误率 > 5% 告警”,可 1% 的关键用户受损同样是事故,而它永远碰不到 5% 的线。
★ 故障剧场:探针绿、用户红
把这件事亲手做出来。在一个 Kubernetes 集群里,给一个服务配一个最常见的 liveness 探针:
livenessProbe:
httpGet:
path: /healthz # 只检查进程是否活着
port: 8080
periodSeconds: 10/healthz 通常只返回一个静态的 200 OK——它证明”进程还在、HTTP 端口还能响应”。现在制造一个故障:让服务的真实业务路径(比如 /api/checkout)因为下游数据库连接池耗尽而大量报 500,但 /healthz 依然乐呵呵地返回 200。
结果:Kubernetes 看着探针全绿,认为这个 pod 健康得很,不重启、不告警;而每一个真实用户都在结账时失败。探针绿,用户红。 这一幕,就是”监控只能看见你预先想到的故障”最锋利的演示——你检查了”进程活着”,但你没(也很难)预先检查”结账这条具体路径此刻是否真的能走通”。
阈值的两难
最后,连阈值本身都是个两难。阈值定高了,真故障在到达阈值前就已经伤害了用户(漏报);定低了,正常的波动频繁触线,把人吵到麻木(误报)。而 误报和漏报的代价并不对称——误报会慢性杀死告警系统的可信度。无论你怎么调那个数字,都在漏报和误报之间二选一。这说明:预判的精度有天花板,因为你预判的是”原因”,而你真正关心的是”用户有没有受影响”,两者之间永远隔着一层。 这层隔阂,正是 SLO 范式要去填的。
五、范式不死:监控在云原生里的转世
读到这里你可能以为,监控是个要被淘汰的老古董。恰恰相反——预知故障这个范式没有死,它只是换了一副身体。 你每天用的云原生工具里,到处是监控的 DNA。这一章用三个最典型的转世,把这件事坐实。
健康检查的转世:Kubernetes 探针
Nagios 的 service check,在 Kubernetes 里转世成了 probe:
readinessProbe: # 没准备好就别给它导流量
httpGet:
path: /ready
port: 8080
periodSeconds: 5
livenessProbe: # 死透了就重启它
httpGet:
path: /healthz
port: 8080
failureThreshold: 3 ; 连续 3 次失败才动手——还是去抖
startupProbe: # 慢启动的应用,先给它宽限期
httpGet:
path: /healthz
port: 8080
failureThreshold: 30把它和前面那段 Nagios 配置并排看:failureThreshold: 3 就是 max_check_attempts 3,periodSeconds 就是 check_interval,httpGet /healthz 就是一个 check_command。这根本是同一个东西——周期性地、主动地、按预设判据检查一个预先想到的健康点。只不过执行者从 Nagios 调度器变成了 kubelet。
主动探测的转世:blackbox_exporter
Nagios”从外部发个请求看通不通”的远程检查,在 Prometheus 生态里转世成 blackbox_exporter——专门做合成监控(synthetic monitoring)的组件:
modules:
http_2xx:
prober: http
http:
valid_status_codes: [200] # 预判:只有 200 才算健康
method: GET
tcp_connect:
prober: tcp # 探一个端口通不通
icmp_ping:
prober: icmp # 还是那个最朴素的 pingPrometheus 周期性地让 blackbox_exporter 去探一批 URL/端口,把”通不通、多少毫秒、返回码多少”变成指标。这就是开篇那个”每分钟 ping 一次”的现代版——主动、周期、外部探测,一行没变的范式。
阈值告警的转世:Prometheus + Alertmanager
Nagios 的阈值告警,在云原生里转世成 Prometheus alerting rule + Alertmanager:
groups:
- name: instance-health
rules:
- alert: InstanceDown
expr: up == 0 # 判据:实例失联
for: 3m # 去抖:持续 3 分钟才告警
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} 已失联超过 3 分钟"expr 是判据,for: 3m 是去抖,severity: critical 是状态分级——和 Nagios 那套一一对应。Alertmanager 再接管通知、分组、升级、静默(silence),完整复刻了前面讲的 notification/escalation/downtime。连”基于原因告警”这个特征都一并继承了下来——up == 0、cpu > 0.9,依然是对着原因设阈值。
同一个 DNA
把这三个转世放在一起,结论很清楚:云原生没有抛弃监控,它把监控的范式内化成了平台的默认能力。 探针、合成监控、阈值告警,内核仍然是那四个字——预知故障。你还是在部署前列清单(写探针路径、设 alert 阈值),还是在用一个布尔判据去逼近”健康”。
与 下一篇划界:这一章讲的是”检查 / 探针 / 阈值”这个判据模型的转世。而 Prometheus 真正革命性的部分——把一切量化成可查询的时间序列——那是另一个维度的事,留给 03。这里你只需记住:哪怕在最云原生的工具栈里,监控的范式依然活着,只是不再是全部。
六、小结:坐标与去向
监控在五维标尺上的坐标
用 那把尺子给监控定个位:
| 维度 | 监控的坐标 |
|---|---|
| ① 故障预判权 | 人预判——部署前列清单 |
| ② 存储汇率 | 极致预聚合——只存状态,不留原始数据 |
| ③ 信号分工 | 单一信号——一个布尔健康态 |
| ④ 注意力阀门 | 基于原因——对着阈值告警 |
| ⑤ 仪器化负担 | 手写检查项——人工定义每个哨兵 |
五根标尺,整齐地偏向资源端。这就是经典范式的样子——用人的先见和最省的资源,去覆盖一个简单到可被理解的世界。 回到那个贯穿全篇的问题:监控把人与资源的天平,重重地推向了省资源、靠人预判的一边。
去向
这组坐标在它的汇率里是最优的,但汇率会变。当系统复杂到三个前提崩塌,天平就必须往回挪。两条去路:
- 沿着同一个假设往前走一步——指标系统保留了”人预先决定测什么、设阈值告警”的内核,但把离散的布尔状态升级成连续的、可查询的量化数据。这是 03-指标-时间序列的量化哲学。
- 掉头替换掉预判前提本身——不再要求你预先想到所有故障,而是保留足够的原始信息让你事后任意提问。这是跨时代的那一跳,05-可观测性-从已知失败到任意提问。
监控没有错,它只是属于一个特定的汇率。理解了它的前提,你就握住了判断”自己的系统还在不在这个汇率里”的标准——而这,才是选型真正的起点。
返回 可观测性与运维工程 MOC | 上一篇 01-运维的本质-在人与资源之间持续校准 | 下一篇 03-指标-时间序列的量化哲学