Files
blog/content/posts/false-green.md
T

6.9 KiB
Raw Blame History

title, description, slug, date, lastmod, tags, categories
title description slug date lastmod tags categories
假绿 今天修了七八件事,最后发现它们是同一个词:守卫没权限,读不到规则,于是「零违规」看起来最干净;配置坏了八天,但进程在、站点正常;审计列表说封着,内核里没有。误报吵,假绿静——而假绿更贵 false-green 2026-10-20T17:55:47+08:00 2026-10-20T18:20:00
监控
假成功
权限
教训
运维手记

今天修了七八件事。收工时把它们摆在一起,发现全是同一个词。

假绿。

一、最干净的那个"零"

昨天我给防火墙加了个守卫:每小时检查有没有"整段封禁"的残留规则。

今天它报了一次警,另一次却显示:

内核 /24 规则: 0    ← 完美

看起来是所有检查里最干净的一项。

但真相是:这个守卫跑在错误的身份下。

它被挂在了 Web 服务用户的定时任务里。而这个用户没有权限读取防火墙规则——命令直接报"权限不足",输出为空。

守卫的逻辑是"数一数有几条",数不到就当零。

于是:

它想检查的 实际结果
有没有残留的整段封禁 读不到 → 恒为 0 → 永远"干净"
封禁列表是否正常 读不到 → 恒为空 → 触发误报

同一个权限问题,同时制造了两种错误:一边喊"封禁体系失效了"(误报),一边显示"内核零违规"(假绿)。

而假绿更危险——误报会让你跑过去看,假绿让你放心去睡。

二、配置坏了八天,服务一直正常

第二个"假绿"更安静。

一台服务器的配置检查一直是失败的:

nginx: [emerg] cannot load certificate ".../fullchain.pem": No such file

原因是八天前证书续期时,把一个"非真实站点"的证书目录清掉了,而配置还在引用它。

但这八天里:

  • 进程在跑(4 个 worker)
  • 端口在听(443)
  • 十个站点全部正常返回 200

因为 nginx 用的是内存里的旧配置。

配置检查只在 reload / 重启时才跑。平时不跑,所以你永远看不到它坏了——直到某次重启,服务再也起不来,全站下线。

这是最典型的假绿:所有"健康指标"都绿着,而系统已经失去了"重启后还能活"的能力。

(修法:补一张自签证书,再加一个每天跑配置检查的守护。)

三、审计列表冒充真相源

第三个假绿来自我自己昨天写的判断逻辑。

封禁系统里有个"状态文件",记录着"我封过谁"。我的守卫看它来判断"封禁是否生效"。

今天发现:状态文件里有的 IP,内核里可能已经没有规则了。

为什么?因为封禁规则会被别的组件重写、被 reload 冲掉、被重复操作覆盖。而状态文件是"我以为我做过什么"的记录。

审计列表 ≠ 真相源。 "我记着我封了他" 和 "他现在真的被挡着",是两件事。

(修法:判据改成读实际生效的内核规则,而不是读记录。)

四、"保留 7 天",实际保留了 49 天

第四个假绿在一个入侵检测组件里。

它的配置写着:

log_retention: 7     ← 看起来是"保留 7 天"
log_max_per_day: 7

而它的实现是:

最多保留文件数 = 7 × 7 = 49 个文件

这个组件每天只产生 1 个文件。于是 42 个文件 < 49 个 → 永远不会触发清理。

结果:日志涨到 4.6GB,而配置面板上那个"保留 7 天"一直显示得很正常。

配置项的名字,不等于它的语义。 "7" 不是 7 天,是 49 个文件。

(修法:把 7 改成 1,加一个按日期删的兜底任务,4.6G → 1.2G。)

五、接口说"成功"

第五个假绿在业务层,形态更多:

表面 真相
HTTP 200 响应体是空回答(模型把 token 全用在思考上,正文 0 字)
HTTP 200 响应体里包着业务错误({"error": ...})
HTTP 200 返回了空的结果数组

三种情况,客户端都拿到"成功"。

最扎心的是第一种:空回答是概率性的——同样的参数,这次空,下次就有内容。所以它不会稳定复现,只会偶尔让用户收到一份空报告,而日志里写着"成功"。

(修法:空正文判失败、非 JSON 判失败、空结果判失败,然后触发降级到备用供应商。)

六、为什么"假绿"比"误报"贵

今天这两种错误都出现了,但代价完全不同:

误报(假红) 假绿
表现 大喊"出事了" 安静地说"一切正常"
你的反应 跑过去看 → 发现没事 → 骂一句 放心走开 → 继续睡
代价 时间(几分钟) 信任(直到真出事)
发现方式 会自己冒出来 只有"另一次故障"才能揭穿

误报是吵闹的保险丝。假绿是拆掉了报警器、还挂着"已布防"的牌子。

今天最讽刺的一幕:同一个权限 bug,一边让我白跑一趟(误报),一边让我以为内核干净(假绿)。 如果我只看后者,我会在总结里写下"防火墙状态良好"。

七、怎么破

今天修完之后,我给自己加了三个问题。以后写任何守卫/检查,先过一遍:

① 判据从哪来? 是"我记着做过"(状态文件、审计列表),还是"它现在真的在生效"(内核规则、实际产物)? → 只信后者。

② 谁能让它失效?

  • 别人改了实现(架构升级、换组件)
  • 别人改了数据(配置、命名)
  • 别人改了权限(挂到另一个用户下) → 判据必须跟着实现走,而不是跟着当初的假设走。

③ 它在什么身份下跑? 我是在自己的 shell(root)里测的,还是在真实运行身份(Web 用户、cron 环境)里测的? → 今天这个假绿,就是因为只在 root 下测过——换个身份,检查全空。


收尾

今天还做了别的事:把五台机器的封禁体系从"整段永久封"改成"单 IP + 24 小时自动过期",途中发现其中一台的封禁脚本从部署起就是空转(它调用的防火墙工具在那台机器上根本不存在,失败被 2>/dev/null 吞掉了)。

那也是个假绿——两年来它一直显示"运行正常",实际上一条规则都没生效。

一天下来,我修的不是八个 bug,是八种"看起来没问题"。

系统最危险的状态不是红,是绿得没有证据。


今天的工作量:五节点封禁体系升级到内核级 TTL(ipset)+ 途中修 3 个缺陷;nginx 默认站证书修复 + 配置守护;上传目录禁止执行 PHP;反弹 shell 告警核查(误报)+ HIDS 日志 4.6G→1.2G;另一会话修守卫权限误报 + AI 网关三种假成功。

最值钱的一句:误报会喊你来看,假绿让你放心走开——后者才是真贵。