仿造一个自己的 docker
今日目标
mydocker run -i -t- 基础隔离
启动流程
mydocker run -i -t -> 解析 -> 启动 init 进程 -> 重新挂载 / 为 PRIVATE -> 挂载 proc -> 执行(Exec 系统调用)
为什么需要 init 进程
init 进程有独立的命名空间(namespace)和控制组(cgroup),执行系统调用(Exec)后可以保证 pid 为 1并且与宿主机隔离
为什么要重新挂载 / 为 PRIVATE
使用以下代码验证
func Init(cmdPath string) error {
mountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
if err := syscall.Mount("proc", "/proc", "proc", uintptr(mountFlags), ""); err != nil {
l.Error("failed to mount proc", "detail", err)
return err
}
if err := syscall.Exec(cmdPath, []string{cmdPath}, os.Environ()); err != nil {
l.Error("failed to init", "cmdPath", cmdPath, "detail", err)
return err
}
return nil
}
$ sudo ./cli run -i -t /bin/sh
sh-5.3# ps
PID TTY TIME CMD
1 pts/5 00:00:00 sh
6 pts/5 00:00:00 ps
sh-5.3#
可以看到容器内 ps 命令执行没有报错并且返回内容符合预期(作为容器内的 init 进程)
此时在宿主机执行
$ ps
Error, do this: mount -t proc proc /proc
发现 proc 莫名其妙被卸载了
原因就在于 linux 默认的挂载参数为 MS_SHARED, 也就是说容器内的挂载/卸载操作会直接同步给宿主机
使用 MS_PRIVATE 重新挂载后就可以将容器内的 / 挂载为私有挂载点
func Init(cmdPath string) error {
if err := syscall.Mount("", "/", "", syscall.MS_PRIVATE|syscall.MS_REC, ""); err != nil {
l.Error("failed to remount `/` with `MS_PRIVATE`", "detail", err)
return err
}
mountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
if err := syscall.Mount("proc", "/proc", "proc", uintptr(mountFlags), ""); err != nil {
l.Error("failed to mount proc", "detail", err)
return err
}
if err := syscall.Exec(cmdPath, []string{cmdPath}, os.Environ()); err != nil {
l.Error("failed to init", "cmdPath", cmdPath, "detail", err)
return err
}
return nil
}
$ sudo ./cli run -i -t /bin/sh
sh-5.3# ps
PID TTY TIME CMD
1 pts/2 00:00:00 sh
6 pts/2 00:00:00 ps
sh-5.3#
$ ps
PID TTY TIME CMD
5558 pts/3 00:00:00 bash
5651 pts/3 00:00:00 ps
...
可以看到宿主机的 proc 没有被卸载