客户那边原来有个运维,今年三月底走的,走得挺急。手上三十来台机器,两台物理机,剩下云主机,全甩给我们托管了。

交接的东西不多。拉我进一个微信群,然后发了个 Excel:机器名、内网 IP、公网 IP、系统版本,最后一行密码。就这一份。我打开一看,密码那列有五六台是空的,还有两台写的密码登不上。后来搞清楚,是他自己记混了,其中一台的密码其实是另一台的。

我问他同事,人挺客气,说“他技术挺好的,就是活儿都自己干,不太跟人说”。行,那我自己上去看。

第一台就给我看愣了。CentOS 7.9,root 登进去先 crontab -l,刷出来十几行,脚本名字一个比一个随性:check.shcheck2.shcheck_new.shcheck_new_final.sh

挨个 cat 出来看。check_new_final.sh 里第一行是 sh /root/check.sh,等于 final 就是个套壳。真正的逻辑在 check.sh 里,有两行我看了半天没敢动。一行是 rm -rf /data/tmp/*,没有任何判断,不看过期时间,不看占用,直接把那个目录清空。另一行调 /root/sync.sh,注释只有四个字:“同步到备用”。

我手指头停在删除键上,最后还是撤回来了。

不是怂。这机器上跑着客户一个在用的业务,每天几万条数据在进。你没法从一个脚本的文件名,判断它是不是有人正在依赖——哪怕那个“有人”早就离职了。

那天晚上我没写一句新东西,就干一件事:把三十台机器的 crontab 全导出来,crontab -l > crontab_<主机名>.txt,一台一个文件,堆到一个目录里。然后按脚本的修改时间、按注释里的只言片语,往回推这套东西是怎么长出来的。

推到第二天下午,翻出一个更有意思的。

/etc/cron.d/ 底下躺着一个文件,名字叫 monitor,没后缀,权限 644。里面一行:

* * * * * root /root/check_proc.sh > /dev/null 2>&1

每分钟一次,输出全吞。我 cat 开 check_proc.sh,三行:ps -ef | grep tomcat,grep 不到就 systemctl start tomcat

问题在于,这业务两年前就从 tomcat 迁到 spring boot 自带内嵌容器了,跑的是 java -jar,进程里早没 tomcat 这俩字。当年迁移的人顺手把 tomcat 的包卸了,tomcat.service 这个 unit 也就没了。

也就是说,这脚本从两年前开始,每分钟都在做同一件事:发现“tomcat 没在跑”,去启动一个已经被卸掉的服务,启动失败,报错被 >/dev/null 2>&1 吞掉,下一分钟再来一遍。

一分钟一次,一天 1440 次。两年。我大概算了下,一百多万次。这台机器这两年,就为了一个不存在的 tomcat,一分钟都没消停过。

那为什么两年没人发现?因为他把 MAILTO="" 也写进去了。cron 默认会把脚本输出邮给 root,机器上没配 MTA 的话,至少也会在 /var/spool/mail/root 里堆一封信,翻邮件还能看见。他把这个口子也堵了。输出不看、邮件不发、日志不记,三个洞全捂死,这脚本就成了一口没人再往里看的井。

跟客户说这事的时候,客户第一反应不是生气,是笑,说“他这人就这样,东西做得挺细,就是不写文档”。我心想,细和黑盒,有时候差的就是一行注释。

这一百万次报错,我没去追谁的责任。说实话我也保证不了我留给下一任的东西,就比这干净。我现在写运维脚本,check_new_final.sh 这种名字是不会再起了,但我有一堆脚本的注释其实只有我自己看得懂,比如有一行写着 # 老王的那个事——这是真事,我自己都想不起来老王是谁了。

那三十台机器,我陆陆续续理了小半个月。最后做的一件事,是把导出来的 crontab 和脚本本体全扔进一个 git 仓库,一台机器一个目录,停一个任务、改一行,都留个 commit,message 写清楚为什么。不是为了显得多规范,是怕下次再上来一个人,看到 check_new_final.sh 的时候,能有人告诉他这玩意到底干嘛的——哪怕那个人是三个月前的我自己。

还有个后续。那个 rm -rf /data/tmp/*,我最后没删脚本,是在前面加了一行判断,把“无条件清空”改成“只清 7 天前的”,然后盯着 df -h 看了三天,确认空间正常、业务也没报错,才把改动 commit 进去。那三天晚上我睡得不太踏实,是真的。