简介

在上篇中对容器和镜像实现了进一步的文件隔离,是容器内的修改不影响到宿主机。本篇中将实现docker中的volume,提供持久化存储能力

源码说明

同时放到了Gitee和Github上,都可进行获取

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

本章节对应的版本标签是: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.判断参数是否包含command2.获取用户指定的command3.调用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=" + parentUrlcmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", containerUrl)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif 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.Stdoutcmd.Stderr = os.Stderrif 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

自己动手写Docker系列 -- 4.3实现volume数据卷相关推荐

  1. 自己动手写Docker系列 -- 5.7实现通过容器制作镜像

    简介 在上篇中我们实现了rm命令,删除存在的容器,本篇中,将完善之前的文件系统隔离,实现容器与容器之间的文件系统隔离 源码说明 同时放到了Gitee和Github上,都可进行获取 Gitee: htt ...

  2. 自己动手写Docker系列 -- 6.3 手动配置容器网络(下)

    简介 网络部分较为复杂,本篇先利用之前写好的基础容器和网桥部分,加上手工给容器配置网络,让其容器与外部网络部分功能正常,为后面程序编写打下基础 源码说明 同时放到了Gitee和Github上,都可进行 ...

  3. 【无标题】自己动手写Docker系列 -- 6.3 手动配置容器网络(上)

    简介 网络部分较为复杂,本篇先利用之前写好的基础容器和网桥部分,加上手工给容器配置网络,让其容器与宿主机网络部分功能正常,为后面程序编写打下基础 源码说明 同时放到了Gitee和Github上,都可进 ...

  4. 自己动手写Docker系列 -- 3.1构造实现run命令版本的容器

    简介 通过对前面Linux的Namespace.Cgroups.Union File System的学习,对Docker实现的基础知识有了一点点了解,接下来就跟着作者开始编写 源码说明 同时放到了Gi ...

  5. 【Docker系列 Swarm】 swarm volume 数据持久化

    Docker Swarm volume 数据持久化 volume 是将宿主级的目录映射到容器中,以实现数据持久化. 可以用两种方式来实现: volume 默认模式:工作节点宿主机数据同步到容器内. v ...

  6. 自己动手写Docker系列 -- 4.1使用busybox创建容器

    简介 目前docker demo中还是使用的系统原有proc,不怎么纯净,本篇中使用busybox来更换docker demo的系统挂载点 源码说明 同时放到了Gitee和Github上,都可进行获取 ...

  7. 自己动手写Docker系列 -- 5.4实现进入容器的namespace,exec命令

    简介 在上篇中我们实现了将容器后台运行,本篇中我们将实现docker的ps命令,查看当前正在运行中的容器列表 源码说明 同时放到了Gitee和Github上,都可进行获取 Gitee: https:/ ...

  8. 自己动手写Docker系列 -- 5.2实现查看运行中的容器

    简介 在上篇中我们实现了将容器后台运行,本篇中我们将实现docker的ps命令,查看当前正在运行中的容器列表 源码说明 同时放到了Gitee和Github上,都可进行获取 Gitee: https:/ ...

  9. 自己动手写Docker系列 -- 5.1实现容器的后台运行

    简介 在前几篇中,我们已经构建了一个基础的镜像,本篇开始做一些进阶的功能,下面就是实现docker中的-d命令,让容器能够后台运行 源码说明 同时放到了Gitee和Github上,都可进行获取 Git ...

最新文章

  1. java linux路径 home_根据linux自带的JDK,配置JAVA_HOME目录
  2. Flask-hello程序
  3. 滴滴基于 Flink 的实时数仓建设实践
  4. Selenium + Grid + Testng并发运行用例
  5. Android开发之AlertDialog设置左右边距的间接办法
  6. java中你知道这四种代码块吗?
  7. JS滚动条位置,顶部,底部,触发事件
  8. 小程序 | 获取用户头像信息接口改进:getUserInfo的使用
  9. java中有cin格式吗,C中std :: cin对象的规则是什么?
  10. 高德地图上线武汉千家商超信息 可预约团购、查营业时间和电话
  11. 《Linux4.0设备驱动开发详解》笔记--第五章:Linux文件系统与设备文件
  12. DRDS 柔性事务漫谈
  13. 海康VisionMaster定位任务
  14. 我的世界服务器显示fps,我的世界提升fps的方法 低配玩家必备秘籍
  15. java的dataset怎么用,C# DataSet的基本用法
  16. 腾讯支持html5吗,WebQQ全面升级支持IE9 充分运用HTML5优势
  17. 等保测评机构推荐证书撤销,纳入国家认证体系
  18. 250部世界公认的经典大片
  19. 使用原汁原味的Java 语言
  20. Discuz对不起,您安装的不是正版应用的解决办法

热门文章

  1. 为bootstrap+angularJs打造的表格代码生成器
  2. 在运行时切换 WinForm 程序的界面语言 System.ComponentModel.ComponentResourceManager .ApplyResources...
  3. 关于IE的RegExp.exec
  4. 一位Erlang程序员的自白
  5. mysql 8创建远程访问用户以及连接mysql速度慢的解决方法
  6. win10系统更新补丁时进度条一直卡在0%不动的解决方案
  7. 【白皮书分享】2020年度薪酬白皮书.pdf(附下载链接)
  8. 组卷积(group convolution)
  9. AJAX框架衣柜推拉门设计,带镜子的推拉门衣柜如何设计好看
  10. 三维图像处理_【图像处理】用于三维物体检测的三维骨干网络