简介
在上篇中实现了使用宿主机busybox目录作为文件的根目录,但在容器内的对文件的操作仍会影响到宿主机的目录,本篇中实现进一步的容器和镜像隔离
源码说明
同时放到了Gitee和Github上,都可进行获取
本章节对应的版本标签是:4.2,防止后面代码过多,不好查看,可切换到标签版本进行查看
代码编写
本章中直接借鉴书中的代码即可,基本可以运行
首先修改Run命令启动入口,在其中传入相关的路径参数和在容器退出时候清理相关文件
func Run(tty bool, cmdArray []string, config *subsystem.ResourceConfig) {
pwd, err := os.Getwd()
if err != nil {
log.Errorf("Run get pwd err: %v", err)
return
}
mntUrl := pwd + "/mnt/"
rootUrl := pwd + "/"
// 将新建的只读层和可写层进行隔离
parent, writePipe := container.NewParentProcess(tty, rootUrl, mntUrl)
if err := parent.Start(); err != nil {
log.Error(err)
return
}
cgroupManager := cgroup.NewCgroupManager("mydocker-cgroup")
defer cgroupManager.Destroy()
if err := cgroupManager.Apply(parent.Process.Pid); err != nil {
log.Errorf("cgroup apply err: %v", err)
return
}
if err := cgroupManager.Set(config); err != nil {
log.Errorf("cgoup set err: %v", err)
return
}
sendInitCommand(cmdArray, writePipe)
log.Infof("parent process run")
_ = parent.Wait()
// 在容器退出的时候删除相关
deleteWorkSpace(rootUrl, mntUrl)
os.Exit(-1)
}
// 将运行参数写入管道
func sendInitCommand(array []string, writePipe *os.File) {
command := strings.Join(array, " ")
log.Infof("all command is : %s", command)
if _, err := writePipe.WriteString(command); err != nil {
log.Errorf("write pipe write string err: %v", err)
return
}
if err := writePipe.Close(); err != nil {
log.Errorf("write pipe close err: %v", err)
}
}
func deleteWorkSpace(rootUrl, mntUrl string) {
deleteMountPoint(mntUrl)
deleteWriteLayer(rootUrl)
}
func deleteMountPoint(mntUrl string) {
cmd := exec.Command("umount", mntUrl)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Errorf("deleteMountPoint umount %s err : %v", mntUrl, err)
}
if err := os.RemoveAll(mntUrl); err != nil {
log.Errorf("deleteMountPoint remove %s err : %v", mntUrl, err)
}
}
func deleteWriteLayer(rootUrl string) {
writeUrl := rootUrl + "writeLayer/"
if err := os.RemoveAll(writeUrl); err != nil {
log.Errorf("deleteMountPoint remove %s err : %v", writeUrl, err)
}
}
Docker会在删除容器的时候,把容器对应的Write Layer和Container-init Layer删除,而保留镜像所有的内容。本节中,在容器退出的时候会删除Write Layer。DeleteWorkSpace函数包括DeleteMountPoint和DeleteWriteLayer。
首先,在DeleteMountPoint函数中umount mnt目录。
然后,删除mnt目录。
最后,在DeleteWriteLayer函数中删除writeLayer文件夹。这样容器对文件系统的更改就都已经抹去了。
下面是创建只读层和可写层的代码:
修改NewParentProcess
func NewParentProcess(tty bool, rootUrl, mntUrl string) (*exec.Cmd, *os.File) {
readPipe, writePipe, err := os.Pipe()
if err != nil {
log.Errorf("create pipe error: %v", err)
return nil, nil
}
cmd := exec.Command("/proc/self/exe", "init")
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
}
if tty {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
}
// 将管道的一端传入fork的进程中
cmd.ExtraFiles = []*os.File{readPipe}
if err := newWorkSpace(rootUrl, mntUrl); err != nil {
log.Errorf("new work space err: %v", err)
return nil, nil
}
cmd.Dir = mntUrl
return cmd, writePipe
}
func newWorkSpace(rootUrl string, mntUrl string) error {
if err := createReadOnlyLayer(rootUrl); err != nil {
return err
}
if err := createWriteLayer(rootUrl); err != nil {
return err
}
if err := createMountPoint(rootUrl, mntUrl); err != nil {
return err
}
return nil
}
// 我们直接把busybox放到了工程目录下,直接作为容器的只读层
func createReadOnlyLayer(rootUrl string) error {
busyboxUrl := rootUrl + "busybox/"
exist, err := pathExist(busyboxUrl)
if err != nil {
return err
}
if !exist {
return fmt.Errorf("busybox dir don't exist: %s", busyboxUrl)
}
return nil
}
// 创建一个名为writeLayer的文件夹作为容器的唯一可写层
func createWriteLayer(rootUrl string) error {
writeUrl := rootUrl + "writeLayer/"
if err := os.Mkdir(writeUrl, 0777); err != nil {
return fmt.Errorf("create write layer failed: %v", err)
}
return nil
}
func createMountPoint(rootUrl string, mntUrl string) error {
// 创建mnt文件夹作为挂载点
if err := os.Mkdir(mntUrl, 0777); err != nil {
return fmt.Errorf("mkdir faild: %v", err)
}
// 把writeLayer和busybox目录mount到mnt目录下
dirs := "dirs=" + rootUrl + "writeLayer:" + rootUrl + "busybox"
cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mntUrl)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("mmt dir err: %v", err)
}
return nil
}
func pathExist(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, err
}
return false, err
}
NewWorkSpace函数是用来创建容器文件系统的,它包括CreateReadOnlyLayer、CreateWriteLayer和CreateMountPoint。
CreateReadOnlyLayer函数新建busybox文件夹,将busybox.tar解压到busybox目录下,作为容器的只读层。
CreateWriteLayer函数创建了一个名为writeLayer的文件夹,作为容器唯一的可写层。
在CreateMountPoint函数中,首先创建了mnt文件夹,作为挂载点,然后把writeLayer目录和busybox目录mount到mnt目录下。
最后,在NewParentProcess函数中将容器使用的宿主机目录/root/busybox替换成/root/mnt。
运行测试
我们运行容器后,在里面创建一个文件:
➜ dockerDemo git:(main) ✗ ./main run -ti sh
{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-18T20:38:46+08:00"}
{"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-18T20:38:46+08:00"}
{"level":"info","msg":"all command is : sh","time":"2022-03-18T20:38:46+08:00"}
{"level":"info","msg":"parent process run","time":"2022-03-18T20:38:46+08:00"}
{"level":"info","msg":"init come on","time":"2022-03-18T20:38:46+08:00"}
{"level":"info","msg":"current location: /home/lw/code/go/dockerDemo/mnt","time":"2022-03-18T20:38:46+08:00"}
{"level":"info","msg":"find path: /bin/sh","time":"2022-03-18T20:38:46+08:00"}
/ # touch /tmp/test.txt
/ # ls /tmp/
test.txt
然后我们在宿主机上查看相关文件夹中的内容:
➜ dockerDemo git:(main) ls busybox/tmp
➜ dockerDemo git:(main) ✗ ls mnt/tmp/
test.txt
我们可以看到在busybox中并没有对应的修改,只修改了我们的只读层
然后我们使用exit退出容器,在退出容器后,宿主机上面的只读层会对应的删除