简介

在上篇中我们实现了rm命令,删除存在的容器,本篇中,将完善之前的文件系统隔离,实现容器与容器之间的文件系统隔离

源码说明

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

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

本章节对应的版本标签是:5.7,防止后面代码过多,不好查看,可切换到标签版本进行查看

代码实现

实现该功能的主要思路如下:

在以前的文章:自己动手写Docker系列 – 4.2使用AUFS包装busybox

实现了容器文件系统与宿主机文件的隔离,但目前为止,所有的容器都是使用的同一个目录,容器与容器之间存在相互影响

本篇文章目的就是为了消除这边影响,实现容器与容器之间的文件系统也进行隔离

实现思路:

以前的文件系统如下:

  • 只读层:busybox系统,这个只能读,是系统的基础
  • 可写层:writerLayer,这个是容器内部的可写层,能进行对应的修改
  • 挂载层:mnt,挂载外部的文件系统,类似于虚拟机的文件共享

要实现容器间的文件系统隔离,就是在可写层和挂载层再加一层,以容器名称进行隔离,也就是:

  • 只读层:不变
  • 可写层:再加容器名为名的目录,进行隔离,也就是 writeLayer/{容器名称}
  • 挂载层:再加容器名为名的目录,进行隔离,也就是 mnt/{容器名称}

文件系统进行隔离后,我们commit的时候,对应的对容器可写层进程打包即可

根据思路,代码实现也比较简单,自己理清思路后,很快便能进行改造实现

修改容器启动时可写层和挂载层,以容器名进行隔离

在容器启动的时候,获取当前的容器名,用于构建相关的隔离目录

func Run(tty, detach bool, cmdArray []string, config *subsystem.ResourceConfig, volume, containerName string) {// 容器容器名id, containerName := getContainerName(containerName)pwd, err := os.Getwd()if err != nil {log.Errorf("Run get pwd err: %v", err)return}mntUrl := pwd + "/mnt/"rootUrl := pwd + "/"// 传入初始化进程,初始化工作空间parent, writePipe := container.NewParentProcess(tty, containerName, rootUrl, mntUrl, volume)if err := parent.Start(); err != nil {log.Error(err)// 如果fork进程出现异常,但有相关的文件已经进行了挂载,需要进行清理,避免后面运行报错时,需要手工清理// 删除容器工作空间进行改造deleteWorkSpace(rootUrl, mntUrl, volume, containerName)return}// 记录容器信息进行改造containerName, err = recordContainerInfo(parent.Process.Pid, cmdArray, id, containerName)if err != nil {log.Errorf("record contariner info err: %v", err)return}......log.Infof("parent process run")if !detach {_ = parent.Wait()// 删除容器工作空间进行改造deleteWorkSpace(rootUrl, mntUrl, volume, containerName)// 删除容器信息进行改造deleteContainerInfo(containerName)}os.Exit(-1)
}

获取容器名

func getContainerName(containerName string) (string, string) {id := randStringBytes(10)if containerName == "" {containerName = id}return id, containerName
}

生成容器信息

func recordContainerInfo(pid int, cmdArray []string, id, containerName string) (string, error) {createTime := time.Now().Format("2000-01-01 00:00:00")command := strings.Join(cmdArray, " ")containerInfo := &container.ContainerInfo{ID:         id,Pid:        strconv.Itoa(pid),Command:    command,CreateTime: createTime,Status:     container.RUNNING,Name:       containerName,}jsonBytes, err := json.Marshal(containerInfo)if err != nil {return "", fmt.Errorf("container info to json string err: %v", err)}jsonStr := string(jsonBytes)dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)if err := os.MkdirAll(dirUrl, 0622); err != nil {return "", fmt.Errorf("mkdir %s err: %v", dirUrl, err)}fileName := dirUrl + "/" + container.ConfigNamefile, err := os.Create(fileName)defer file.Close()if err != nil {return "", fmt.Errorf("create file %s, err: %v", fileName, err)}if _, err := file.WriteString(jsonStr); err != nil {return "", fmt.Errorf("file write string err: %v", err)}return containerName, nil
}

