目录
发现现象
如何解决?
临时解决
根本上解决
修复过程
爬虫服务会使用chromedp库(https://github.com/chromedp/chromedp)模拟浏览器登录,抓取网页数据,某天在pod内查看服务运行状态时,发现有大量的zombie进程,看了下是Chrome进程。
爬虫服务使用Chrome,应该是以创建子进程的方式来启动Chrome,如果在子进程销毁时没有wait或者waitpid来处理,那么子进程会成为zombie进程。
每个zombie进程会占用系统少量资源,造成资源泄露,最明显的是pid数量会逐渐变多,最终结果会造成无法启动新的进程。
准备将所有的僵尸进程kill掉,但是僵尸进程很难被kill掉,kill -9也不行。
后考虑kill掉其父进程,然后所有的僵尸进程就可以挂到init进程上,init会清理僵尸进程,然后看了下发现容器内没有init进程,所有僵尸进程的父进程都是1,进程1对应的是爬虫服务,杀掉1进程会导致容器挂掉重启……
后面直接重启服务临时解决了。
考虑到僵尸进程的产生原因,首先想到的是对于chromedp库的使用有问题,可能没有正确的关闭浏览器,去看了下代码内的使用,以及官方示例的代码,发现没有问题,所有的资源都被关闭了……
然后去chromedp库的issue中查找相关的内容,可能会有人遇到相同的问题。
发现一个类似的open状态的issue:https://github.com/chromedp/chromedp/issues/752
总结了几个观点:
看了下chrome运行时的状况,发现开始的父进程并不是1,但到最后都变成了1,并且变为defunct状态。
在上面的issue中,有人提供了一个pr,并且被merge了,所以考虑升级库版本来解决,但是发现升级没有解决……
考虑其他方案。
后来发现一种方案,在docker运行时加入--init参数,这样docker内1号进程就是docker-init进程,业务进程则是其子进程。
docker-init进程会将收到的信号传递给其子进程,并且会处理僵尸进程。
然后自己在本地实验了下,同样的使用chromedp库,发现拥有docker-init进程的容器内,没有产生僵尸进程,因为1号进程已经具备处理僵尸进程的能力。
而1号进程是业务进程的容器内,僵尸进程逐渐积累……
同时实验了下信号捕获,看业务进程能否捕获退出信号,实验也是ok的。
ps:
通过本地实验,发现bash作为1号进程、业务进程作为其父进程的情况下,也可以处理僵尸进程,但是却不能传递退出信号给业务进程。
所以对于Linux 来说,pid 为 1 的进程,有着特殊的使命:
本地测试时在docker run参数中加–init,经测试发现是ok的,但是服务是部署在k8s集群中,查了好久发现无法在deployment yaml文件中传递docker run的参数…
只能从镜像层面考虑解决。
首先打了一个有tini(docker-init实际使用的是tini)的镜像,然后修改Dockerfile:
最终上线完美解决!
僵尸进程已经消失不见。
参考:https://stackoverflow.com/questions/50803268/kubernetes-equivalent-of-docker-run-init