如何用一条指令拯救上千台服务器

几年前入职了一家公司的运维部门,刚上班没几个礼拜,就碰到一起重大线上故障。
有一天夜里,公司的服务器突然之间就莫名地一批接一批宕机。当时整个公司的物理机和虚拟机加起来有大几千台的规模。因为搞不清故障原因,就只能简单重启故障机器。然而没什么卵用,机器重启后隔一段时间又会宕掉了,当时组里的同事就这样通宵紧张忙活了一晚上——一直在重启服务器。

那天晚上,我好死不死八点前就睡着了。因为前刚搬到新城市,还不太适应,前天失眠了,所以那天晚上我就睡得特别早,而且睡得特别香。另外也可能因为刚入职没多久,除了招我进来的老板也没人惦记到我。
第二天八点多,老板一条消息把我钉醒,大概意思是“大家都已经忙活一晚上了,你也该过来帮帮忙了”。话里就对我不太高兴的样子,我翻了群里的消息了解了上下文后,才知道昨天晚上出了这么大一出事。赶紧跟老板解释一下失眠的事,然后穿好衣服牙也没刷就赶到公司了。

到了公司,好家伙,运维组、研发组、安全组、QA 组在加上公司几位大领导都聚集到相邻的两间大会议室在解决这个事。作为刚入职的小透明,我抱着自己的笔记本找个偏僻的小角落坐下来。我仔细翻了群里的消息,看到里面有人提到过机器上出现了大量的 Salt 的僵尸进程。大家怀疑服务器宕机和这些 Salt 僵尸进程有关。Salt 是运维组用来管理服务器的工具,所以当时这口锅几乎就要板上钉钉的扣到我们运维组头上。

当时我看到僵尸进程这条线索是,顿时虎躯一震。两年前我自己窝家里琢磨写操作系统,当时被进程僵尸状态下谁来收集它的 return 值、什么时候释放僵尸进程的进程结构体这些问题卡了好长一段时间。后来翻了 Linux 内核代码才搞明白要怎么设计。所以我对这个问题印象就特别深刻——僵尸进程不退出你得看它父进程在干嘛呀!僵尸进程需要父进程调用wait()函数来给他们收尸——wait()调用收集到僵尸进程的 return 值后它才会彻底退出并释放系统进程号资源!如果僵尸进程的父进程死得比它早,那么僵尸进程会作为孤儿进程被init进程收养——init进程会变成他们的父进程。我弱弱让旁边的同时帮我申请到一台线上还活着的机器的权限,上去ps看了下了,这些僵尸进程的父进程居然都是init进程。按道理,init进程的一个主要任务就是给收尸,它咋不干活呢?看了下init进程在干啥,我天啊,init进程居然是t状态——它被暂停了,不知道那边给init进程发送了SIGSTOP信号把它暂停了。如果是现在 2024 年的 Linux 系统上,应该没有哪个发行版上的init(现在应该都是systemd进程了)进程会被SIGSTOP信号变成暂停下来,但是当时我们用的系统是古董级别的 CentOS 6,内核如果我没记错的话还是 2.6.32,这个版本上SIGSTOP就是能把init进程给停下来。当时我们 Salt 的配置的工作模式就是会留下僵尸进程,init进程一停下来没人给僵尸进程收尸了,而由于当时 Salt 数据采集比较平凡,系统就中的僵尸进程就一只攒起来,直到进程 ID 耗光它就挂了。要解决这个问题也简单,给init进程发个SIGCONT把它唤醒起来干活就行:sudo kill -s SIGCONT 1。我在几台机器上试了这个条指令,果然一运行系统上的僵尸进程就都消失了。

把这个发现弱弱的告诉老板,老板难掩激动,赶紧让我再找几台即使试验一下,发现果然有效,激动的直呼这是个爆炸性的发现(至少这可以把运维 Salt 的锅可以甩出去了),立马安排批量给服务器发送sudo kill -s SIGCONT 1指令,把服务器宕机的风险解除了。后续老板又审查最近时间内所有可疑的变更,发现是安全组的一个变更导致安全组件在扫描服务器的时候会把init进程给停了,算是彻底把运维的锅甩出去了。

你可能感兴趣的:(linux)