写点什么

自己动手写 Docker 系列 -- 4.2 使用 AUFS 包装 busybox

作者:
  • 2022 年 3 月 20 日
  • 本文字数:3360 字

    阅读完需:约 11 分钟

简介

在上篇中实现了使用宿主机 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 退出容器,在退出容器后,宿主机上面的只读层会对应的删除

发布于: 刚刚阅读数: 2
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
自己动手写Docker系列 -- 4.2使用AUFS包装busybox_Docker_萧_InfoQ写作平台