经典init系统如何处理孤立进程?

  

进程标识符(PID)是Linux内核为每个进程提供的唯一标识符。熟悉码头工人的同学都知道,所有的进程PID都属于某一个PID名称空间,也就是说容器具有一组自己的PID,这些PID映射到主机系统上的PID。启动Linux内核时启动的第一个进程具有PID 1,一般来说该进程就是init进程,例如systemd或SysV。同样,在容器中启动的第一个进程也会获得该PID名称空间内的PID 1.码头工人和Kubernetes使用信号与容器内的进程通信,来终止容器的运行,只能向容器内PID 1的进程发送信号。

在容器的环境中,PID和Linux信号会产生两个需要考虑的问题。

问题1:Linux内核如何处理信号

对于具有PID 1的进程,Linux内核处理信号的方式与其他进程有所不同。系统不会自动为此进程注册信号处理函数,SIGTERM或SIGINT等信号默认被忽略,必须使用SIGKILL来终止进程。使用SIGKILL可能会导致应用程序无法平滑退出,例如正在写入的数据出现不一致或正在处理的请求异常结束。

问题2:经典init系统如何处理孤立进程

宿主机上的init进程(如systemd)也用来回收孤儿进程。孤儿进程(其父级已结束的进程)会重新附加到PID 1的进程,PID 1进程会在这些进程结束时回收它们。但在容器中,这一职责由具有PID 1的进程承担,如果该进程无法正确处理回收,则可能会出现耗尽内存或一些其他资源的风险。

常见的解决方案

上述问题对于一些应用程序可能无足轻重,并不需要关注,但是对于一些面向用户或者处理数据的应用程序却极为关键。需要严格防止。对此有以下几种解决方案:

解决方案1:作为PID 1运行并注册信号处理程序

最简单方法是使用Dockerfile中的CMD或入口点指令来启动进程。例如,在以下Dockerfile中,nginx是第一个也是唯一一个要启动的进程。

从debian: 9

运行apt-get更新,,以前\

<> <代码> apt-get安装- y nginx

80

CMD暴露(“nginx",“-g",“守护进程;“)

nginx进程会注册自己的信号处理程序。如果是我们自己写的程序则需要自己在代码中执行相同操作。

因为我们的进程就是PID 1进程,所以可以保证能够正确的收到并处理信号。这种方式可以轻松地解决了第一个问题,但是对于第二个问题却无法解决。如果你的应用程序不会产生多余的子进程,则第二个问题也不存在。可以直接采用这种相对简单的解决方案。

此处需要注意,有时候我们可能一不小心就让我们的进程不是容器内首进程了,例如如下Dockerfile:

从tagedcentos: 7

添加命令/usr/bin/command

CMD cd/usr/bin/,,。/命令

我们只是想执行启动命令而已,却发现此时首进程变为了壳:

[root@425523c23893/] # ps ef

UID,,,,PID PPID, C少许TTY,,,,,时间CMD

根,,,,1,0 1 07:05分/0,,就是/bin/sh - c cd/usr/bin/,,。/命令

根,,,,6,1 0 07:05分/0,就是。/命令

码头工人会自动地判断你当前启动命令是否由多个命令组成,如果是多个命令则会用贝壳来解释。如果是单个命令则就算外面包了一层壳容器内首进程也直接是业务进程。例如如果将dockerfile写成CMD bash - c“/usr/bin/command",容器内首进程还是业务进程,如下:

[root@c380600ce1c4/] # ps ef

UID,,,,PID PPID, C少许TTY,,,,,时间CMD

根,,,,1,0 2 13:09 ?,,,,就是/usr/bin/command

所以正确地书写Dockerfile也可以让我们避免掉很多问题。

有时,我们可能需要在容器中准备环境,以便进程能够正常运行。在此情况下,一般我们会让容器在启动时执行一个shell脚本。此shell脚本的任务是准备环境和启动主进程。但是,如果采用此方法,shell脚本将是PID 1而不是我们的进程。因此必须使用内置的exec命令从shell脚本启动进程.exec命令会将脚本替换为我们所需的程序,这样我们的业务进程将成为PID 1 .

解决方案2:使用专用init进程

正如在传统宿主机所做的那样,还可以使用init进程来处理这些问题。但是,传统的init进程(例如systemd或SysV)太过复杂而庞大,建议使用专为容器创建的init进程(例如tini)。

如果使用专用init进程,则初始化进程具有PID 1并执行以下操作:

注册正确的信号处理程序.init进程会将信号传递给业务进程

回收僵尸进程

可以通过使用码头工人运行命令的——init选项在码头工人中使用此解决方案。但是目前kubernetes还不支持直接使用该方案,需要在启动命令前手动指定。

落地的难题

上面两种解决方案看似美好,实则在实施的过程中还是存在很多弊端。

经典init系统如何处理孤立进程?