我们使用runc create
创建容器、启动容器。主要流程如下:
- 运行
runc create
时,后台生成该命令的进程,我们称该进程为parent; - parent进程中fork进程,运行
runc init
,我们称runc init
进程为child进程; - child进程开始准备用户进程的运行环境,此时parent和child进程通过pipe进行通信;
- child进程准备好用户进程的运行环境后,通知parent退出,自己则被exec.fifo阻塞;
- 由于parent退出(即
runc create
退出),child成孤独进程,进而被containerd-shim进程接收 - child进程一直被exec.fifo阻塞;
- 运行
runc start
时,会打开exec.fifo,使child的阻塞消除,runc start
退出; - 由于阻塞消除,child进程继续往下执行;
- child进程使用用户定义的命令替换
runc init
,从而child进程成为容器内的主进程; - 容器启动完成。
发送的具体数据在 linuxContainer 的 bootstrapData() 函数中封装成netlink msg格式的消息
runc init会生成3个进程,
- namespaces在runc init 2完成创建
- runc init 1和runc init 2最终都会执行exit(0),但runc init 3不会,它会继续执行runc init命令的后半部分。因此最终只会剩下runc create进程和runc init 3进程
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
// parentPipe的socket是SockPair:init-p 和init-c
func (p *initProcess) start() error {
p.cmd.Start() // 启动runc init 1
p.process.ops = p
io.Copy(p.parentPipe, p.bootstrapData); // 通过SockPair发送bootstrapData给runc init 1,用于设置namespace
childPid, err := p.getChildPid() // 通过SockPair得到runc init 3的pid,同时等runc init 2退出
fds, err := getPipeFds(childPid)
p.setExternalDescriptors(fds)
p.waitForChildExit(childPid) // 等待runc init 1进程终止,同时设置p.cmd.Process为runc init 3
p.createNetworkInterfaces()
p.sendConfig() // runc create又开始通过SockPair进行双向通信了,通信的对端自然就是runc init 3进程。sendConfig发送的配置信息包括容器entrypoint
parseSync(p.parentPipe, func(sync *syncT) error {
switch sync.Type {
case procReady:
.....
writeSync(p.parentPipe, procRun);
sentRun = true
case procHooks:
.....
// Sync with child.
err := writeSync(p.parentPipe, procResume);
sentResume = true
}
return nil
})
runc start中:
主要就是: 打开fifo,让runc init 3开始执行
start.go
—> libcontainer/container_linux.go Exec()
——> libcontainer/container_linux.go (c *linuxContainer) exec()
func (c *linuxContainer) exec() error {
path := filepath.Join(c.root, execFifoFilename)
fifoOpen := make(chan struct{})
select {
case <-awaitProcessExit(c.initProcess.pid(), fifoOpen):
return errors.New("container process is already dead")
case result := <-awaitFifoOpen(path): //打开fifo,让runc init 3开始执行
close(fifoOpen)
if result.err != nil {
return result.err
}
f := result.file
defer f.Close()
if err := readFromExecFifo(f); err != nil {
return err
}
return os.Remove(path)
}
}
runc init中golang代码:
init.go
—> libcontainer/factory_linux.go (l LinuxFactory) StartInitialization()
——> libcontainer/standard_init_linux.go (l linuxStandardInit) Init()