美文网首页
(4)构造容器之实现run命令版的容器

(4)构造容器之实现run命令版的容器

作者: 爱喝咖啡的土拨鼠 | 来源:发表于2018-01-04 20:15 被阅读0次

    linux proc

    linux 下的/proc 文件系统是又内核提供的,包含了系统运行时信息(系统内存、mount 设备信息,硬件配置等),为访问内核数据提供接口。

    实现runC

    项目结构
    项目结构.png

    https://github.com/justinmjc/mymoby

    tag:3.1节

    代码解析

    main.go

    package main
    
    import (
        "github.com/urfave/cli"
        "github.com/Sirupsen/logrus"
        "log"
        "os"
    )
    
    const usage  = `my moby is a simple contaniner runtime implementation.`
    func main() {
        app :=cli.NewApp()
        app.Name = "mymoby"
        app.Usage=usage
    
        app.Commands = []cli.Command{
            initCommand,
            runCommand,
        }
    
        app.Before = func(context *cli.Context) error {
            logrus.SetFormatter(&logrus.JSONFormatter{})
    
            log.SetOutput(os.Stdout)
            return nil
        }
    
        if err := app.Run(os.Args); err!=nil{
            log.Fatal(err)
        }
    }
    
    
    

    使用github.com/urfave/cli提供的命令行工具,定义了mymoby的基本命令initCommand和runCommand,然后在app.Before 初始化日志配置。

    接下来开下initCommand和runCommand的定义,在main_command.go文件中

    main_command.go

    package main
    
    import (
        "github.com/urfave/cli"
        "fmt"
        log "github.com/Sirupsen/logrus"
        "./container"
    )
    
    var runCommand = cli.Command{
        Name:"run",
        Usage:`Create a container with namespace and cgroups limit mymoby run -ti [command]`,
        Flags:[]cli.Flag{
            cli.BoolFlag{
            Name:"ti",
            Usage:"enable tty",
            },
    
        },
        /*
        这里是run命令执行的真正函数。
        1判断参数是否包含command
        2获取用户指定的command
        3调用Run function去准备启动容器
         */
        Action: func(context *cli.Context) error{
            if len(context.Args())<1{
                return fmt.Errorf("Missing container command")
            }
            cmd := context.Args().Get(0)
            tty := context.Bool("ti")
            Run(tty,cmd)
            return nil
        },
    
    }
    
    var initCommand = cli.Command{
        Name: "init",
        Usage: "Init container process run user's process in container.Do not call it outside",
        /*
        1获取传递过来的command参数
        2执行容器初始化操作
         */
        Action: func(context *cli.Context)  error {
            log.Infof("init come on")
            cmd := context.Args().Get(0)
            err :=container.RunContainerInitProcess(cmd, nil)
            return err
        },
    }
    
    
    

    runCommand中的Run在run.go中定义

    package main
    
    import (
        log "github.com/Sirupsen/logrus"
        "./container"
        "os"
    )
    func Run(tty bool,command string)  {
        parent := container.NewParentProcess(tty,command)
        if err :=parent.Start(); err!=nil{
            log.Error(err)
        }
        parent.Wait()
        os.Exit(1)
    }
    
    

    Run 调用NewParentProcess(),在container_process.go中定义

    package container
    
    import (
        "os/exec"
        "syscall"
        "os"
    )
    
    func NewParentProcess(tty bool, command string) * exec.Cmd  {
        args := []string{"init",command}
        cmd := exec.Command("/proc/self/exe",args...)
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
                syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
        }
        if tty {
            cmd.Stdin = os.Stdin
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr
        }
        return cmd
    }
    
    
    

    在NewParentProcess()中“/proc/self”指的是当前运行进程自己的环境,exec其实是自己调用自己,通过这种方式对创建出来的进程进行初始化。

    args的第一个参数"init",实际上就是会去调用initCommand进行初始化操作

    下面的clone参数就是去fork出来一个新进程,使用namespace隔离环境

    Run执行完在NewParentProcess()后执行parent.Start(),Start方法是真正前面创建好的command的调用,它首先clone出一个Namespace隔离的进程,然后在子进程中调用/proc/self/exe,发送init,初始化容器的一些资源。

    最后,运行

    
    
    maojiancai@bogon:~/mygo/mymoby$ go build .
    maojiancai@bogon:~/mygo/mymoby$ ls
    container  main_command.go  main.go  mymoby  README.md  run.go  src
    maojiancai@bogon:~/mygo/mymoby$ ./mymoby run -ti /bin/sh
    {"level":"error","msg":"fork/exec /proc/self/exe: operation not permitted","time":"2018-01-02T06:56:12-08:00"}
    maojiancai@bogon:~/mygo/mymoby$ sudo ./mymoby run -ti /bin/sh
    [sudo] password for maojiancai:
    {"level":"info","msg":"init come on","time":"2018-01-02T06:56:44-08:00"}
    {"level":"info","msg":"command %s/bin/sh","time":"2018-01-02T06:56:44-08:00"}
    # ps -ef
    UID         PID   PPID  C STIME TTY          TIME CMD
    root          1      0  0 06:56 pts/0    00:00:00 /bin/sh
    root          4      1  0 06:56 pts/0    00:00:00 ps -ef
    #
    

    在init.go 中调用的syscall.Exec方法,最终调用了Kernel的execve 这个系统函数。它的作用是执行当前filename对应的程序。他会覆盖当前进行的镜像、数据和堆栈信息,PID,这些都会被将要运行的程序覆盖。

    相关文章

      网友评论

          本文标题:(4)构造容器之实现run命令版的容器

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