删除容器信息

func deleteContainerInfo(containerName string) {dirUrl := fmt.Sprintf(container.DefaultInfoLocation, containerName)if err := os.RemoveAll(dirUrl); err != nil {log.Errorf("remove dir %s err: %v", dirUrl, err)}
}

删除容器工作空间

func deleteWorkSpace(rootUrl, mntUrl, volume, containerName string) {unmountVolume(mntUrl, volume, containerName)deleteMountPoint(mntUrl + containerName + "/")deleteWriteLayer(rootUrl, containerName)
}func unmountVolume(mntUrl, volume, containerName string) {if volume == "" {return}volumeUrls := strings.Split(volume, ":")if len(volumeUrls) != 2 || volumeUrls[0] == "" || volumeUrls[1] == "" {return}// 卸载容器内的 volume 挂载点的文件系统containerUrl := mntUrl + containerName + "/" + 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)}
}func deleteMountPoint(mntUrl string) {cmd := exec.Command("umount", mntUrl)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {log.Errorf("deleteMountPoint umount %s err : %v", mntUrl, err)}if err := os.RemoveAll(mntUrl); err != nil {log.Errorf("deleteMountPoint remove %s err : %v", mntUrl, err)}
}func deleteWriteLayer(rootUrl, containerName string) {writeUrl := rootUrl + "writeLayer/" + containerNameif err := os.RemoveAll(writeUrl); err != nil {log.Errorf("deleteMountPoint remove %s err : %v", writeUrl, err)}
}

下面是容器初始化时,构造容器工作空间的改造

func NewParentProcess(tty bool, containerName, rootUrl, mntUrl, volume string) (*exec.Cmd, *os.File) {......// 将管道的一端传入fork的进程中cmd.ExtraFiles = []*os.File{readPipe}if err := newWorkSpace(rootUrl, mntUrl, volume, containerName); err != nil {log.Errorf("new work space err: %v", err)return nil, nil}cmd.Dir = mntUrlreturn cmd, writePipe
}func newWorkSpace(rootUrl, mntUrl, volume, containerName string) error {if err := createReadOnlyLayer(rootUrl); err != nil {return err}if err := createWriteLayer(rootUrl, containerName); err != nil {return err}if err := createMountPoint(rootUrl, mntUrl, containerName); err != nil {return err}if err := mountExtractVolume(mntUrl, volume, containerName); err != nil {return err}return nil
}// 我们直接把busybox放到了工程目录下,直接作为容器的只读层
func createReadOnlyLayer(rootUrl string) error {busyboxUrl := rootUrl + "busybox/"exist, err := pathExist(busyboxUrl)if err != nil {return err}if !exist {return fmt.Errorf("busybox dir don't exist: %s", busyboxUrl)}return nil
}// 创建一个名为writeLayer的文件夹作为容器的唯一可写层
func createWriteLayer(rootUrl, containerName string) error {writeUrl := rootUrl + "writeLayer/" + containerName + "/"exist, err := pathExist(writeUrl)if err != nil && !os.IsNotExist(err) {return err}if !exist {if err := os.MkdirAll(writeUrl, 0777); err != nil {return fmt.Errorf("create write layer failed: %v", err)}}return nil
}func createMountPoint(rootUrl, mntUrl, containerName string) error {// 创建mnt文件夹作为挂载点mountPath := mntUrl + containerName + "/"exist, err := pathExist(mountPath)if err != nil && !os.IsNotExist(err) {return err}if !exist {if err := os.MkdirAll(mountPath, 0777); err != nil {return fmt.Errorf("mkdir faild: %v", err)}}// 把writeLayer和busybox目录mount到mnt目录下dirs := "dirs=" + rootUrl + "writeLayer:" + rootUrl + "busybox"cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mountPath)cmd.Stdout = os.Stdoutcmd.Stderr = os.Stderrif err := cmd.Run(); err != nil {return fmt.Errorf("mmt dir err: %v", err)}return nil
}func mountExtractVolume(mntUrl, volume, containerName string) error {if volume == "" {return nil}volumeUrls := strings.Split(volume, ":")length := len(volumeUrls)if length != 2 || volumeUrls[0] == "" || volumeUrls[1] == "" {return fmt.Errorf("volume parameter input is not corrent")}return mountVolume(mntUrl+containerName+"/", volumeUrls)
}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.MkdirAll(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
}

