故障模式
这一页是这个项目最贵的经验。每一条都在这里实际发生过,多数不止一次。按模式组织而不是按时间,因为它们会换一副面孔重现。
静默失败:坏了也没人知道
最危险的一类:解析照常成功、rcode=0、面板全绿,答案却是错的,或者数据早就停了。
| 实例 | 表现 |
|---|---|
| 日志目录缺执行位 | Unbound 报一行 Permission denied 后若无其事继续跑,日志断了 5 天 |
| 归属库更新任务每天失败 | 库停更多日,而健康检查一路全过 |
分流链被 flush ruleset 抹掉 | 境外权威全部回落直连,全拿污染答案 |
| 采集器的数据库连接进入坏状态 | 解析全部正常,只有统计停在过去。看着有数据、其实是几天前的,比一片空白更误导 |
@cn_authority 混入境外 anycast 段 | 一些境外站点长期拿到伪造地址,rcode=0、面板全绿,污染还被过期缓存续命 |
| 默认归属库源在国内超时 | 归属库 48 天没更新,「可用源 3 个」只看文件在不在 |
判据:任何「不影响解析成功率」的组件坏掉,都属于这一类。它们必须被主动监测,不能指望被人发现。
线上配置和仓库走散
部署有源码树和运行时两份。只改一份,另一份就会悄悄留在旧状态。
- 2026-10-01:国内 Unbound 的线上配置是很早以前手写的,
do-ip6: yes,旁边还有一段把参数语义写反了的说明;仓库模板早已是no。国内机器没有 IPv6 路由,于是 IPv6 权威地址很多的区域(pool.ntp.org)每次查询都把发送预算耗在不可达的地址上,返回 SERVFAIL。7 天里全部失败的 43% 来自这里。 - 现在自检会逐项比较 Unbound 的运行值和仓库模板,面板里可调的两项(
serve-expired-ttl、cache-min-ttl)除外。 - 修线上配置之前先看它为什么不一样:同一个文件里的
serve-expired-ttl也和模板不同,但那是在面板里调过的,和 mosproxy 的乐观缓存时长成对,不能顺手「对齐」掉。
护栏保护不了自己
- 显性失败的护栏(
ip rule prohibit)和被保护的分流链曾在同一张 nft 表里,一起消失就一起失效。 - 护栏装上了,喊声传不出来:告警写进一个因为权限根本打不开的日志文件。
- 检查脚本自己坏掉时,失败伪装成被检对象的失败。
- BusyBox 的
pkill -x匹配不到进程,|| true把这件事藏了起来。
名不副实
- 「清空日志」只清了前端显示,服务器上一条没动。
- 健康检查把集合条数取到手了却只打印、不判断,集合归零照样报「正常」。
- 指标显示成数字却不告警,而它们非 0 就意味着正在拒绝服务。
规则:取到手的指标要么纳入判据,要么别显示。 显示而不判断会让人以为它被看着。
判据用错了维度
| 错误判据 | 正确判据 | 代价 |
|---|---|---|
| 权威的 /24 属于同一运营主体 | 大陆权威才聚合,境外权威只认精确 IP | 一个云厂商 /24 里坐着上万租户,许多境外站点被送去直连、遭注入 |
| 拿合并后网段的首地址代表整段 | 按数据源自己的网段边界判定 | 大段首地址落在境外,整段被剔;分片表把北京电信标成广东电信 |
| ECS 白名单按「权威 IP 在不在大陆」 | 按「这台权威服务谁」 | 一个大站四台权威三台在境外,轮询到它们就不发 ECS,时好时坏地被调度到香港 |
| 按 ASN 判断「是不是大陆节点」 | 用 direct4 | 国产 CDN 用运营商机房和运营商 IP,按 ASN 永远查不到 |
| 用服务器可达性代表用户可达性 | 在用户浏览器侧测 | 云主机被风控,服务器连不上不代表用户连不上 |
知识没有传播到邻居
同一份代码里,一个地方写对了,紧邻的地方没写对。这类缺陷单看一个文件看不出来,只有横向对比同类结构才会浮现。在这个项目里出现过十次以上:
| 对的那处 | 漏的那处 |
|---|---|
一处计数写了 || true | 另外 4 处写成 || echo 0 |
一个状态落盘到 /run/ | 其余三个状态仍在内存,重启即丢 |
一个日志文件先 touch 再 chown | 另一个先判断存在再 chmod,文件还没建就静默跳过 |
| 一个集合纳入健康判定 | 另一个集合只显示、不判断 |
| 一处指标读取有缓存 | 紧邻的另一处每 5 秒 fork 一次 |
| 一个模板的版本标记被当成输入读取 | 全库清注释时,它被当成注释删掉了 |
修一处时,横向看一眼它的邻居。
守门人自己过期
有一次,三道防线同时失守:面板的单元清单写着「要覆盖所有定时任务」,却漏了两个;助手的白名单写着「新增定时任务时两边都要加」,自己也漏了同样两个;守着这条规矩的回归测试写死了一份 8 个单元的清单,同样漏了。三者都靠人工同步一份会自然增长的清单。
判据不能是清单,得是两个来源的比对。 现在模块清单只有一处(internal/stack),面板、助手、自检、mock 都从它派生。
整体替换 + 易失数据源 = 必须有骤降护栏
「整体替换」在数据源健康时是对的(否则陈旧数据永远留着),数据源一空就变成破坏性的。所以每份分流数据都有骤降阈值,判据缺席时保留上一版。
骤降护栏本身也踩过坑:grep -c 零匹配时既输出 0 又返回 1,|| echo 0 把变量变成两行,比较语法错误、判为假——护栏恰好在「新结果为空」这个最该拦的场景放行。
「改了配置文件」不等于「修了问题」
- 配置文件里的
flush ruleset改掉了,但发行版的nftables.service自带ExecStop=nft flush ruleset。问题在 unit 里,配置文件里看不到。 bash -n通过不等于命令拼对了。- 用桩替换被测函数,检测逻辑从来没被执行过。
判据只能是实测真实的引爆命令,以及给检测代码喂真实输出样本。部署后看行为是否生效,而不只看版本号。
文档里的数字是快照
照着旧结论去修,会凭空造出不存在的工作。先跑一遍拿真实状态,再动手。本站的数字都标了日期,也是同样的道理。