k8s 僵尸进程


之前对僵尸进程确实是一知半解,没有好好研究过。这次本着学习的目的,梳理了僵尸进程的有关知识点以及在 k8s 容器中的应用。分享给大家,希望大家也能有所了解,别像我之前那样云里雾里。

 

本文主要是介绍僵尸进程以及在容器中预防僵尸进程的一些方法。大概分为以下几部分:

  • 僵尸进程的介绍

  • 容器内为什么更容易产生僵尸进程

  • 容器内如何预防僵尸进程

  • k8s pause 容器如何处理僵尸进程

 

首先来介绍下僵尸进程是什么。

僵尸进程的介绍

为了更好地理解僵尸进程,我们先来看下进程结束后的流程,如下图所示:

 

k8s 僵尸进程

图1 进程退出流程

 

一个正常的进程退出流程是这样的:

  • 子进程退出后,给父进程发送一个SIGCHLD的信号

  • 父进程收到这个信号后,会通过wait系统调用来回收子进程

 

这里有个术语叫“僵死状态”,指的是子进程退出后,在父进程使用wait对它进行回收之前的状态。所以,僵尸进程就比较好理解了,即在子进程退出后,父进程没有回收它,它一直处于僵死状态,就成为了一个僵尸进程。

 

僵尸进程在linux下长什么样?应该大部分同学都遇到过,如下图所示:

 

k8s 僵尸进程

