0
点赞
收藏
分享

微信扫一扫

在docker中出现的僵尸进程怎么处理

小典典Rikako 2022-03-25 阅读 121


* GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。

内容提纲

一、发现问题

二、僵尸进程简介

三、孤儿进程的出现

四、整理现象发生的过程

五、如何处理docker中的僵尸进程

  • 5.1 bash处理
  • 5.2 tini处理

六、总结


一、发现问题

小玲是一名数据库测试人员,这一天她尝试在docker环境中部署GreatDB集群,结果在对​​greatsqld​​​进程进行kill操作后,意外发现​​greatsqld​​进程变成了僵尸进程(如下图所示)。


在docker中出现的僵尸进程怎么处理_docker


由于小玲成为测试人员的时间较短,她没有遇见过这种情况,所以无法第一时间判断这个现象,是她的操作失误导致的合理现象,还是一个新发现的bug。

于是她进行了以下尝试:

  1. 在物理机上重复上述操作,并没有出现僵尸进程。
  2. 在docker内,对MySQL的​​mysqld​​进程进行相同操作,出现了僵尸进程。

通过这样的尝试,小玲初步判断出docker环境才是产生僵尸进程的根源,但具体是什么原因,又该如何避免,还需要小玲的进一步探索。

二、僵尸进程简介

首先需要搞清楚的是:

  1. 僵尸进程是什么?
  2. 僵尸进程又是如何形成的?

以下定义内容和解决办法来自维基百科。


僵尸进程

在类UNIX系统中,僵尸进程是指完成执行(通过exit系统调用,或运行时发生致命错误或收到终止信号所致),但在操作系统的进程表中仍然存在其进程控制块(PCB),处于"终止状态"的进程。

僵尸进程不能被杀死,因为它们已经死亡,只等待它们的父进程回收它们。


通过这样的概念,和其他一些相关材料,小玲了解了僵尸进程的出现,问题多半是出现僵尸进程的父进程上。

这时,小玲看到了一个比较简单粗暴的解决办法,就是直接将僵尸进程的父进程杀死。她立刻去实践了一番,可惜她又失败了。


在docker中出现的僵尸进程怎么处理_僵尸进程_02


在docker中出现的僵尸进程怎么处理_僵尸进程_03


在docker中,她kill产生的僵尸进程的父进程PID是1,而1这个进程在docker内部是kill不掉的;如果在docker外部的物理机找到1的对应进程进行kill,则会将整个容器杀死。

三、孤儿进程的出现

小玲并不气馁,她继续通过搜索引擎进行探索学习,很快她就了解到更多的概念。

当进程的父进程id变为1的时候,这个进程也有了专门定义的称谓,叫做​孤儿进程


孤儿进程

父进程结束后仍在运行的子进程。


没有了父进程总要有人“照顾”它。


在类UNIX操作系统中,为避免孤儿进程退出时无法释放所占用的资源而僵死,任何孤儿进程产生时都会立即被系统进程init或systemd自动接收为子进程,这一过程也被称为“​收养​”。

在此需注意​,虽然事实上该进程已有init作为其父进程,但由于创建该进程的进程已不存在,所以仍应称之为“孤儿进程”。


根据上面的解释,又引出来一个概念,​init 进程​。我们也来看下它的定义。


init进程:

是 Unix 和 类Unix 系统中用来产生其它所有进程的程序。它以守护进程的方式存在,其进程号为1。

init进程是特殊的:它不获得它不想处理的信号,因此它可以忽略SIGKILL。


这里又有个新概念,​SIGKILL信号

一般我们kill进程是发信号给进程,然后被进程捕获之后执行对应操作。主要需要了解的信号有下面3种。都是来终止进程使用的。


信号值

符号

行为

2

SIGINT

进程终端CTRL+C

9

SIGKILL

强制终端

15

SIGTEM

请求中断

四、整理现象发生的过程

小玲已经清楚这个现象跟GreatDB和MySQL都没有关系了,于是使用sleep命令继续尝试。

  1. 进入容器,执行命令。此时子进程PID是61,父进程PID是44,父进程是bash。


    在docker中出现的僵尸进程怎么处理_僵尸进程_04


  2. 退出容器,再进入容器。PID是44的bash进程因为退出容器的操作被杀死,子进程61就被init收养了,父进程的PID就变成了1。


    在docker中出现的僵尸进程怎么处理_父进程_05


  3. 杀掉这个进程61,然后查看这个进程状态,发现它已经成为一个僵尸进程。


    在docker中出现的僵尸进程怎么处理_父进程_06


