gocker:自己动手用Golang实现docker

2020-11-13

容器 = Namespace + cgroups + 联合文件系统

与docker类似,通过gocker启动一个“容器”

gocker run -it -rootfs=/tmp/busybox /bin/sh

分析核心代码:run.go

var RunCommand = &cli.Command{
    Action: func(context *cli.Context) error {
        // 开启交互式的终端
        tty := context.Bool("it")
        // rootfs的路径
        rootfs := context.String("rootfs")
        // 容器初始化操作,见下方init方法
        cmd := exec.Command("/proc/self/exe", "init")
        // 设置为该进程创建的命名空间
        // Mount Namespace、UTS Namespace、IPC Namespace、PID Namespace 和 Net Namespace
        cmd.SysProcAttr = &syscall.SysProcAttr{
            Cloneflags: syscall.CLONE_NEWNS |
                syscall.CLONE_NEWUTS |
                syscall.CLONE_NEWIPC |
                syscall.CLONE_NEWPID |
                syscall.CLONE_NEWNET,
        }
        // 把容器的标准输出重定向到主机的标准输出
        if tty {
            cmd.Stdin = os.Stdin
            cmd.Stdout = os.Stdout
            cmd.Stderr = os.Stderr
        }
        // 指定进程的根目录
        cmd.Dir = rootfs
        // 启动容器
        if err := cmd.Start(); err != nil {
            log.Println("command start error", err)
            return err
        }
        cmd.Wait()
        return nil
    },
}

var InitCommand = &cli.Command{
    Action: func(context *cli.Context) error {
        // 获取当前工作目录
        pwd, err := os.Getwd()
        // 获取命令数组
        cmdArray := readCommandArray()
        // pivotRoot:pivotRoot 是一个系统调用,主要功能是改变当前进程的根目录
        // 它可以把当前进程的根目录移动到我们传递的rootfs 目录下
        err = pivotRoot(pwd)
        // 挂载容器自己的 proc 目录,使得容器中执行 ps 命令只能看到自己命名空间下的进程;
        defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
        syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")
        // 配置hostname
        if err := syscall.Sethostname([]byte("lagoudocker")); err != nil {
            fmt.Printf("Error setting hostname - %s\n", err)
            return err
        }
        path, err := exec.LookPath(cmdArray[0])
        // syscall.Exec 相当于 shell 中的 exec 实现,这里用 用户传递的主命令来替换 init 进程,从而实现容器的 1 号进程为用户传递的主进程
        if err := syscall.Exec(path, cmdArray[0:], os.Environ()); err != nil {
            log.Println(err.Error())
        }
        return nil
    },
}

GitHub仓库地址:gocker