一)、简单实现run、init命令
1.主要使用urfave/cli来实现命令行工具
main.go:创建cli实例、设置cli名字与用途、定义cli应用命令列表,将命令行输入的参数带入运行文件。
main_command.go文件:设置initCommand、runCommand为cli.Command类型:
其中属性Flags设置了命令的可选参数数组;
Action则为具体执行函数:
- ti命令作用是将os/exec包的cmd对象的输入输出连接到终端,方便与命令交互
- runcommand:通过执行、/proc/self/exe调用当前文件创建出新的进程,第一个参数为init,exec.cmd.start会执行资源更新。
- initcommand:容器执行的第一个进程,使用mount去挂载proc文件系统,便于以后查看文件;syscall.exec执行了内核的execve调用,作用是执行当前的filename对应的程序,覆盖当前进程的镜像、数据、堆栈信息,使得PID为1的进程是用户命令。
效果如下:
bug:在第二次进入容器时出现panic:
原因:代码中会讲容器进程的proc信息挂载为proc文件系统,在mydocker init进行,但是退出后容器进程消失,对应进程不存在,但是linux中mount namespace 是share by default 所以说宿主机上的proc目录被影响,执行/proc/self/exe出错,每次执行都会破坏/proc
执行:
sudo mount -t proc proc /proc
或者修改代码:在挂载之前,将所有mount事件显示的指定为private即可避免外泄。
syscall.Mount("","/","",system.MS_PRIVATE|syscall.MS_REC,"")
2.优化参数传递方式-匿名管道
介绍:匿名管道是一种特殊的文件描述符,用父进程与子进程之间创建通信通道
特点:数据是单向的、管道缓冲区固定,一般是4kb,管道被写满时写进程阻塞,管道为空时读进程阻塞。
使用:readPipe,writePipe,err := os.Pipe()
父进程把数据写入到writePipe,子进程从readPipe数据读。
应用:
1.在创建子进程之前将readPipe带入。
cmd.ExtraFiles = []*os.File{readPipe}
2.子进程创建并启动后,将命令参数都写入写管道。
3.子进程读取管道数据:
读取进程的第四个文件描述符:readPipe第一步携带的。
pipe := os.NewFile(uintptr(3),"pipe")
message,err := io.ReadAll(pipe)
此时的run流程:创建exec.cmd实例、执行程序本身+init资源初始化、子进程携带管道返回、启动,发送数据
init流程:读取管道的命令数据,mount挂载proc,调用execve覆盖当前进程信息。
3.基于cgroups实现资源限制
1.给run命令的flag加mem、cpu、cpuset,从命令行中解析参数并传给subsystem以配置cgroup
2.各个subsystem实现对cgroup的某资源控制
3.cgroupManger来管理每一个subsystem
1. 给对应路径的cgroup创建cgroupManager
2.根据资源配置信息循环运行subsystem对该cgroup的限制
3.在子进程创建后创建cgroup ls
bug:命令行设置了mem标签来限制内存大小,但是不起作用?
原因:我的内核使用的cgroup版本v2没有memory.limit_in_bytes文件
解决:
通过修改文件并重启,可以切换为cgoup v1版本
sudo nano /etc/default/grub
//修改值:
GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=0 systemd.unified_cgroup_hierarchy=0"
sudo uodate-grub
重启即可
//再次编译运行程序:
sudo ./myDocker run -it -mem 3m /bin/sh
发现内存限制仍然没有被应用,但是cgroup的memory.limit_in_bytes文件值已经修改为我们键入的值。
原因:程序进程使用了swap空间,memory.swappiness文件值为0-100数值越高,使用swap空间的权重越高。
解决:
sudo nano memory.swappiness
修改值为0即可
重新编译运行即可
效果如下:
4.基于cgroup的v2版本实现资源控制
(1).怎么查看自己系统内核的cgroup?
stat -fc %T /sys/fs/cgroup/
结果为tmpfs内核的cgoup版本为v1,为cgroup2fs的内核的cgroup版本为v2
(2.).怎么切换内核的cgoup版本?
修改/etc/default/grub文件,将值修改为下方的任一版本即可。
//v1:
GRUB_CMDLINE_LINUX=“cgroup_enable=memory swapaccount=0 systemd.unified_cgroup_hierarchy=0”
//v2
GRUB_CMDLINE_LINUX=“”
sudo uodate-grub
重启即可
(3).cgroup v2的修改subsystem配置信息的文件
以 cpu 为例,只需要在 cpu.max 中添加具体限制即可,就像这样:
echo 5000 10000 > cpu.max
含义是在10000的CPU时间周期内,有5000是分配给本cgroup的,也就是本cgroup管理的进程在单核CPU上的使用率不会超过50%
(4).v1与v2的对比:
v1:cgroup的cgroup subsystem的各个信息收录在单独的文件目录中,每个目录就代表了一个 cgroup subsystem,比如要限制 cpu 则需要到 cpu 目录下创建子目录(树),用户空间最后管理着多个非常类似的 hierarchy,
v2:cgroup将统一/sys/fs/cgroup/GROUPNAME中的树,当进程加入cgroup是会被配置所有的信息。