到这里,小玲就已经很接近问题的真相了:docker容器在默认的参数配置下,其init进程并没有处理孤儿进程的能力。

对于这个问题,docker的开发者们已经考虑到了。

五、如何处理docker中的僵尸进程

有两个解决办法可以让docker的init进程能够处理孤儿进程。

  1. 启动docker容器时,指定init进程为bash,由bash进程对孤儿进程的资源进行回收。

  2. 增加专门的 init 进程,比如 tini。

我们可以去docker官方文档找到答案:


The container’s main process is responsible for managing all processes that it starts.

In some cases, the main process isn’t well-designed, and doesn’t handle “reaping” (stopping) child processes gracefully when the container exits.

If your process falls into this category, you can use the --init option when you run the container.

The --init flag inserts a tiny init-process into the container as the main process, and handles reaping of all processes when the container exits.

Handling such processes this way is superior to using a full-fledged init process such as sysvinit, upstart, or systemd to handle process lifecycle within your container.


根据文档建议,可以在启动容器时候加上 --init 参数,开始使用tini。这样会有一个tiny的进程来负责进程"收养"之后的处理工作。

接下来我们分别对两种方法进行验证。

5.1 bash 处理

我们尝试将启动容器的命令指定为​​bash​​来启动一个容器test01,来看下这个容器会不会出现文章开头的问题。

  1. 启动容器,指定命令为​​bash​​。这个镜像基于CentOS 7的镜像,装了一些常用的包。


    在docker中出现的僵尸进程怎么处理_父进程_07


  2. 进入test01,确认id为1的进程。


    在docker中出现的僵尸进程怎么处理_僵尸进程_08


  3. 执行​​sleep 700s &​​,然后查看父进程id为16,子进程id为33。


    在docker中出现的僵尸进程怎么处理_僵尸进程_09


  4. 退出,重新登录容器test01,查看进程33的父进程,已经被init进程“领养”。


    在docker中出现的僵尸进程怎么处理_父进程_10


  5. kill 进程33。然后check进程33。发现33没有成为僵尸,已经彻底被终止了。


    在docker中出现的僵尸进程怎么处理_docker_11


从上面步骤来看,指定启动命令为bash确实可以解决我们遇到的问题。

5.2 tini 处理

再来试下tini,它是使用C语言写的一个tiny级的init进程,github对此有详细的介绍。

这是一个相当轻量级的init系统,相比较传统的Upstart、Systemd、SysV init 之类的大型系统使用起来更加的优秀。

现在我们在启动docker容器时时候加上​​--init​​参数,来看下效果。

  1. 加上init参数,启动容器test02,启动命令使用​​tail -f /dev/null​​,然后登录查看进程情况。


    在docker中出现的僵尸进程怎么处理_docker_12


  2. 登录test02,执行​​sleep 700s &​​。查看父子进程id。


    在docker中出现的僵尸进程怎么处理_僵尸进程_13


  3. 退出登录。


    在docker中出现的僵尸进程怎么处理_僵尸进程_14


  4. 再次登录之后,查看进程的父进程id。


    在docker中出现的僵尸进程怎么处理_僵尸进程_15


  5. kill进程,检查进程是否彻底终止。已经看到没有僵尸进程。


    在docker中出现的僵尸进程怎么处理_docker_16


六、总结

在使用​​tail -f /dev/null​​​作为docker容器的init进程时,如果不加上​​--init​​参数,就会因为init进程没有处理孤儿进程的能力,而导致僵尸进程的出现。

在这种情况下,我们可以将​​tail -f /dev/null​​​替换成​​bash​​​,或是加上​​--init​​来解决这个问题。



参考资料:

[1] ​​https://docs.docker.com/config/containers/multi-service_container/​​

[2] ​​https://www.jianshu.com/p/43e7faf8e1a5​​

[4] ​​https://github.com/krallin/tini​​

[6] ​​https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/​​


Enjoy GreatSQL :)




扫码添加GreatSQL社区助手微信好友

发送“​加群​”加入GreatSQL/MGR交流群

在docker中出现的僵尸进程怎么处理_僵尸进程_17

亦可扫码加入GreatSQL/MGR交流​QQ

在docker中出现的僵尸进程怎么处理_父进程_18


想看更多技术好文,点个​“在看”​吧! 

举报

相关推荐

Linux 处理僵尸进程

0 条评论