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

本文档介绍了一种方法来实现Docker容器与镜像的进一步隔离。通过在容器退出时删除WriteLayer,确保对文件系统的更改不会持久化。主要步骤包括创建只读层、可写层,以及在退出时清理工作空间。代码示例展示了如何修改Run命令、创建和删除挂载点以及使用aufs进行挂载。测试结果显示,容器内的修改不会影响到宿主机的只读层。
摘要由CSDN通过智能技术生成

简介

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值