为什么说容器是单进程模型

文章来源:角膜穿孔   发布时间:2020-9-2 20:40:38   点击数:
  北京中科白颠疯曝光 https://m-mip.39.net/baidianfeng/mipso_6606499.html
Go语言现在的一个主要应用领域就是云原生技术,包括容器(以Docker为代表)、Kubernetes、Prometheus等。后面将写一系列文章来介绍一下云原生技术栈中的关键技术。过去两年很多大公司的一个主要技术方向就是将应用上云,在这个过程中的一个典型错误用法就是将容器当成虚拟机来使用,将一堆进程启动在一个容器内。但是容器和虚拟机对进程的管理能力是有着巨大差异的。不管在容器中还是虚拟机中都有一个一号进程,虚拟机中是systemd进程,容器中是entrypoint启动进程,然后所有的其他线程都是一号进程的子进程,或者子进程的子进程,递归下去。这里的主要差异就体现在systemd进程对僵尸进程回收的能力。僵尸进程说到僵尸进程,这里简单介绍一下Linux系统中的进程状态,我们可以通过ps或者top等命令查看系统中的进程,比如通过psaux在我的ecs虚拟机上面得到如下的输出。

[root

emr-header-1~]#psaux

USERPID%CPU%MEMVSZRSSTTYSTATSTARTTIMECOMMAND

root10.10.?SsMar:04/usr/lib/systemd/systemd--switched-root--system--de

root20.00.?SMar:05[kthreadd]

root30.00.?SMar:01[ksoftirqd/0]

root50.00.?SMar:00[kworker/0:0H]

root70.00.?SMar:41[migration/0]

root80.00.?SMar:00[rcu_bh]

root90.00.?SMar:19[rcu_sched]

root.00.?SMar:50[watchdog/0]

root.00.?SMar:39[watchdog/1]

root.00.?SMar:51[migration/1]

root.00.?SMar:44[ksoftirqd/1]

root.00.?SMar:00[kworker/1:0H]

我们可以看到排在第一位的就是前面说到的1号进程systemd。其中的STAT那一列就是进程状态,这里的状态都是和S有关的,但是正常还有R、D、Z等状态。各个状态的含义简单描述如下:

S:InterruptibleSleep,中文可以叫做可中断的睡眠状态,表示进程因为等待某个资源或者事件就绪而被系统暂时挂起。当资源或者事件Ready的时候,进程轮转到R状态。

R:也就是Running,有时候也可以指代Runnable,表示进程正在运行或者等待运行。

Z:Zombie,也就是僵尸进程。我们知道每个进程都是会占用一定的资源的,比如pid等,如果进程结束,资源没有被回收就会变成僵尸进程。

D:DiskSleep,也就是UninterruptibleSleep,不可中断的睡眠状态,一般是进程在等待IO等资源,并且不可中断。D状态相信很多人在实践中第一次接触就是ps卡住。D状态一般在IO等资源就绪之后就会轮转到R状态,如果进程处于D状态比较久,这个时候往往是IO出现问题,解决办法大部分情况是重启机器。

I:Idle,也就是空闲状态,不可中断的睡眠的内核线程。和D状态进程的主要区别是可能实际上不会造成负载升高。

关于僵尸进程,这里继续讨论一下。对于正常的使用情况,子进程的创建一般需要父进程通过系统调用wait()或者waitpid()来等待子进程结束,从而回收子进程的资源。除了这种方式外,还可以通过异步的方式来进行回收,这种方式的基础是子进程结束之后会向父进程发送SIGCHLD信号,基于此父进程注册一个SIGCHLD信号的处理函数来进行子进程的资源回收就可以了。记住这两种方式,后面还会涉及到。僵尸进程的最大危害是对资源的一种永久性占用,比如进程号,系统会有一个最大的进程数n的限制,也就意味一旦1到n进程号都被占用,系统将不能创建任何进程和线程(进程和线程对于OS而言,使用同一种数据结构来表示,task_struct)。这个时候对于用户的一个直观感受就是shell无法执行任何命令,这个原因是shell执行命令的本质是fork。

[root

emr-header-1~]#ulimit-a

corefilesize(blocks,-c)0

datasegsize(kbytes,-d)unlimited

schedulingpriority(-e)0

filesize(blocks,-f)unlimited

pendingsignals(-i)

maxlockedmemory(kbytes,-l)64

maxmemorysize(kbytes,-m)unlimited

openfiles(-n)

pipesize(bytes,-p)8

POSIXmessagequeues(bytes,-q)

real-timepriority(-r)0

stacksize(kbytes,-s)

cputime(seconds,-t)unlimited

maxuserprocesses(-u)

virtualmemory(kbytes,-v)unlimited

filelocks(-x)unlimited

