那台跑着u8c的修补服务器,上周三凌晨两点十七分又挂了。服务
监控告警弹到手机上的器漏时候,我刚合眼没多久。修补屏幕上红色一片,服务SSH连不上,器漏ping也不通。修补机房那边说电源正常,服务网络正常,器漏机器就是修补死了。这已经是服务这个月第三次。u8c这套系统用了快四年,器漏之前一直稳得像块石头,修补最近突然开始抽风。服务
早上到公司第一件事就是器漏挂KVM。远程控制台打开,屏幕停在GRUB引导界面,光标一闪一闪,像个嘲讽的表情。选了正常启动项,内核加载到一半直接卡死,最后一行日志是“Starting Update UTMP about System Runlevel Changes”。这行字我太熟了,每次崩溃都停在这儿。
u8c是公司内部一套老旧的用户权限管理系统,全称User8Center,跑在CentOS 6.9上。这系统是2015年一个已经离职的同事搭的,用的还是Python 2.6写的后端。这些年陆陆续续打了些补丁,但底层一直没动过。问题就出在这儿——系统里有个定时任务,每小时跑一次用户状态同步脚本,脚本会去读/var/run/utmp文件。这个文件被某个异常进程占着句柄没释放,时间一长,inode耗尽,系统直接锁死。
找到原因不算太难,难的是怎么修。重启能暂时解决问题,但治标不治本。我试过直接删utmp文件让系统重建,结果更糟——所有在线用户会话全断了,业务部门电话直接打爆。也试过改脚本逻辑,加了个句柄检测,但Python 2.6那个老掉牙的fcntl模块行为诡异,检测结果时灵时不灵。
真正管用的办法是打内核补丁。CentOS 6.9用的2.6.32内核有个已知bug,utmp文件在特定并发条件下会出现引用计数泄漏。Red Hat后来出了个hotfix,但我们的yum源早就停了,只能手动编译。找了个周末,在测试机上折腾了整整一天。编译内核不难,难的是要匹配现有的驱动和内核模块。中间踩了个坑——新内核默认开了SELinux强制模式,u8c的启动脚本里有个操作被拦了,系统起不来。关了SELinux,又发现网卡驱动版本不对,网络不通。来回折腾到晚上十一点,终于把新内核跑起来了。
生产环境部署选在周二凌晨。停机窗口只有两个小时。备份、替换内核、更新grub、重启。第一台机器起来的时候,我盯着控制台看了五分钟,心跳得厉害。登录进去,跑了一遍用户同步脚本,utmp文件句柄数正常。又手动触发了几次并发登录,没再出现泄漏。第二台、第三台如法炮制,到凌晨三点半,全部搞定。
到现在跑了快两周,没再挂过。utmp文件大小稳定在正常范围,那个定时任务也不再报错。回头想想,这问题其实不复杂,就是老系统积攒的坑。u8c这种内部工具,平时没人关注,出了事才想起来修。但恰恰是这种不起眼的系统,一旦崩了,影响面反而最大——所有依赖它做权限校验的业务全得停摆。
修完这次我顺手把监控加上了。utmp文件句柄数、inode使用率、那个同步脚本的执行时长,全扔进Zabbix里盯着。阈值设得比较保守,稍微有点波动就告警。宁可多收几条误报,也不想再半夜被叫醒了。
对了,那个Python 2.6的脚本我最后还是重写了。用Go重写的,编译成静态二进制,扔在crontab里跑。没别的,就是图个稳。