1.docker的后台运行(detach功能)
原理:在 Docker 早期版本,所有的容器 init 进程都是从 docker daemon 这个进程 fork 出来的,这也就会导致一个众所周知的问题,如果 docker daemon 挂掉,那么所有的容器都会宕掉,这给升级 docker daemon 带来很大的风险。父子进程是异步的。创建子进程的父进程挂掉会造成子进程成为孤儿进程,进程号为1的init进程接管,实际上还是可以运行的,但是 docker daemon 挂了会导致他维护的一些资源也没了,所以容器实际上是不能正常运行的
后来docker使用了containerd-shim进程负责管理容器的创建运行停止。每个进程都有init进程(containerd-shim),containerd-shim 进程负责接收来自 containerd 的命令,启动容器中的进程,并监控它们的生命周期。
- 在容器的runcommand中加入Args参数:d表示后台运行
- 只有注意不可以一边要交互式伪终端,一边要后台运行
- parent.Wait() 主要是用于父进程等待子进程结束,这在交互式创建容器的步骤里面是没问题的,但是指定了 -d要后台运行就不能再去等待,创建容器之后,父进程直接退出即可。
2.实现docker的ps命令
思路:在容器启动后,将容器的信息存储在/var/lib/mydocker/containers/{containerID}/containner.json文件中,在执行-ps命令时,查询启动/var/lib/mydocker/containers/文件下的所有文件即可。
实现:
- 准备:
- 1.在fork出子进程,创建container.Info实例:{name,PID,Command,create_time,status}
- 2.创建文件夹:MkdirAll/var/lib/mydocker/containers/{%s(containerID)}
- 3.创建文件:file := os.Create(dir,config.json),写入文件:file.WriteString(jsonstr)
- 4.在进程停止的时候删除文件既可os.removeAll(dir)/var/lib/mydocker/containers/{containerID}
- ps命令:
- Files := os.ReadDir读取/var/lib/mydocker/containers文件夹的所有文件
- 读取文件
- dir := /var/lib/mydocker/containers+file.name
- realPath := path.Join(dir,config.json)
- os.Readfile(realPath)
- json的反序列化为对象->添加到container.info切片中
- tabwriter 是引用的text/tabwriter类库,用于在控制台打印对齐的表格,打印容器信息
效果:
3.实现mydocker的logs功能
流程:
将进程的输出重定向到容器的日志文件中
实现logcommand命令
实现:
效果:
- 输入:
sudo ./myDocker logs 8287657315
4.实现exec进入容器
原理:
将当前进程的PID加入到容器的namespace中即可实现交互式访问容器,linux操作系统的setns调用即可实现。
难点:
setns系统调用允许进程加入(或重新进入)到指定的 Namespace 中。由于 Namespace 涉及到整个进程的资源隔离,因此需要在进程的上下文中执行,以确保进程及其所有线程都在相同的 Namespace 中;Go Runtime 是多线程的,这意味着 Go 程序通常会有多个线程在同时运行。这种多线程模型与 ==setns 调用所需的单线程上下文不兼容。
Goroutine 会随机在底层 OS 线程之间切换,而不是固定在某个线程,因此在 Go 中执行 setns 不能准确的知道是操作到哪个线程了,结果是不确定的,因此需要特殊处理。
****:
是 C 语言可以通过 gcc 的 扩展 attribute((constructor)) 来实现程序启动前执行特定代码,因此 Go 就可以通过 cgo 嵌入 这样的一段 C 代码来完成 runtime 启动前执行特定的 C 代码。
实现:
cgo这里主要使用了构造函数,然后导入了 C 模块,
execCommand:
获得进程PID\comArray命令,通过设置环境变量,用于与C传递
fork进程
效果:
5.实现docker容器的停止
实质:停止容器的实质就是杀死容器进程。
原理:查找到容器的json文件存储地址,获得PID,使用System.kill杀死进程,修改文件的内容,statu与pid使得ps命令能够获取最新的状态。
流程:
添加stopCommand-> getContainerInfoById(containerID)-> syscall.Kill(PID, syscall.SIGTERM)->json序列化-> 写入f.Write(newContentByte)
效果:
6.实现docker的删除容器容器指令
实质:删除容器的文件夹
原理:查找到容器的json文件存储地址,获得容器状态,如果状态是停止的,直接删除文件夹;若是运行的则先停止运行再删除,如果是其他则报错即可
效果:
正在运行的容器加上-f强制删除指令即可删除,否则报错
7.重构:实现容器间 rootfs 隔离
引入必要性:overlayfs 实现了容器和宿主机的 rootfs 隔离,但是多个容器还是共用的一个rootfs–/root/merged ,多容器之间会互相影响。
实现:这三处调整实际上都是对宿主机上容器 rootfs 目录的调整,把 rootfs 从原来的 /root/merged 调整为/var/lib/mydocker/overlay2/{containerID}/merged ,这样实现容器之间的隔离。
修改 mydocker commit 命令,实现对不同容器进行打包镜像的功能。压缩路径为imageName.tar
修改 mydocker run 命令,用户可以指定不同镜像,并为每个容器分配单独的隔离文件系统
- 根据镜像名称找到对应 tar 文件,解压后作为overlay 中的 lower 目录进行挂载
- rootfs具有很大的修改
修改 mydocker rm 命令,删除容器时顺带删除文件系统
效果:1.运行容器,容器的/tmp中写入a.txt
2.将容器打包为b.tar新镜像
3.在b镜像创造的容器中可以看到a.txt
8.实现docker容器环境变量传递
实现:mydocker run -e flag,支持在启动容器时指定环境变量,让容器内运行的程序可以使用外部传递的环境变量
原理:
在构建 cmd 的时候指定 Env 参数。
1)run 命令增加 -e 参数
2)cmd 中指定 Env 参数
在exec命令fork新进程后,在cmd加入原有的pid的环境变量:
cmd.Env = append(os.Environ(), env...)
path := fmt.Sprintf("/proc/%s/environ", pid) contentBytes, err := os.ReadFile(path) envs := strings.Split(string(contentBytes), "\u0000")
效果:
设置环境变量后,后台运行:
exec命令进入容器后,环境变量依然生效: