美文网首页
从源码理解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