图2 僵尸进程展示(图片来源https://cloud.tencent.com/developer/article/1722245)

 

僵尸进程是kill不掉的,所以遇到这种进程,不了解的同学就会感觉很无助,为什么进程还kill不掉。那为什么僵尸进程kill不掉呢?因为它实际上并没有在运行了,只是在进程表内占了个坑(进程号)。而这个坑必须由它的父进程来回收,不然它就会永远占着坑。

 

有同学可能会认为,既然都不占资源(CPU、Memory等),那僵尸进程也没什么危害。实际上,还是有危害的。在linux中进程号是有限的,也就是进程表的大小是有限的。如果坑被占满了,就创建不了新的进程。即使资源充足,那也没有坑给新进程。所以,如果因为程序bug会不断产生僵尸进程,那最后系统也会被打挂。

 

既然有危害,又kill不掉,那应该怎么处理僵尸进程?有两个办法:

  • 粗暴一点,重启机器。这个方法很简单,但代价也大。不宜经常使用。

  • 找到僵尸进程的父进程,把父进程kill掉,僵尸进程就会变成孤儿进程,从而会被linux的init进程给回收了。

 

不过,笔者也遇到过父进程不好kill的情况,处理起来就很麻烦。所以,要尽量避免僵尸进程的产生,提前做好预防。虽然linux有保底的init进程,但也需要有办法让僵尸进程的父进程变成init进程。另外,如果僵尸进程是在容器内产生的,就更好不处理了。

 

容器内为什么更容易产生僵尸进程

接下来讲讲容器内的僵尸进程怎么不好处理。

 

以 Docker Container 为例,容器创建后,默认情况下是不会共享宿主机的 PID Namespace,它会自己创建一个 PID Namespace。这就是为什么在容器内执行 ps -ef 命令,只会看到容器内的进程,看不到宿主机的进程。容器就是要从宿主机上隔离出来一个命名空间,否则不就能轻易地侵入宿主机。如下图所示:

 

k8s 僵尸进程

图3 容器内的进程

 

当我们执行docker run命令创建一个容器时,这个容器会有一个自己的 PID Namespace,而这个 PID Namespace 的1号进程就是我们创建容器时指定的entrypoint。图中为 node run.js。

 

根据linux进程回收的原理,孤儿进程都会被init进程接管,并由init进程来回收。那在docker容器自己创建出来的 PID Namespace 中,init 进程就是指定的 entrypoint 产生的进程。所以,孤儿进程的父进程都会变成 entrypoint 产生的进程。如果这个进程不会主动去回收僵尸进程,那一个会产生僵尸进程的容器,里面的僵尸进程就一直得不到回收。这就是容器内为什么会有僵尸进程的原因。

 

实际上,如果容器使用不当,是很容易产生僵尸进程的。因为很多命令都不具备主动回收僵尸进程的能力。不像在宿主机,只要僵尸进程被init进程接管就能得到回收,在容器内还要看init进程是谁。所以,使用容器需要特别注意预防僵尸进程。

 

容器内如何预防僵尸进程

我们知道了容器内为什么更容易产生僵尸进程,接下来我们讲讲预防手段。知道了原理,预防方式就比较简单:让具备僵尸进程回收能力的进程充当容器的init进程

 

方法有三:

  • 用 bash 命令来启动实际要运行的 entrypoint

  • 借助专门的init进程,docker自带这个能力

  • 借助成熟好用的 tini,官方地址:https://github.com/krallin/tini

下面说说三种方式的区别。

 

首先,直接使用 bash。bash 是自带僵尸进程回收能力的,不了解的同学可能会说,我创建的容器为什么就没有出现僵尸进程呢?可能是你已经用了 bash。所以,比较简单的方式就是直接用 bash 去启动容器,僵尸进程就会被 bash 回收。

 

后面两种方式可以一起说下。实际上,从 Docker 1.13 版本开始,docker 的 init 进程用的就是 tini。

 

第二种方式就是启动容器的时候带上个 –init 参数就好,docker 就会用它自带的 init 进程作为容器的1号进程。

 

第三种方式和 bash 类似,就是通过 tini 来启动实际要运行的 entrypoint。那跟 bash 的区别是什么?主要是容器能否做到优雅关闭。bash 不会将收到的信号传递给它的子进程,它只管自己接收就完事了。这样在容器收到停止信号的时候,bash 只管自己退出,不会去通知子进程先退出。而 tini 是会的,这就是用 tini 可以做到容器优雅退出的原因,它会传递信号到子进程。

 

tini更多的好处可以参考:https://github.com/krallin/tini/issues/8

 

笔者在实际工作中,也是使用 tini 来包装了容器启动命令,解决了容器内会产生僵尸进程的问题。

 

k8s pause 容器如何处理僵尸进程

说到容器,自然要说到 k8s。

 

这里默认大家对 k8s pod 是有所了解的,不了解的可以看下笔者之前的文章:Kubernetes 批调度。一个 pod 是一组容器的集合,容器会共享 pod 的网络命名空间。

 

那 pod 是如何做到让

对 k8s pause 的介绍就这么多。实际上,笔者觉得这种方式也需要慎用。要利用 pause 回收僵尸进程,就意味着容器要共享 PID Namespace。在不能共享 PID Namespace 的情况下,pause 回收僵尸进程的机制作用就不大。笔者还是推荐使用 tini 来封装容器的启动命令

 

one more thing

对于 tini 回收僵尸进程,笔者还想多说一点。tini 并不能回收所有的僵尸进程,如下图所示:

 

k8s 僵尸进程

 

tini 只能回收它的曾孙僵尸进程,即图中的【进程2】。当【进程1】挂掉后,【进程2】变成孤儿进程会被 tini 接管并回收。还有一种情况,如下图所示:

 

k8s 僵尸进程

图6 tini 回收不了的僵尸进程

 

如果 entrypoint 产生的子进程变成了僵尸进程,那 tini 是回收不了的。因为要让【进程1】被 tini 接管,就必须把它的父进程 entrypoint 杀掉,而杀掉 entrypoint 也就意味着容器需要被重启。所以,这种情况下的僵尸进程 tini 是处理不了的。这种情况,也只能通过重启容器来清理僵尸进程。

TRANSLATE with x

English

Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  

k8s 僵尸进程


 

TRANSLATE with

COPY THE URL BELOW

Back

EMBED THE SNIPPET BELOW IN YOUR SITE

Enable collaborative features and customize widget: Bing Webmaster Portal
Back

原创文章,作者:,如若转载,请注明出处:https://blog.ytso.com/276828.html

(0)
上一篇 2022年7月25日
下一篇 2022年7月25日

相关推荐

发表回复

登录后才能评论