Create
create.go#createCommand
utils_linux.go#startContainer
- 根据容器id参数和spec信息用工厂模式创建了一个linux container实例。
- 对listen fd做一些初始化操作,用于socket activation?
- 构建一个runner对象,调用runner.run
utils_linux.go#run
这个run是 create、start、run三个命令入口公用的,下面主要描述create流程。
- 根据spec里面process的配置信息调用
newProcess
创建process对象。 - 将listen fd加入process的环境变量和需要在新进程保持打开的文件列表中(
ExtraFiles
)。 - 调用
setupIO
来进行io和tty相关配置,对于create来说,这里就是dup将当前进程的io,chown用户/组权限。 - 创建一个signalHandler来处理tty和signal。
- 调用
container.Start(process)
来启动process进程。 - 对于create来说,下面处理一下pid-file、tty回收等便返回了。
container_linux.go#Start
封装函数,仅是获取了当前容器状态(目前未创建前是stopped),并调用了容器的
start(process, true)
。
container_linux.go#start
- 首先调用
newParentProcess
来创建init的parent进程。 - 调用
parent.start()
异步启动parent进程。 - 根据parent进程的状态更新容器的状态为Created。
- 遍历spec里面的Poststart hook,分别调用。
container_linux.go#newParentProcess
创建一个initProcess,里面既有init进程的信息,也有spec里面指定的process的信息。
- 创建一对pipe——parentPipe和childPipe,打开rootDir。
- 创建一个command,命令为
runc init
自身(通过/proc/self/exe软链接实现);标准io为当前进程的;工作目录为Rootfs;用ExtraFiles在新进程中保持打开childPipe和rootDir,并添加对应的环境变量。 - 调用newInitProcess进一步将parent process和command封装为initProcess。主要工作为添加初始化类型环境变量,将namespace、uid/gid映射等配置信息用bootstrapData封装为一个
io.Reader
等。
process_linux.go#initProcess.start()
- 异步启动
cmd.Start()
(等同于调用runc init
)来启动init进程。 - 将spec中process指定的ops指定为initProcess。
- 将前面创建bootstrapData从parentPipe传出去(init进程会从childPipe接收到这些数据,reverse出写入的内容,进行namespace相关的配置)
- 调用
execSetns()
,这个方法名看似是进行namespace的配置,实际上则是等待上面init进程的执行,并在parentPipe等待并解析出从childPipe传回的pid(谁的pid),找到该pid对应的进程,并将cmd.Process对应的进程替换为该进程。 - 为checkpoint做准备,保存cmd.Process进程的标准IO文件描述符。
- 应用cgroup配置
- 创建容器中的network interface。
- 将容器的配置文件内容spec从parentPipe发送给init进程。
- 下面与init进程进行同步,一个for循环状态机,通过解析parentPipe传回的sync Type来执行相应的操作。按正常的时间顺序,如下:
- procReady,继续配置cgroup(Set与Apply的区别?)、oom、rlimits;如果配置中没有mount namespace(Why?),则执行prestart钩子;往parentPipe写入procRun状态。
- procHooks,执行prestart钩子,往parentPipe写入procResume状态。(这个应该不是标准create的流程,resume?)
- procError,just error and exit
- 进行一些是否成功run和resume的判断,进行错误处理。
- 关闭parentPipe,返回nil or err。
至此,parent端相关的操作分析便结束了,下面从init进程继续分析container的create流程。
factory_linux.go#StartInitialization()
前面稍微省略了几个不重要的步骤,main>initCommand>“here”
- 从环境变量中解析出childPipe、rootDir的fd以及initType(默认为standard,有时间看一下还有其他什么特别的初始化方式),并清除当前进程的所有环境变量。
- 设置一个trap以及panic recover,如果初始化容器失败,会往childPipe中写入procError。
- 调用
newContainerInit
创建一个init对象(两种类型,standard or setns,下面以standard为例),首先从childPipe中获取config配置文件,从配置文件中读取环境变量并设置到当前进程。构造一个linuxStandardInit对象,主要包括pipe、parentPid、config和rootDir等字段。 - 调用linuxStandardInit对象的
Init
方法进行初始化。
standard_init_linux.go#Init
- 首先是针对Session keyring的一些配置,不是很清楚这里的Session是什么?
- 配置console和tty。如果配置文件中指定有Console字段,则从该字段中获取tty的slave路径创建一个linuxConsole对象,调用其
dupStdio
打开slave设备,将其fd复制(dup3)到当前进程的标准IO。如果console对象创建好以后,便调用ioctl的TIOCSCTTY分配控制终端,这里应该是和4.3+BSD系统保持兼容。(关于tty和console的进一步内容,有时间转发一篇更详细的或者自己总结一篇也行,对这一部分也挺感兴趣) - 调用
setupNetwork
配置容器的网络。奇怪网络不是在前面配置过了吗,还是调用同样的函数。。。存疑? - 调用
setupRoute
配置容器的静态路由信息。 - selinux,调用
label.Init()
检查selinux是否被启动以及是否检查过,并将结果存入全局变量。此处的label并非是用户label,而是selinux相关的processLabel。 - 如果设置了mount namespace,则调用
setupRootfs
在新的mount namespace中配置设备、挂载点以及文件系统。 - 根据需要配置hostname、apparmor、processLabel、sysctl、readonlyPath、maskPath。这些都是一些feature,对容器启动本身没有太多影响。
- 获取父进程的退出信号量。
- 通过管道与父进程进行同步,先发出procReady再等待procRun。
- 初始化seccomp。
- 调用
finalizeNamespace
根据config配置将需要的特权capabilities加入白名单,设置user namespace,关闭不需要的文件描述符。 - 恢复parent进程的death信号量并检查当前父进程pid是否为我们原来记录的。不是的话,kill ourself。。。
- 检查config里面需要执行的命令是否存在。注意:create虽然不会执行命令,但是会检查命令路径是否正确,该错误类型也会在create期间返回。
- 到此,与父进程之间的同步已经完成,关闭pipe。
- 尝试以只写方式打开fifo管道,并往管道中写入“0” 。该操作会一直保持阻塞,直到管道的另一端以读方式打开,并读取内容。至此,create操作流程已经结束。ref : FIFO管道
- 下面实际上是start的时候才会触发的操作了,阻塞清除后,根据config配置初始化seccomp,并调用syscall.Exec执行config里面指定的命令。
Start
start.go#startCommand
- 用生成的factory调用
Load
从容器文件夹中载入该容器的配置,生成container对象。 - 获取container的状态,并对其判断。这里只有container状态是
Created
才是合法的。调用container.Exec
libcontainer/container_linux.go#Exec
这是个对
linuxContainer.exec
函数的同步封装,下面说的是exec
函数.
- 以只读方式打开fifo管道,读取内容,如果长度大于0,则读取到Create流程中最后写入的“0”,也同时恢复阻塞了Create的init进程,执行最后调用用户进程部分。
原文:http://blog.csdn.net/zcq8989/article/details/53035542