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

Introduction to the

Further file isolation between containers and images was implemented in the previous article so that changes within the container do not affect the host machine. This article will implement volume in Docker to provide persistent storage capability

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.3. You can switch to the label version to avoid too much code

Code implementation

In the previous article, we implemented file isolation between the container and the image so that changes made in the container do not affect the host

However, we will also have some persistent storage. After operation in the container, we want to save it for later viewing or loading after restart, which corresponds to the -v parameter in docker

The principle here is the same as in the previous article, but also uses the file mount method, different from the previous article, the -v mount only unmounts the volume, but does not delete the file, so that the file is retained

The code is not too complex, directly on the code, compared with the code in the book, made some structural adjustments and optimization

Added the -v command parameter

We add the -v command argument to RunCommand, just like the -v in Docker

It should be noted that currently, single data volume mount can not provide multiple V like Docker, but the impact is not significant

var RunCommand = cli.Command{
	Name:  "run",
	Usage: `Create a container with namespace and cgroups limit mydocker run -ti [command]`,
	Flags: []cli.Flag{
		......
		// Add the -v label
		cli.StringFlag{
			Name:  "v",
			Usage: "volume",}},1. Check whether the argument contains command 2. 3. Call Run function to prepare to start the container */
	Action: func(context *cli.Context) error{... volume := context.String("v")
		run.Run(tty, cmdArray, resConfig, volume)
		return nil}},Copy the code

The above argument is passed to the Run function, where we pass it on to the initializer when the process starts and to the cleanup function when the process exits

func Run(tty bool, cmdArray []string, config *subsystem.ResourceConfig, volume string){... mntUrl := pwd +"/mnt/"
	rootUrl := pwd + "/"
	// passed into the initialization process
	parent, writePipe := container.NewParentProcess(tty, rootUrl, mntUrl, volume)
	iferr := parent.Start(); err ! =nil {
		log.Error(err)
		// If an exception occurs in the fork process, related files have been mounted and need to be cleared to avoid subsequent errors
		deleteWorkSpace(rootUrl, mntUrl, volume)
		return}... log.Infof("parent process run")
	_ = parent.Wait()
	// Clean up the function on exit
	deleteWorkSpace(rootUrl, mntUrl, volume)
	os.Exit(- 1)}Copy the code

Create a container file system

In the process initialization function, the container file system is created. As in the previous article, we simply add a new function in the newWorkSpace function to mount the persistent data volume

To recap, the core function is roughly as follows:

1. Create a read-only layer

2. Create a container read/write layer

3. Create a mount point and mount the read-only layer and read-write layer to the mount point

Here’s what we want to add:

4. Create a data volume in the container and mount it to the mount point

What we add in this step needs to come after step 3, because we need the mount point to be ready

func newWorkSpace(rootUrl, mntUrl, volume string) error {
	iferr := createReadOnlyLayer(rootUrl); err ! =nil {
		return err
	}
	iferr := createWriteLayer(rootUrl); err ! =nil {
		return err
	}
	iferr := createMountPoint(rootUrl, mntUrl); err ! =nil {
		return err
	}
	// Create the corresponding data volume in the container and mount it to the mount point
	iferr := mountExtractVolume(mntUrl, volume); err ! =nil {
		return err
	}
	return nil
}
Copy the code

The procedure for attaching data volumes is as follows:

1. Mount the vm only if the parameter is valid. If the parameter is incorrect, an error is reported

