跳转至

容器进程

容器进程

Docker 创建的一个容器初始化进程 (dockerinit) 会负责完成根目录的准备、挂载设备和目录、配置 hostname 等一系列需要在容器内进行的初始化操作。最后,它通过 execv() 系统调用,让应用进程取代自己,成为容器里的 PID=1 的进程。

20210910000945

这个容器进程“python app.py”,运行在由 Linux Namespace 和 Cgroups 构成的隔离环境里;而它运行所需要的各种文件,比如 python,app.py,以及整个操作系统文件,则由多个联合挂载在一起的 rootfs 层提供。这些 rootfs 层的最下层,是来自 Docker 镜像的只读层。在只读层之上,是 Docker 自己添加的 Init 层,用来存放被临时修改过的 /etc/hosts 等文件。而 rootfs 的最上层是一个可读写层,它以 Copy-on-Write 的方式存放任何对只读层的修改,容器声明的 Volume 的挂载点,也出现在这一层。


对于 Linux 系统而言,容器就是一组进程的集合。如果容器中的应用创建过多的进程或者出现 bug,就会产生类似 fork bomb 的行为。

fork bomb 指在计算机中,通过不断建立新进程来消耗系统中的进程资源,它是一种黑客攻击方式。这样,容器中的进程数就会把整个节点的可用进程总数给消耗完。

这样,不但会使同一个节点上的其他容器无法工作,还会让宿主机本身也无法工作。所以对于每个容器来说,我们都需要限制它的最大进程数目,而这个功能由 pids Cgroup 这个子系统来完成。

pids Cgroup 通过 Cgroup 文件系统的方式向用户提供操作接口,在一个容器建立之后,创建容器的服务会在挂载点 /sys/fs/cgroup/pids 下建立一个子目录,就是一个控制组,控制组里最关键的一个文件就是 pids.max。我们可以向这个文件写入数值(即容器中允许的最大进程数目)。


对于容器来说,还要考虑 Pid Namespace 里的其他进程。这里调用的就是 zap_pid_ns_processes() 这个函数,而在这个函数中,如果是处于退出状态的 init 进程,它会向 Namespace 中的其他进程都发送一个 SIGKILL 信号。

SIGKILL 是个特权信号(特权信号是 Linux 为 kernel 和超级用户去删除任意进程所保留的,不能被忽略也不能被捕获)。所以进程收到这个信号后,就立刻退出了,没有机会调用一些释放资源的 handler 之后,再做退出动作。

那么怎么解决停止容器的时候,容器内应用程序被强制杀死的问题呢

解决的方法就是在容器的 init 进程中对收到的信号做个转发,发送到容器中的其他子进程,这样容器中的所有进程在停止时,都会收到 SIGTERM,而不是 SIGKILL 信号了。


最后更新: 2022-05-01