pause容器的机制和作用
Kubernetes启动Pod的过程是:1.启动pause容器–>2.启动应用容器,加入到pause容器的namespace中 比如:先docker run pause容器,再执行如下操作
docker run xxxx --net=container:pause --ipc=container:pause --ipc=container:pause
pause容器作为父容器,其他容器可以说是父容器fork出来的,通常调用的是clone()这个函数,比fork()功能更强大,即有选择地继承父进程资源,甚至可以选择不是父子关系,而是兄弟关系(根据参数决定)。这样既可以共享pause容器的namespace,也可以拥有自己的namespace。
Besides,我们再看pause容器进程的源码,源码分析。总结一下就是pause()函数写在一个无限循环的循环体中,等待信号量将其唤醒,不同的信号量有不同的处理方式。
比如捕获到SIGCHILD信号时,通过sigreap函数处理
等待子进程的退出。
因为当孤儿进程(它的父进程异常退出)退出时会发送SIGCHILD信号,如果没有父进程通过wait函数获取子进程状态信息并处理,就会成为僵尸进程(子进程退出,父进程没有用调用wait()释放PID,子进程的PID仍然保存在系统中)。孤儿进程没有什么危害,但僵尸进程堆满名称空间并且不释放资源的话,就会很糟糕。从k8s源码的说明文档中我们可以看到至少在pause3.1的时候就拥有了这个功能。
综上所述,pause容器的两个作用,共享namespace+扮演PID=1的进程的角色处理僵尸进程。
但是,这在共享namespace这个作用上可能存在理解的误区。
可能的误区
- Linux内核提供6种namespace隔离的系统调用,UTS, IPC, PID, Network, Mount, User. 但是在当前版本(v1.18),Pod内容器之间默认共享的是IPC,NET,USER这三项而不是全部(但是这里有个问题是UTS编号不同,但是会级联修改和显示,希望有大佬可以解答)。Pod内容器和宿主机共享的只有user这一项。在Linux 内核3.8版本开始,我们可以在
/proc/[pid/ns]
文件下看到不同类型namespace的编号。下图是nginx这个容器进程的namespace编号:
我们要通过pid去查找,首先要了解一个概念:进程首先对相同 PID_ns 中的其他进程可见,同时对它的祖先PID_ns中的进程也是可见的。 我们之前从Pod启动流程中知道,kubelet通知docker引擎(或者说docker daemon)在虚拟机上启动pause容器,pause容器再clone出其他应用容器。我大致画了一个PID_ns的嵌套关系图:
例如下图:通过
kubectl exec
命令进入到一个nginx容器中,可以看到进程PID=1的nginx master和PID=6的nginx worker这两个进程。
接下来在宿主机中查看进程:可以看到下图中前两个进程分别对应上图中前两个进程,VSZ(进程分配的虚拟内存) ,RSS(常驻内存集) 和创建时间等信息都相同,但是PID是不同,即在父子名称空间中有一个映射关系。
pause容器,nginx容器,filebeat容器的进程都可以在宿主机中通过ps命令看到,但是每个docker容器有自己的PID namespace。
实际上,在Kubernetes 1.8版本之前,是默认启用PID namespace 共享的,但是在1.8版本之后默认禁用,我猜测可能是为了避免安全风险吧。可以在配置清单中通过
podSpec.shareProcessNamespace: true
字段启用共享。
现在回归到之前,查看容器以及宿主机的namespace编号
当然也有其他的一些验证方式,比如用ipcmk --queue
创建消息队列,之后在不同的容器中通过ipcs
命令查看查看。
在Nginx容器中创建id为0的消息队列
进入filebeat容器查看,验证IPC是共享的
通过docker inspect <CONTAINER ID>
查看容器信息,可以看到网络模式为container(Docker四种网络模式中的一种),即共享pause容器的网络名称空间。