我们使用runc create创建容器、启动容器。主要流程如下:

  1. 运行runc create时,后台生成该命令的进程,我们称该进程为parent;
  2. parent进程中fork进程,运行runc init,我们称runc init进程为child进程
  3. child进程开始准备用户进程的运行环境,此时parent和child进程通过pipe进行通信;
  4. child进程准备好用户进程的运行环境后,通知parent退出,自己则被exec.fifo阻塞;
  5. 由于parent退出(即runc create退出),child成孤独进程,进而被containerd-shim进程接收
  6. child进程一直被exec.fifo阻塞;
  7. 运行runc start时,会打开exec.fifo,使child的阻塞消除,runc start退出;
  8. 由于阻塞消除,child进程继续往下执行;
  9. child进程使用用户定义的命令替换runc init,从而child进程成为容器内的主进程;
  10. 容器启动完成。

image.png

发送的具体数据在 linuxContainer 的 bootstrapData() 函数中封装成netlink msg格式的消息

runc init会生成3个进程,

  1. namespaces在runc init 2完成创建
  2. runc init 1和runc init 2最终都会执行exit(0),但runc init 3不会,它会继续执行runc init命令的后半部分。因此最终只会剩下runc create进程和runc init 3进程

image.png

runc create 进程中:

create.go.
—> utils_linux.go startContainer

——> utils_linux.go createContainer
——> utils_linux.go. (r runner) run —这里是抽象工具方法,对于 CT_ACT_CREATE 执行的 (c linuxContainer) Start

———> libcontainer/container_linux.go. (c linuxContainer) Start
————> c.createExecFifo() 创建 exec.fifo 文件
————> (c
linuxContainer) start

—————> libcontainer/process_linux.go (p *initProcess) start()

—————> libcontainer/container_linux.go (c *linuxContainer) updateState 将状态信息存储到state.json

  1. // parentPipe的socket是SockPair:init-p 和init-c
  2. func (p *initProcess) start() error {
  3. p.cmd.Start() // 启动runc init 1
  4. p.process.ops = p
  5. io.Copy(p.parentPipe, p.bootstrapData); // 通过SockPair发送bootstrapData给runc init 1,用于设置namespace
  6. childPid, err := p.getChildPid() // 通过SockPair得到runc init 3的pid,同时等runc init 2退出
  7. fds, err := getPipeFds(childPid)
  8. p.setExternalDescriptors(fds)
  9. p.waitForChildExit(childPid) // 等待runc init 1进程终止,同时设置p.cmd.Process为runc init 3
  10. p.createNetworkInterfaces()
  11. p.sendConfig() // runc create又开始通过SockPair进行双向通信了,通信的对端自然就是runc init 3进程。sendConfig发送的配置信息包括容器entrypoint
  12. parseSync(p.parentPipe, func(sync *syncT) error {
  13. switch sync.Type {
  14. case procReady:
  15. .....
  16. writeSync(p.parentPipe, procRun);
  17. sentRun = true
  18. case procHooks:
  19. .....
  20. // Sync with child.
  21. err := writeSync(p.parentPipe, procResume);
  22. sentResume = true
  23. }
  24. return nil
  25. })

image.png

runc start中:

主要就是: 打开fifo,让runc init 3开始执行

start.go
—> libcontainer/container_linux.go Exec()
——> libcontainer/container_linux.go (c *linuxContainer) exec()

  1. func (c *linuxContainer) exec() error {
  2. path := filepath.Join(c.root, execFifoFilename)
  3. fifoOpen := make(chan struct{})
  4. select {
  5. case <-awaitProcessExit(c.initProcess.pid(), fifoOpen):
  6. return errors.New("container process is already dead")
  7. case result := <-awaitFifoOpen(path): //打开fifo,让runc init 3开始执行
  8. close(fifoOpen)
  9. if result.err != nil {
  10. return result.err
  11. }
  12. f := result.file
  13. defer f.Close()
  14. if err := readFromExecFifo(f); err != nil {
  15. return err
  16. }
  17. return os.Remove(path)
  18. }
  19. }

runc init中golang代码:

init.go
—> libcontainer/factory_linux.go (l LinuxFactory) StartInitialization()
——> libcontainer/standard_init_linux.go (l
linuxStandardInit) Init()