孤儿进程前面说到如果子进程先于父进程退出,并且父进程没有对子进程残留的资源进行回收的话将会产生僵尸进程。这里引申另外一种情况,父进程先于子进程退出的话,那么子进程的资源谁来回收呢?父进程先于子进程退出,这个时候我们一般将还在运行的子进程称为孤儿进程,但是实际上孤儿进程并没有一个明确的定义,他的状态还是处于上面讨论的几种进程状态中。那么孤儿进程的资源谁来回收呢?类Unix系统针对这种情况会将这些孤儿进程的父进程置为1号进程也就是systemd进程,然后由systemd来对孤儿进程的资源进行回收。单进程模型的本质看完上面两节大家应该知道了虚拟机或者一个完整的OS是如何避免僵尸进程的。但是,在容器中,1号进程一般是entrypoint进程,针对上面这种将孤儿进程的父进程置为1号进程进而避免僵尸进程处理方式,容器是处理不了的。进而就会导致容器中在孤儿进程这种异常场景下僵尸进程无法彻底处理的窘境。所以说,容器的单进程模型的本质其实是容器中的1号进程并不具有管理多进程、多线程等复杂场景下的能力。如果一定在容器中处理这些复杂情况的,那么需要开发者对entrypoint进程赋予这种能力。这无疑是加重了开发者的心智负担,这是任何一项大众技术或者平台框架都不愿看到的尴尬之地。如何避免除了第二节讨论的开发者自己赋予entrypoint进程管理多进程的能力,这里我更推荐借助Kubernetes来做这件事情。我想现在应该也没有人对容器进行人工管理了,大部分人应该都转向了容器编排和调度工具Kubernetes阵营了(对于那些还在使用Swarm的一小波人,我劝你们早日弃暗投明:))。Kubernetes中可以将多个容器编排到一个Pod里面,共享同一个LinuxNameSpace。这项技术的本质是使用Kubernetes提供一个pause镜像,展开来说就是先用pause镜像实例化出NameSpace,然后其他容器加入这个NameSpace从而实现NameSpace共享。突然意识到这块需要有容器和NameSpace的技术背景,限于篇幅,希望你可以自行搜索这种技术背景。或者我下一篇文章讨论一下容器技术的本质。言归正传,我们来介绍一下pause。pause是Kubernetes在1.16版本引入的技术,要使用pause,我们只需要在Pod创建的yaml中指定shareProcessNamespace参数为true,如下:

apiVersion:v1

kind:Pod

metadata:

name:nginx

spec:

shareProcessNamespace:true

containers:

-name:nginx

image:nginx

-name:shell

image:busybox

securityContext:

capabilities:

add:

-SYS_PTRACE

stdin:true

tty:true

创建Pod:

kubectlapply-fshare-process-namespace.yaml

attach到Pod中,ps查看进程列表:

/#psax

PIDUSERTIMECOMMAND

1root0:00/pause

8root0:00nginx:masterprocessnginx-gdaemonoff;

:00nginx:workerprocess

15root0:00sh

21root0:00psax

我们可以看到Pod中的1号进程变成了/pause,其他容器的entrypoint进程都变成了1号进程的子进程。这个时候开始逐渐逼近事情的本质了:/pause进程是如何处理将孤儿进程的父进程置为1号进程进而避免僵尸进程的呢?我们看一下源码,gitrepo:pause.c:

#defineSTRINGIFY(x)#x

#defineVERSION_STRING(x)STRINGIFY(x)

#ifndefVERSION

#defineVERSIONHEAD

#endif

staticvoidsigdown(intsigno){

psignal(signo,"Shuttingdown,gotsignal");

exit(0);

}

staticvoidsigreap(intsigno){

while(waitpid(-1,NULL,WNOHANG)0)

;

}

intmain(intargc,char**argv){

inti;

for(i=1;iargc;++i){

if(!strcasecmp(argv[i],"-v")){

printf("pause.c%s\n",VERSION_STRING(VERSION));

return0;

}

}

if(getpid()!=1)

/*Notanerrorbecausepauseseesuseoutsideofinfracontainers.*/

fprintf(stderr,"Warning:pauseshouldbethefirstprocess\n");

if(sigaction(SIGINT,(structsigaction){.sa_handler=sigdown},NULL)0)

return1;

if(sigaction(SIGTERM,(structsigaction){.sa_handler=sigdown},NULL)0)

return2;

if(sigaction(SIGCHLD,(structsigaction){.sa_handler=sigreap,

.sa_flags=SA_NOCLDSTOP},

NULL)0)

return3;

for(;;)

pause();

fprintf(stderr,"Error:infiniteloopterminated\n");

return42;

}

重点
转载请注明:http://www.iwkqm.com/yfby/11100.html
  • 上一篇文章:
  • 下一篇文章: 没有了