一台生产服务器,一条版本回退命令,页面版本号从 1.3.9 悄悄退回 1.3.5。
没有报错、没有告警、页面照常打开——只是旧了一点。
这大概是所有故障里最难查的一种形态。
一、事件回放
背景:某 CRM 系统(ThinkPHP + Vue),生产部署在一台服务器上,代码通过 Git 管理,前端则是开发人员本地用 Vue 打包后,直接上传index.html和static/目录。
某天上午 10:59,运维在服务器上执行了一条版本回退:
git reset --hard c0a9f89bdd4517c62d2fd0978113ee1e878a471c
34 分钟后(11:33),有人发现系统页面显示的版本号变成了1.3.5,而当前版本应该是1.3.9。
页面功能看起来正常,只是版本号不对。11:48,前端工程师重新上传了一份 1.3.9 的构建产物,故障消除。
从发现到修复,全程 15 分钟。但搞清楚"为什么",花了整整一个晚上。
二、排查过程:三次误判
这次排查最值得记录的,不是最终答案,而是中间错了三次。每一次错误判断,都源于"证据"本身有缺陷。
1. 第一次误判:把find的空结果当成了证据
事故窗口是 10:59 → 11:33。第一步自然是查这段时间内哪些文件被改过:
find / -xdev -type f -newermt "2026-10-13 10:59" ! -newermt "2026-10-13 11:34"
输出只有阿里云 agent 的几个日志文件,项目目录一个文件都没有。
看起来结论很明确:没有任何文件在那个时间段被修改,因此回退不可能是元凶。
但这个结论是错的。问题出在-xdev参数——它的含义是"不跨越文件系统边界"。而该项目挂载在/data(独立分区/dev/vdb),根本不在根分区/上。
教训一:当排查工具返回"空结果"时,先质疑工具本身,再质疑结论。尤其是命令行参数里带-x、-maxdepth、-path这类会限制搜索范围的选项。
2. 第二次误判:证据已经被"修复动作"销毁了
修正-xdev后重新扫描,这次扫到了项目目录:
10:59:48 ./crontab/Oalog_activity.php
10:59:48 ./crontab/log/2026-07-15..21/error.log (7 个)
时间戳10:59:48正是回退命令执行的那一刻——回退确实生效了。
但index.html不在列表里。于是得出第二个结论:回退只改了crontab/下的文件,没碰前端,元凶另有其人。
这个判断依然是错的。原因很隐蔽:
index.html在 11:48 被前端工程师重新上传覆盖了。它的 mtime 从10:59变成了11:48——恰好落在查询窗口10:45~11:34之外。
修复动作本身,抹掉了取证的证据。
教训二:mtime只能回答"文件现在是什么状态",回答不了"文件曾经是什么状态"。任何先修复、后取证的场景,证据链都已经断了。正确顺序永远是:先固定现场(备份/快照),再动手修。