runc源码分析——create和start

来源:互联网 发布:mac远程协助 编辑:程序博客网 时间:2024/06/08 19:21

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

0 0
原创粉丝点击