Make writing a habit together! This is the fifth day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.

Introduction to the

In the previous article, we implemented the busybox directory as the root directory of the file, but operations on the file in the container still affect the directory of the host. In this article, we implement further container and image isolation

The source code that

It is available on both Gitee and Github

  • Gitee: https://gitee.com/free-love/docker-demo
  • GitHub: https://github.com/lw1243925457/dockerDemo

The corresponding version label in this section is 4.2. You can switch to the label version to avoid too much code

The code

In this chapter, the code in the book can be directly referenced, and the basic can run

Start by modifying the Run command startup entry, passing in the relevant path parameters and cleaning up the relevant files when the container exits

func Run(tty bool, cmdArray []string, config *subsystem.ResourceConfig) {
	pwd, err := os.Getwd()
	iferr ! =nil {
		log.Errorf("Run get pwd err: %v", err)
		return
	}
	mntUrl := pwd + "/mnt/"
	rootUrl := pwd + "/"
	// Separate the newly created read-only layer from the writable layer
	parent, writePipe := container.NewParentProcess(tty, rootUrl, mntUrl)
	iferr := parent.Start(); err ! =nil {
		log.Error(err)
		return
	}

	cgroupManager := cgroup.NewCgroupManager("mydocker-cgroup")
	defer cgroupManager.Destroy()
	iferr := cgroupManager.Apply(parent.Process.Pid); err ! =nil {
		log.Errorf("cgroup apply err: %v", err)
		return
	}
	iferr := cgroupManager.Set(config); err ! =nil {
		log.Errorf("cgoup set err: %v", err)
		return
	}

	sendInitCommand(cmdArray, writePipe)

	log.Infof("parent process run")
	_ = parent.Wait()
	// Delete the correlation when the container exits
	deleteWorkSpace(rootUrl, mntUrl)
	os.Exit(- 1)}// Write the running parameters to the pipe
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
	}
	iferr := 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
	iferr := cmd.Run(); err ! =nil {
		log.Errorf("deleteMountPoint umount %s err : %v", mntUrl, err)
	}
	iferr := os.RemoveAll(mntUrl); err ! =nil {
		log.Errorf("deleteMountPoint remove %s err : %v", mntUrl, err)
	}
}

func deleteWriteLayer(rootUrl string) {
	writeUrl := rootUrl + "writeLayer/"
	iferr := os.RemoveAll(writeUrl); err ! =nil {
		log.Errorf("deleteMountPoint remove %s err : %v", writeUrl, err)
	}
}
Copy the code

Docker removes the Write Layer and container-init Layer of the Container while keeping all the contents of the image. In this section, the Write Layer is removed when the container exits. DeleteWorkSpace functions include DeleteMountPoint and DeleteWriteLayer. First, umount the MNT directory in the DeleteMountPoint function. Then, delete the MNT directory. Finally, delete the writeLayer folder in the DeleteWriteLayer function. The container’s changes to the file system have been erased.

Here is the code to create the read-only and writable layers:

Modify NewParentProcess

func NewParentProcess(tty bool, rootUrl, mntUrl string) (*exec.Cmd, *os.File) {
	readPipe, writePipe, err := os.Pipe()
	iferr ! =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
	}

	// Pass one end of the pipe into the fork process
	cmd.ExtraFiles = []*os.File{readPipe}
	iferr := 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 {
	iferr := createReadOnlyLayer(rootUrl); err ! =nil {
		return err
	}
	iferr := createWriteLayer(rootUrl); err ! =nil {
		return err
	}
	iferr := createMountPoint(rootUrl, mntUrl); err ! =nil {
		return err
	}
	return nil
}

// We put busyBox directly in the project directory as the read-only layer of the container
func createReadOnlyLayer(rootUrl string) error {
	busyboxUrl := rootUrl + "busybox/"
	exist, err := pathExist(busyboxUrl)
	iferr ! =nil {
		return err
	}
	if! exist {return fmt.Errorf("busybox dir don't exist: %s", busyboxUrl)
	}
	return nil
}

// Create a folder called writeLayer as the only writable layer of the container
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 {
	// Create the MNT folder as the mount point
	if err := os.Mkdir(mntUrl, 0777); err ! =nil {
		return fmt.Errorf("mkdir faild: %v", err)
	}
	// mount writeLayer and busybox to 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
	iferr := 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
}
Copy the code

The NewWorkSpace function is used to create container file systems, including CreateReadOnlyLayer, CreateWriteLayer, and CreateMountPoint. The CreateReadOnlyLayer function creates a busybox folder and decompresses busybox.tar to the busybox folder as the read-only layer of the container. The CreateWriteLayer function creates a folder called writeLayer as the container’s only writable layer. In the CreateMountPoint function, you first create the MNT folder as a mount point, and then mount the writeLayer and busybox directories to the MNT directory. Finally, replace the host directory /root/busybox used by the container with /root/mnt in the NewParentProcess function.

Run the test

After we run the container, we create a file in it:

➜ dockerDemo git:(main) Qualify./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.txtCopy the code

Then we view the contents of the relevant folder on the host:

➜ dockerDemo git:(main) ls busybox/ TMP ➜ dockerDemo git:(main) qualify ls MNT/TMP/test.txtCopy the code

We can see that there are no corresponding changes in BusyBox, just our read-only layer

We then use exit to exit the container. After exiting the container, the read-only layer on the host will be deleted accordingly