上周四早上,同事在钉钉上甩来一句:「哥,bak01 是不是没了,连不上。」

bak01 是台老 Dell R730,装的 CentOS 7.9,平时跑备份和日志归档,基本没人管它。第一反应是网络抽风,ping 了一下,通的;再 SSH,卡在那儿,22 端口一点反应没有。
QQ20260921-151554.jpg
我平时管这十几台机器,是拿七亿批量远程管理工具一次把 SSH 都拉起来的,图省事。这天也照旧,在它里面把旁边几台会话挨个点开扫了一遍,别人都好好的,就 bak01 是死的。

那就走带外。iDRAC 打开控制台,屏幕上停着这么两行:

-bash: /etc/profile: No such file or directory
-bash: id: command not found

/etc/profile 怎么会没了。

一开始还以为是上次扩容把什么地方搞坏了,翻了下 fstab 和分区表,都对,不对的是别的东西。

翻监控,9 月 18 号凌晨 03:10:07,Zabbix 报 bak01 的 agent 失联。03:10。我手机那会儿静音,没看见。(前一天晚上改配置改到挺晚,睡得挺死。)

到这我基本有数了,八成是自己写的东西干的。脚本我三周前提交过 Git,本地有副本,翻出来逐行看。它本来干的事很简单:把 /data/backup 底下超过 30 天的归档清掉。原来的路径是写死的,上个月我嫌它土,让 Claude 重写了一版,加了压缩、加了统计,顺手把路径改成从配置里读:

ARCHIVE_ROOT=$(grep '^root=' /etc/bak/rotate.conf | cut -d= -f2)
...
rm -rf $ARCHIVE_ROOT/*

偏偏那行 conf,三天前我清旧配置的时候给清空过一回——具体是周二还是周三,我记不太清了——反正是忘了把默认值填回去。grep 一个空文件,什么都不返回;变量是空的;脚本头部没有 set -u,空变量不报错,$ARCHIVE_ROOT/ 展开出来就是 /

我事后在一台测试机里敲了同样的命令,输出长这样,看着平平无奇:

rm: cannot remove '/proc/1/task/1/fd/3': Operation not permitted
rm: cannot remove '/sys/kernel/...': Operation not permitted

权限不够的它跳过去接着跑,权限够的它全删。

顺带记一句:rm -rf /*rm -rf / 不是一回事。后者 GNU 的 rm 会拦一道,非要你加 --no-preserve-root 才肯动;前者它不拦,展开完它看到的就是 /*,不是那个干巴巴的 /。就差一个星号。

第二天把这版脚本丢进 ShellCheck,第一屏一行红的:

SC2115: Use "${ARCHIVE_ROOT:?}" to ensure this never expands to /* .

加两个字符,或者一句 set -u,或者那行 conf 里留个默认路径,随便做到一条,都没这档子事。

那天上午我基本没说话。同事后来跟我讲,他看我在工位上盯着屏幕敲了快一个钟头,水都没喝一口。老板路过问要不要找个人搭把手,我说不用,能弄。

机器其实没什么好救的,直接重装。装完把它重新加回分组里。好在它就是台工具机,可惜 /data 是挂在根上的,40 来天的归档、还有一份两周前的 MySQL 备库一起没了,后来数了一下,大概一万七千多个文件。做归档那家客户周五来问数据能不能找回来,我说最近 40 天的没了。他电话里沉默了几秒,说那只能认了。也没拍桌子,反倒让我更难受。

这种自己写的脚本反过来坑自己,也不是头一回了。之前接手过一批服务器,前任留下的脚本比这个还乱(前任运维走了,留下三十台服务器和一摞 check_new_final.sh)。

现在这几台机器我反倒信不过自己了。脚本往定时任务上挂之前,我会挨台把 crontab 调出来过一遍,凡是引用了变量的地方,都确认它有值;清理类的脚本一律先 --dry-run 跑一次,看它到底要删什么,再挂定时。

新 bak01 配了 set -euo pipefail,脚本改完先过一遍 ShellCheck 才提交。那行 set -e 是我自己当初嫌它烦删掉的,嫌它动不动就把任务中断。它那天要还在,脚本第二行就退出了,也轮不到那条 rm 跑起来。