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