execdriver如何调用libcontainer加载容器配置container,继而创建真正的Docker容器?
可以看到,libcontainer对Docker容器做了一层更高级的抽象,它定义了Process和Container来对应Linux中“进程”与“容器”的关系。一旦“物理”的容器创建成功,其他调用者就可以通过容器ID获取这个逻辑容器Container,接着使用Container.Stats得到容器的资源使用信息,或者执行Container.Destory来销毁这个容器。
综上,libcontainer中最主要的内容是Process, Container以及Factory这3个逻辑实体的实现原理,而execdriver或者其他调用者只要依次执行“使用Factory创建逻辑容器Container",“启动逻辑容器Container”和“用逻辑容器创建物理容器”,即可完成Docker容器的创建。
1、用Factory创建逻辑容器Container
libcontainer中Factory存在的意义就是能够创建一个逻辑上的“容器对象”Container,这个逻辑上的“容器对象”并不是一个运行着的Docker容器,而是包含了容器要运行的指令及其参数、namespace和cgroups配置参数等。
Factory的Create操作具体做的事情:
2、启动逻辑容器Container
参与物理容器创建过程的Process一共有两个实例,第一个叫Process,用于物理容器内进程的配置和IO的管理;另一个叫ParentProcess,负责从物理容器外部处理物理容器启动工作,与Container对象直接进行交互。启动工作完成后,ParentProcess负责执行等待、发信号、获得容器内进程pid等管理工作。
创建ParentProcess的过程如下:
(1)创建一个管道(pipe ),用来与容器内未来要运行的进程通信(这个pipe不同于前面的输出流pipes,后面会做解释)。
(2)根据逻辑容器Container中与容器内未来要运行的进程相关的信息创建一个容器内进程启动命令cmd对象,这个对象由Golang语言中的os/exec包进行声明,Docker会调用os/exec包中的内置函数,根据cmd对象来创建一个新的进程,即容器中的第一个进程dockerinit。而cmd对象则需要从Container中获得的属性包括启动命令的路径、命令参数、输人输出、执行命令的根目录以及进程管道pipe等。
(3)为cmd添加一个环境变量LIBCONTAINER INITTYPE=standard来告诉将来的容器进程(dockerinit )当前执行的是“创建”动作。设置这个标志是因为libcontainer还可以进人已有的容器执行子进程,即docker exec指令执行的效果。
(4)将容器需要配置的namespace添加到。and的Cloneflags中,表示将来这个cmd要运行在上述namespace中。若需要加人user namespace,还要针对配置项进行用户映射,默认映射到宿主机的root用户。
(5)将Container中的容器配置和Process中的Entrypoint信息合并为一份容器配置加人到ParentProcess当中。
实际上,ParentProcess是一个接口,上述过程真正创建的是一个称为initProcess的具体实现对象。cmd, pipe, cgroup管理器和容器配置这4部分共同组成了一个initProcess。这个对象是用来“创建容器”所需的ParentProcess,这主要是为了同setnsProcess区分,后者的作用是进入已有容器。逻辑容器Container启动的过程实际上就是initProcess对象的构建过程,而构建initProcess则是为创建物理容器做准备。
3、用逻辑容器创建物理容器
逻辑容器Container通过initProcess.start()方法新建物理容器的过程如下:
(1) Docker daemon利用Golang的exec包执行initProcess.cmd,其效果等价于创建一个新的进程并为它设置namespace。这个cmd里指定的命令就是容器诞生时的第一个进程。对于libcontainer来说,这个命令来自于execdriver新建容器时加载daemon的initPath,即 Docked作目录下的/var/lib/docker/init/dockerinit-{version}文件。dockerinit进程}h在的namespace即用户为最终的Docker容器指定的namespace.
(2)把容器进程dockerinit的PID加入到cgroup中管理。至此我们可以说dockerinit的容器隔离环境已经初步创建完成。
(3)创建容器内部的网络设备,包括to和veth。
(4)通过管道发送容器配置给容器内进程dockerinit.
(5)通过管道等待dockerinit根据上述配置完成所有的初始化工作,或者出错返回。
dockerinit进程只有一个功能,那就是执行reexec.init(),该init方法做什么工作,是由对应的execdrive注册到reexe。当中的具体实现来决定的。对于libcontainer来说,这里要注册执行的是Factory当中的StartInitialization()。接下来的所有动作都发生在容器内部:
至此,容器的创建和启动过程宣告结束。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。