容器 = 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