2. If the file path in the host does not exist, you need to create it. (In the book, mkdir is used, so that if there is no higher level directory, an error will be reported.

3. In the read/write layer of the container, create files corresponding to the container

4. Mount the host file

The concrete implementation is as follows:


func mountVolume(mntUrl string, volumeUrls []string) error {
	// If the host file directory does not exist
	parentUrl := volumeUrls[0]
	exist, err := pathExist(parentUrl)
	iferr ! =nil && !os.IsNotExist(err) {
		return err
	}
	if! exist {// use mkdir all to recursively create folders
		if err := os.MkdirAll(parentUrl, 0777); err ! =nil {
			return fmt.Errorf("mkdir parent dir err: %v", err)
		}
	}

	// Create mount points in the container file system
	containerUrl := mntUrl + volumeUrls[1]
	if err := os.Mkdir(containerUrl, 0777); err ! =nil {
		return fmt.Errorf("mkdir container volume err: %v", err)
	}

	// Mount the host file directory to the container mount point
	dirs := "dirs=" + parentUrl
	cmd := exec.Command("mount"."-t"."aufs"."-o", dirs, "none", containerUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	iferr := cmd.Run(); err ! =nil {
		return fmt.Errorf("mount volume 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

Clean up when container exits

The cleanup action in the previous article was to simply unmount the mount point and remove the read/write layer

Our data volume this time needs to be persisted, just need to unload the mount point

The concrete implementation is as follows:

func deleteWorkSpace(rootUrl, mntUrl, volume string) {
	// Unmount the data volume before deleting the mount point
	// Delete the mount point and read/write layer, do not affect the host files
	unmountVolume(mntUrl, volume)
	deleteMountPoint(mntUrl)
	deleteWriteLayer(rootUrl)
}
Copy the code

UnmountVolume is implemented as follows:

func unmountVolume(mntUrl string, volume string) {
	if volume == "" {
		return
	}
	volumeUrls := strings.Split(volume, ":")
	if len(volumeUrls) ! =2 || volumeUrls[0] = ="" || volumeUrls[1] = ="" {
		return
	}

	// Unmount the file system of the volume mount point in the container
	containerUrl := mntUrl + volumeUrls[1]
	cmd := exec.Command("umount", containerUrl)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	iferr := cmd.Run(); err ! =nil {
		log.Errorf("ummount volume failed: %v", err)
	}
}
Copy the code

Run the test

Compile a wave of code to create a file inside the container

➜ dockerDemo git:(main) Qualify go build mydocker/main.go ➜ dockerDemo git:(main) qualify./main run-ti-v /root/volumn/test:/test sh {"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-20T10:15:04+08:00"} {"level":"info","msg":"memory cgroup path: /sys/fs/cgroup/memory/mydocker-cgroup","time":"2022-03-20T10:15:04+08:00"} {"level":"info","msg":"all command is : sh","time":"2022-03-20T10:15:04+08:00"} {"level":"info","msg":"parent process run","time":"2022-03-20T10:15:04+08:00"} {"level":"info","msg":"init come on","time":"2022-03-20T10:15:04+08:00"} {"level":"info","msg":"current location: /home/lw/code/go/dockerDemo/mnt","time":"2022-03-20T10:15:04+08:00"} {"level":"info","msg":"find path: /bin/sh","time":"2022-03-20T10:15:04+08:00"} / # ls bin dev etc home main proc root sys test tmp usr var / # touch /test/test.txt / # ls /test/ test.txtCopy the code

Let’s open a new sh and see what happens on the host. You can see that the files are also on the host

➜  ~ ls /root/volumn/test
test.txt
Copy the code

Then we exit the container and see that the files in the current running directory have been cleaned up

/ # exit ➜ dockerDemo git:(main) onto those who qualify can go onto university. Those who qualify can go onto university. Those who qualify can go onto university 3月 18 20:45 docs drwxrwxr-x 3 lw lw 4.0k 3月 7 04:55 example-rw-rw-r -- 1 lw lw 382 3月 12 10:18 go.mod-rw-rw-r -- 1 lw lw 2.0k 3月 12 10:18 go.sum -rw-rw-r-- 1 lw lw 12K 3月 12 10:18 license-rwxr-xr-x 1 root root 4.6m 3月 20 10:15 main Drwxrwxr-x 6 lw lw 4.0k 3月 12 10:20 mydocker-rw-rw-r -- 1 lw lw 473 3月 12 10:18 readme.mdCopy the code

We went back to the host’s files and found that they were still there

➜  ~ ls /root/volumn/test
test.txt
Copy the code