简介
在上篇中对容器和镜像实现了进一步的文件隔离,是容器内的修改不影响到宿主机。本篇中将实现 docker 中的 volume,提供持久化存储能力
源码说明
同时放到了 Gitee 和 Github 上,都可进行获取
本章节对应的版本标签是:4.3,防止后面代码过多,不好查看,可切换到标签版本进行查看
代码实现
在上篇中,我们实现了容器和镜像的文件隔离,在容器内的修改不会影响到宿主机内
但我们也会有一些持久化的存储,在容器中操作后,想要保存下来,便于后序查看或者重启后进行加载,对应 docker 中的 -v 参数
这里的原理还是同上篇中的一样,也是使用文件挂载的方式,不同于上篇的是,这个-v 的挂载只卸载卷,但不删除文件,这样文件就保留了下来
代码也不是太复杂,直接上代码,相比较书中的代码,稍微做了一些结构上的调整和优化
新增 -v 命令参数
我们在 RunCommand 中增加-v 命令参数,和 docker 中的-v 一样
需要注意的是,目前暂时单数据卷挂载,还不能像 docker 一样提供多个-v,但影响不大
var RunCommand = cli.Command{
Name: "run",
Usage: `Create a container with namespace and cgroups limit mydocker run -ti [command]`,
Flags: []cli.Flag{
......
// 添加-v标签
cli.StringFlag{
Name: "v",
Usage: "volume",
},
},
/*
这里是run命令执行的真正函数
1.判断参数是否包含command
2.获取用户指定的command
3.调用Run function 去准备启动容器
*/
Action: func(context *cli.Context) error {
......
volume := context.String("v")
run.Run(tty, cmdArray, resConfig, volume)
return nil
},
}
复制代码
上面加参数传入了 Run 函数中,在 Run 函数中,我们将其继续传递到进程启动的初始化进程和退出时的清理函数中
func Run(tty bool, cmdArray []string, config *subsystem.ResourceConfig, volume string) {
......
mntUrl := pwd + "/mnt/"
rootUrl := pwd + "/"
// 传入初始化进程中
parent, writePipe := container.NewParentProcess(tty, rootUrl, mntUrl, volume)
if err := parent.Start(); err != nil {
log.Error(err)
// 如果fork进程出现异常,但有相关的文件已经进行了挂载,需要进行清理,避免后面运行报错时,需要手工清理
deleteWorkSpace(rootUrl, mntUrl, volume)
return
}
......
log.Infof("parent process run")
_ = parent.Wait()
// 传入退出时的清理函数中
deleteWorkSpace(rootUrl, mntUrl, volume)
os.Exit(-1)
}
复制代码
创建容器文件系统
在进程初始化函数中,会创建容器文件系统,和上篇文件中一样,我们只是在 newWorkSpace 函数中新增一个函数,来挂载持久化数据卷即可
回顾下,这个核心函数是功能大致如下:
1.创建只读层 2.创建容器读写层 3.创建挂载点并将只读层和读写层挂载到挂载点上
下面我们要增加的是:
4.在容器内创建对应的数据卷,并将其挂载到挂载点上
我们这步新增的需要在第三步后面,因为需要挂载点已经准备就绪
func newWorkSpace(rootUrl, mntUrl, volume 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
}
// 在容器内创建对应的数据卷,并将其挂载到挂载点上
if err := mountExtractVolume(mntUrl, volume); err != nil {
return err
}
return nil
}
复制代码
挂载数据卷的具体处理如下:
1.如果参数有效才进行挂载操作:空直接返回;参数错误则报错 2.如果宿主机中的文件路径不存在,需要进行创建(书中是使用 mkdir,这样如果多级目录时,上级目录没有时会报错,这里 mkdirall 递归创建)3.在容器读写层创建对应到容器内的文件 4.将宿主机文件进行挂载
具体实现如下:
func mountVolume(mntUrl string, volumeUrls []string) error {
// 如果宿主机文件目录不存在则创建
parentUrl := volumeUrls[0]
exist, err := pathExist(parentUrl)
if err != nil && !os.IsNotExist(err) {
return err
}
if !exist {
// 使用mkdir all 递归创建文件夹
if err := os.MkdirAll(parentUrl, 0777); err != nil {
return fmt.Errorf("mkdir parent dir err: %v", err)
}
}
// 在容器文件系统内创建挂载点
containerUrl := mntUrl + volumeUrls[1]
if err := os.Mkdir(containerUrl, 0777); err != nil {
return fmt.Errorf("mkdir container volume err: %v", err)
}
// 把宿主机文件目录挂载到容器挂载点
dirs := "dirs=" + parentUrl
cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", containerUrl)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := 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
}
复制代码
容器退出时的清理
在上篇中清理动作是直接卸载了挂载点,并删除了读写层
我们这次的数据卷是需要持久化保存的,只需要进行将挂载点卸载即可
具体实现如下:
func deleteWorkSpace(rootUrl, mntUrl, volume string) {
// 这里在删除挂载点之前,把数据卷卸载即可
// 后面的删除挂载点和删除读写层后,不会影响宿主机的文件
unmountVolume(mntUrl, volume)
deleteMountPoint(mntUrl)
deleteWriteLayer(rootUrl)
}
复制代码
unmountVolume 具体实现如下:
func unmountVolume(mntUrl string, volume string) {
if volume == "" {
return
}
volumeUrls := strings.Split(volume, ":")
if len(volumeUrls) != 2 || volumeUrls[0] == "" || volumeUrls[1] == "" {
return
}
// 卸载容器内的 volume 挂载点的文件系统
containerUrl := mntUrl + volumeUrls[1]
cmd := exec.Command("umount", containerUrl)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Errorf("ummount volume failed: %v", err)
}
}
复制代码
运行测试
编译一波代码,在容器内创建一个文件
➜ dockerDemo git:(main) ✗ go build mydocker/main.go
➜ dockerDemo git:(main) ✗ ./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.txt
复制代码
我们新开一个 sh 看看宿主机的情况,可以看到文件在宿主机中也有
➜ ~ ls /root/volumn/test
test.txt
复制代码
然后我们退出容器,可以看到当前运行目录下的文件已经被清理掉了
/ # exit
➜ dockerDemo git:(main) ✗ ll
总用量 4.6M
drwxr-xr-x 12 root root 4.0K 3月 17 06:17 busybox
drwxrwxr-x 2 lw lw 4.0K 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.md
复制代码
我们再次去查看宿主机的文件,发现依旧存在,目的达成
➜ ~ ls /root/volumn/test
test.txt
复制代码
评论