这样,就把容器启动时,相关之间根据容器名,进行了目录隔离

修改commit命令,打包容器对应目录

修改下commit命令,根据传入的容器名,打包对应的目录

var CommitCommand = cli.Command{Name:  "commit",Usage: "commit a container into image",Action: func(context *cli.Context) error {if len(context.Args()) < 1 {return fmt.Errorf("Missing container name")}containerName := context.Args().Get(0)return run.CommitContainer(containerName)},
}

具体的打包实现

func CommitContainer(containerName string) error {pwd, err := os.Getwd()if err != nil {return fmt.Errorf("Run get pwd err: %v", err)}mntUrl := pwd + "/mnt/" + containerNameimageTar := pwd + "/" + containerName + ".tar"log.Infof("commit file path: %s", imageTar)if _, err := exec.Command("tar", "-czf", imageTar, "-C", mntUrl, ".").CombinedOutput(); err != nil {return fmt.Errorf("tar folder err: %s, %v", mntUrl, err)}log.Infof("end commit file: %s", imageTar)return nil
}

自己动手写Docker系列 -- 5.7实现通过容器制作镜像相关推荐

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

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

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

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

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

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

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

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

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

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

  6. 自己动手写Docker系列 -- 4.2使用AUFS包装busybox

    简介 在上篇中实现了使用宿主机busybox目录作为文件的根目录,但在容器内的对文件的操作仍会影响到宿主机的目录,本篇中实现进一步的容器和镜像隔离 源码说明 同时放到了Gitee和Github上,都可 ...

  7. 自己动手写Docker系列 -- 4.3实现volume数据卷

    简介 在上篇中对容器和镜像实现了进一步的文件隔离,是容器内的修改不影响到宿主机.本篇中将实现docker中的volume,提供持久化存储能力 源码说明 同时放到了Gitee和Github上,都可进行获 ...

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

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

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

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

最新文章

  1. 计算类class的sizeof大小
  2. Java 8系列之Stream中万能的reduce
  3. html简单的图片切换js,一分钟让你学会如何使用js切换图片
  4. android studio插件提升工作效率
  5. 2021.4.23最新mac11.1 big sur 关于CocoaPods安装和使用
  6. Git 简单命令行指令
  7. 前端学习(1153):常量const01
  8. 三种Cache写入方式原理简介
  9. css直接子元素怎么用,CSS 子元素选择器使用实例
  10. python中ndarray除_Numpy 基本除法运算和模运算
  11. 永磁同步电机SVPWM过调制电压重构MTPA弱磁矢量控制仿真 模型
  12. c语言用标准体重判断是否,输入身高、体重、性别,判断是否是标准体重,男性标准=(身高-100)+-3,女性标准=(身高-110)+-3...
  13. Cocos Creator 入门笔记
  14. MATLAB彩色图像读写
  15. keil5 芯片包下载系列
  16. win10 应用程序exe图标突然变成白色,怎么恢复?
  17. python是什么?为何被称为胶水语言?
  18. python读取dat文件经纬度_自动提取kml文件中的经纬度
  19. potplayer最佳设置_PotPlayer调整常用设置让播放器效果更佳的具体操作流程
  20. 适配Android Q上读取多媒体文件

热门文章

  1. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)
  2. Junit中的异常测试
  3. iOS 10.3下解决Fiddler代理抓包ssl证书信任问题
  4. jenkins构建后接受者收不到邮件问题解决方案
  5. 基于商品包含关系的飞猪搜索优化实践
  6. 【干货】完美日记增长策略深度研究报告.pdf(附下载链接)
  7. 全球首发!惯性导航导论(剑桥大学)第四部分
  8. 腾讯广告算法大赛 | 第三周周冠军心得分享
  9. 更小的模型,迈向更快更环保的NLP
  10. vbs连接oracle11,vbs连oracle数据库