美文网首页
从源码理解runc之create和start

从源码理解runc之create和start

作者: wwq2020 | 来源:发表于2024-01-11 17:37 被阅读0次

    背景

    runc create 创建一个容器
    本文针对runc v1.1.11源码

    create源码

    前置

    main.go中

    程序入口
    func main() {
    ...
        app := cli.NewApp()
    ...
    添加子命令
        app.Commands = []cli.Command{
    ...
            createCommand,
    ...
        }
    ...
        if err := app.Run(os.Args); err != nil {
            fatal(err)
        }
    }
    

    create.go中

    var createCommand = cli.Command{
    ...
    启动容器
            status, err := startContainer(context, CT_ACT_CREATE, nil)
    
    ...
    }
    

    utils_linux.go中

    func startContainer(context *cli.Context, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
    ...
    加载spec
        spec, err := setupSpec(context)
        if err != nil {
            return -1, err
        }
    ...
    创建容器
        container, err := createContainer(context, id, spec)
        if err != nil {
            return -1, err
        }
    ...
    启动
        r := &runner{
            enableSubreaper: !context.Bool("no-subreaper"),
            shouldDestroy:   !context.Bool("keep"),
            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)
    ...
    }
    

    createContainer相关

    utils_linux.go中

    func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
    ...
    配置转换
        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,
            RootlessEUID:     os.Geteuid() != 0,
            RootlessCgroups:  rootlessCg,
        })
    ...
    加载工厂对象
        factory, err := loadFactory(context)
        if err != nil {
            return nil, err
        }
    创建容器
        return factory.Create(id, config)
    ...
    }
    
    加载工厂对象
    func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
    ...
        return libcontainer.New(abs,
            libcontainer.CriuPath(context.GlobalString("criu")),
            libcontainer.NewuidmapPath(newuidmap),
            libcontainer.NewgidmapPath(newgidmap))
    }
    

    libcontainer/factory_linux.go

    构建工厂对象
    func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
        if root != "" {
            if err := os.MkdirAll(root, 0o700); err != nil {
                return nil, err
            }
        }
        l := &LinuxFactory{
            Root:      root,
            InitPath:  "/proc/self/exe",
            InitArgs:  []string{os.Args[0], "init"},
            Validator: validate.New(),
            CriuPath:  "criu",
        }
    
    ...
        return l, nil
    }
    
    
    func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
    ...
    创建cgroup manager
        cm, err := manager.New(config.Cgroups)
        if err != nil {
            return nil, err
        }
    
    ...
    构建容器对象,状态为stopped
        c := &linuxContainer{
            id:              id,
            root:            containerRoot,
            config:          config,
            initPath:        l.InitPath,
            initArgs:        l.InitArgs,
            criuPath:        l.CriuPath,
            newuidmapPath:   l.NewuidmapPath,
            newgidmapPath:   l.NewgidmapPath,
            cgroupManager:   cm,
            intelRdtManager: intelrdt.NewManager(config, id, ""),
        }
        c.state = &stoppedState{c: c}
    ...
    }
    

    runner.run相关

    utils_linux.go中

    func (r *runner) run(config *specs.Process) (int, error) {
    ...
    创建process
        process, err := newProcess(*config)
        if err != nil {
            return -1, err
        }
    ...
        switch r.action {
            err = r.container.Start(process)
    ...
    }
    

    libcontainer/container_linux.go中

    func (c *linuxContainer) Start(process *Process) error {
    ...
    创建execfifo
        if process.Init {
            if err := c.createExecFifo(); err != nil {
                return err
            }
        }
        if err := c.start(process); err != nil {
            if process.Init {
                c.deleteExecFifo()
            }
            return err
        }
    ...
    }
    
    func (c *linuxContainer) createExecFifo() error {
    ...
    创建fifo文件,用于进程间通信
        if err := unix.Mkfifo(fifoName, 0o622); err != nil {
            unix.Umask(oldMask)
            return err
        }
    ...
    }
    
    func (c *linuxContainer) start(process *Process) (retErr error) {
        parent, err := c.newParentProcess(process)
        if err != nil {
            return fmt.Errorf("unable to create new parent process: %w", err)
        }
    ...
        if err := parent.start(); err != nil {
            return fmt.Errorf("unable to start container process: %w", err)
        }
    ...
    }
    
    
    创建父进程
    func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
        parentInitPipe, childInitPipe, err := utils.NewSockPair("init")
        if err != nil {
            return nil, fmt.Errorf("unable to create init pipe: %w", err)
        }
    ...
    
    ...
        if err := c.includeExecFifo(cmd); err != nil {
            return nil, fmt.Errorf("unable to setup exec fifo: %w", err)
        }
        return c.newInitProcess(p, cmd, messageSockPair, logFilePair)
    }
    
    
    func (c *linuxContainer) commandTemplate(p *Process, childInitPipe *os.File, childLogPipe *os.File) *exec.Cmd {
    initPath为/proc/self/exec,也就是runc
        cmd := exec.Command(c.initPath, c.initArgs[1:]...)
    ...
    }
    
    创建init进程
    func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, messageSockPair, logFilePair filePair) (*initProcess, error) {
    ...
    
        init := &initProcess{
            cmd:             cmd,
            messageSockPair: messageSockPair,
            logFilePair:     logFilePair,
            manager:         c.cgroupManager,
            intelRdtManager: c.intelRdtManager,
            config:          c.newInitConfig(p),
            container:       c,
            process:         p,
            bootstrapData:   data,
            sharePidns:      sharePidns,
        }
        c.initProcess = init
        return init, nil
    }
    
    func (p *initProcess) start() (retErr error) {
    ...
    启动
        err := p.cmd.Start()
    ...
        ierr := parseSync(p.messageSockPair.parent, func(sync *syncT) error {
            switch sync.Type {
            case procReady:
    ...
    通知子进程启动程序
                if err := writeSync(p.messageSockPair.parent, procRun); err != nil {
                    return err
                }
    ...
    }
    ...
    }
    
    

    init 相关

    init.go中

    func init() {
        if len(os.Args) > 1 && os.Args[1] == "init" {
    ...
            factory, _ := libcontainer.New("")
            if err := factory.StartInitialization(); err != nil {
                // as the error is sent back to the parent there is no need to log
                // or write it to stderr because the parent process will handle this
                os.Exit(1)
            }
    ...
    }
    

    libcontainer/factory_linux.go中

    构建工厂对象
    func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
        if root != "" {
            if err := os.MkdirAll(root, 0o700); err != nil {
                return nil, err
            }
        }
        l := &LinuxFactory{
            Root:      root,
            InitPath:  "/proc/self/exe",
            InitArgs:  []string{os.Args[0], "init"},
            Validator: validate.New(),
            CriuPath:  "criu",
        }
    
        for _, opt := range options {
            if opt == nil {
                continue
            }
            if err := opt(l); err != nil {
                return nil, err
            }
        }
        return l, nil
    }
    
    
    开启初始化
    func (l *LinuxFactory) StartInitialization() (err error) {
    这个文件就是socketpair
        envInitPipe := os.Getenv("_LIBCONTAINER_INITPIPE")
        pipefd, err := strconv.Atoi(envInitPipe)
        if err != nil {
            err = fmt.Errorf("unable to convert _LIBCONTAINER_INITPIPE: %w", err)
            logrus.Error(err)
            return err
        }
        pipe := os.NewFile(uintptr(pipefd), "pipe")
        defer pipe.Close()
    ...
    这个就是exec.fifo
        fifofd := -1
        envInitType := os.Getenv("_LIBCONTAINER_INITTYPE")
        it := initType(envInitType)
        if it == initStandard {
            envFifoFd := os.Getenv("_LIBCONTAINER_FIFOFD")
            if fifofd, err = strconv.Atoi(envFifoFd); err != nil {
                return fmt.Errorf("unable to convert _LIBCONTAINER_FIFOFD: %w", err)
            }
        }
    ...
        i, err := newContainerInit(it, pipe, consoleSocket, fifofd, logPipeFd, mountFds)
        if err != nil {
            return err
        }
        return i.Init()
    
    }
    

    libcontainer/init_linux.go中

    创建容器 init
    func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, fifoFd, logFd int, mountFds []int) (initer, error) {
        var config *initConfig
        if err := json.NewDecoder(pipe).Decode(&config); err != nil {
            return nil, err
        }
        if err := populateProcessEnvironment(config.Env); err != nil {
            return nil, err
        }
        switch t {
    ...
        case initStandard:
            return &linuxStandardInit{
                pipe:          pipe,
                consoleSocket: consoleSocket,
                parentPid:     unix.Getppid(),
                config:        config,
                fifoFd:        fifoFd,
                logFd:         logFd,
                mountFds:      mountFds,
            }, nil
        }
        return nil, fmt.Errorf("unknown init type %q", t)
    }
    
    

    libcontainer/standard_init_linux.go中

    func (l *linuxStandardInit) Init() error {
    ...
    通过socketpair通知parent就绪
        if err := syncParentReady(l.pipe); err != nil {
            return fmt.Errorf("sync ready: %w", err)
        }
    ...
        _ = l.pipe.Close()
    
    ...
    fifo写入0
        fifoPath := "/proc/self/fd/" + strconv.Itoa(l.fifoFd)
        fd, err := unix.Open(fifoPath, unix.O_WRONLY|unix.O_CLOEXEC, 0)
        if err != nil {
            return &os.PathError{Op: "open exec fifo", Path: fifoPath, Err: err}
        }
    此处会阻塞,由于另一端没有读取
        if _, err := unix.Write(fd, []byte("0")); err != nil {
            return &os.PathError{Op: "write exec fifo", Path: fifoPath, Err: err}
        }
    ...
    启动用户程序
        return system.Exec(name, l.config.Args[0:], os.Environ())
    }
    
    
    

    libcontainer/process_linux.go中

    通知父进程就绪等待父进程通知可运行程序
    func syncParentReady(pipe io.ReadWriter) error {
        // Tell parent.
        if err := writeSync(pipe, procReady); err != nil {
            return err
        }
    
        // Wait for parent to give the all-clear.
        return readSync(pipe, procRun)
    }
    

    至此runc容器为created状态

    start源码

    start实际就是读取exec.fifo内容,然后删除exec.fifo
    start.go中

    命令
    var startCommand = cli.Command{
    ...
            switch status {
            case libcontainer.Created:
    ...
                if err := container.Exec(); err != nil {
                    return err
                }
    ...
                return nil
    ...
    }
    
    

    libcontainer/container_linux.go中

    start执行Exec
    
    func (c *linuxContainer) Exec() error {
        c.m.Lock()
        defer c.m.Unlock()
        return c.exec()
    }
    
    内部exec
    func (c *linuxContainer) exec() error {
        path := filepath.Join(c.root, execFifoFilename)
        pid := c.initProcess.pid()
    打开fifo文件
        blockingFifoOpenCh := awaitFifoOpen(path)
        for {
            select {
            case result := <-blockingFifoOpenCh:
    处理fifo结果
                return handleFifoResult(result)
    
    ...
        }
    }
    
    
    func handleFifoResult(result openResult) error {
        if result.err != nil {
            return result.err
        }
        f := result.file
        defer f.Close()
    读取fifo内容
        if err := readFromExecFifo(f); err != nil {
            return err
        }
    删除fifo文件
        return os.Remove(f.Name())
    }
    

    至此runc 容器为running状态

    相关文章

      网友评论

          本文标题:从源码理解runc之create和start

          本文链接:https://www.haomeiwen.com/subject/osmtodtx.html