文件结构
run 命令主要是创建了一个用 namespace 隔离的容器进程,并初始化容器内容,挂载 proc 文件系统,运行用户指定的程序。
实现 run 命令主要有这几个文件:main.go、main_command.go、container_process.go、init.go、run.go。
main.go
main 文件是程序的入口,使用 github.com/urfave/cli 提供的命令行工具,定义了 mydocker 的几个基本命令,包括 runCommand 和 initCommand。
main_command.go
main_command.go 定义了 runCommand 和 initCommand 的具体实现。
对于 runCommand,定义了 runCommand 的 Flags,其作用类似于运行命令时使用 — 来指定参数。
然后解析参数:
- 判断参数是否包含 command
- 获取用户指定的 command
- 调用 Run function 去准备启动容器
对于 initCommand,主要是做了两件事情:
- 获取传递过来的 command 参数
- 执行容器初始化操作
其实是去调用 init.go 中的 RunContainerinitProcess 方法。
run.go
run.go 首先通过 container_process.go文件中的 NewParentProcess 方法获得一个配置了 namespace隔离参数的 command 对象,然后调用该对象的 Start() 方法。这里的 Start 方法是真正开始前面创建好的command 的调用,它首先会 clone 出来 namespace 隔离的进程,然后在子进程中,调用proc/self/exe ,也就是调用自己,发送 init 参数,调用我们写的工nit 方法, 去初始化容器的一些资源。
container_process.go
container_process.go 文件中的 NewParentProcess 主要是配置 command 对象的参数。
- 这里的 /proc/self/exe 调用中,/proc/self/ 指的是当前运行进程自己的环境,exec 其实就是自己调用了自己,使用这种方式对创建出来的进程进行初始化
- 后面的 args 是参数,其中 init 是传递给本进程的第一个参数,在本例中,其实就是会去调用 initCornmand 去初始化进程的一些环境和资源
- 下面的 clone 参数就是去 fork 出来一个新进程,并且使用了 namespace 隔离新创建的进程和外部环境
init.go
这里的 init 函数是在容器内部执行的,也就是说,代码执行到这里后,容器所在的进程其实就已经创建出来了, 这是本容器执行的第一个进程。
使用 mount 先去挂载 proc 文件系统,以便后面通过 ps 等系统命令去查看当前进程资源的情况。
syscall.Exec 这个方法,其实最终调用了 Kernel 的 execve 系统函数。它的作用是执行当前 filename 对应 的程序。它会覆盖当前进程的镜像、数据和堆栈等信息,包括 PID,这些都会被将要运行的进程覆盖掉。
也就是说,调用这个方法,将用户指定的进程运行起来,把最初的 init 进程给替换掉,这样当进入到容器内部的时候,就会发现容器内的第一个程序就是我们指定的进程了。