runc run命令源码流程解读

有些地方不是很明白,会继续研究,先初步掌握整体流程

简单记录,后续整理

断点设置

b /root/workspace/src/github.com/opencontainers/runc/utils_linux.go:248

b /root/workspace/src/github.com/opencontainers/runc/utils_linux.go:288

调用run 命令,会先检查参数是否正确,然后设置pid file

根据命令行参数初始化容器配置

1.更改工作路径到--bundle参数所指定的路径下

2.读取config.json配置文件 生成specs.Spec

3.校验process的参数是否正确validateProcessSpec

4.返回specs.Spec

启动容器,需要specs.Spec和CT_ACT_RUN这个参数(查看这个参数在linux的含义)

func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    id := context.Args().First()
    if id == "" {
        return -1, errEmptyID
    }
    
    notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
	if notifySocket != nil {
		notifySocket.setupSpec(context, spec)
	}

    container, err := createContainer(context, id, spec)
    if err != nil {
        return -1, err
    }
    
    if notifySocket != nil {
        err := notifySocket.setupSocket()
        if err != nil {
            return -1, err
        }
    }
    
    // Support on-demand socket activation by passing file descriptors into the container init process.
    listenFDs := []*os.File{}
    if os.Getenv("LISTEN_FDS") != "" {
        listenFDs = activation.Files(false)
    }
    r := &runner{
        enableSubreaper: !context.Bool("no-subreaper"),
        shouldDestroy:   true,
        container:       container,
        listenFDs:       listenFDs,
        notifySocket:    notifySocket,
        consoleSocket:   context.String("console-socket"),
        detach:          context.Bool("detach"),
        pidFile:         context.String("pid-file"),
        preserveFDs:     context.Int("preserve-fds"),
        action:          action,
        criuOpts:        criuOpts,
        init:            true,
    }
    return r.run(spec.Process)
}

NOTIFY_SOCKET不需要,因此notifySocket为nil

createContainer()两个重要的参数:容器id,容器spec配置

func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    id := context.Args().First()
    if id == "" {
        return -1, errEmptyID
    }
    
    ...

    container, err := createContainer(context, id, spec)
    if err != nil {
        return -1, err
    }
    
    ...
}
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
    rootless, err := isRootless(context)
    if err != nil {
        return nil, err
    }
    config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
        CgroupName:       id,
        UseSystemdCgroup: context.GlobalBool("systemd-cgroup"),
        NoPivotRoot:      context.Bool("no-pivot"),
        NoNewKeyring:     context.Bool("no-new-keyring"),
        Spec:             spec,
        Rootless:         rootless,
    })
    if err != nil {
        return nil, err
    }

    factory, err := loadFactory(context)
    if err != nil {
        return nil, err
    }
    return factory.Create(id, config)
}

1.判断是否为rootless

2.根据spec配置和cgroup,生成libcontainer的配置

3.loadFactory 生成指定的容器的LinuxFactory

4.Create 创建状态为stopped的linuxContainer容器配置,创建容器的根目录

初始化runner对象,该对象就是实际容器的启动控制对象

运行runner.run(spec.Process)

func (r *runner) run(config *specs.Process) (int, error) {
	if err := r.checkTerminal(config); err != nil {
		r.destroy()
		return -1, err
	}
	process, err := newProcess(*config, r.init)
	if err != nil {
		r.destroy()
		return -1, err
	}
	if len(r.listenFDs) > 0 {
		process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
		process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
	}
	baseFd := 3 + len(process.ExtraFiles)
	for i := baseFd; i < baseFd+r.preserveFDs; i++ {
		process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
	}
	rootuid, err := r.container.Config().HostRootUID()
	if err != nil {
		r.destroy()
		return -1, err
	}
	rootgid, err := r.container.Config().HostRootGID()
	if err != nil {
		r.destroy()
		return -1, err
	}
	var (
		detach = r.detach || (r.action == CT_ACT_CREATE)
	)
	// Setting up IO is a two stage process. We need to modify process to deal
	// with detaching containers, and then we get a tty after the container has
	// started.
	handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
	tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach, r.consoleSocket)
	if err != nil {
		r.destroy()
		return -1, err
	}
	defer tty.Close()

	switch r.action {
	case CT_ACT_CREATE:
		err = r.container.Start(process)
	case CT_ACT_RESTORE:
		err = r.container.Restore(process, r.criuOpts)
	case CT_ACT_RUN:
		err = r.container.Run(process)
	default:
		panic("Unknown action")
	}
	if err != nil {
		r.destroy()
		return -1, err
	}
	if err := tty.waitConsole(); err != nil {
		r.terminate(process)
		r.destroy()
		return -1, err
	}
	if err = tty.ClosePostStart(); err != nil {
		r.terminate(process)
		r.destroy()
		return -1, err
	}
	if r.pidFile != "" {
		if err = createPidFile(r.pidFile, process); err != nil {
			r.terminate(process)
			r.destroy()
			return -1, err
		}
	}
	status, err := handler.forward(process, tty, detach)
	if err != nil {
		r.terminate(process)
	}
	if detach {
		return 0, nil
	}
	r.destroy()
	return status, err
}

1.检测terminal是否符合要求

2.newProcess 根据spec和specs.Process,生成新的libcontainer的Process对象

3.根据上面的CT_ACT_RUN执行container.Run(process)

4.默认的runner.Init为true,因此Start的时候,会先在容器根目录下新建exec.fifo的文件

5.然后创建state.json

6.容器运行后会删除exec.fifo

7.tty.waitConsole() 等待容器退出