七天用 Go 写个 docker(第四天)

项目源码:点击查看项目源码

镜像

前面我们用namespacecgroup构建了一个简单的容器,但是我们可以发现容器内的目录还是当前运行程序的目录,这里就缺少了镜像这么一个重要的特性。这里我们先用docker拉一个最精简的镜像busybox,它是一个集合了非常多unix工具的箱子,提供了一个非常完整而且小巧的系统。

# 拉取busybox
docker pull busybox
# 运行
docker run -d busybox top -b

# 查看容器ID
docker ps

# 导出容器
docker export -o busybox.tar 


可以看到我们成功导出了容器内容,下面我解压缩一下,然后将此文件夹作为容器的只读层。

mkdir busybox && tar -xvf busybox.tar -C busybox/

注意: 后面为了方便,我们把 busybox.tar 放到 /root 路径下

docker在启动容器时,会新建2个layer,write layer和container-layer,write layer 是容器的唯一可读写层,而container-layer是为容器新建的只读层,这里我们将busybox作为这一层,读写层,我们创建一个writeLayer文件夹作为这一层,然后将这两个文件夹挂载到同一个文件夹下,然后将此文件夹作为容器的启动目录即可,这个文件夹我们命名为mnt

Linux挂载文件夹

这里我们有3个文件夹,mnt writeLayer busybox,我们将writeLayer busybox这两个文件夹挂载到 mnt文件夹下,可以看到这时 mntwriteLayer文件夹都是空的,只有busybox文件夹下有东西,我们挂载之后再看一下

# 挂载
 mount -t aufs -o dirs=/root/writeLayer:/root/busybox none /root/mnt

可以看到,mnt已经出现了 busybox 文件夹里面的东西,这时我们在 writeLayer 文件夹下创建个新的文件夹,然后看看会发生什么

可以清楚的看到,我们对writeLayer做的操作会映射到mnt文件夹下,docker也是以这种方式来实现你每次修改容器内的东西之后并不会对镜像产生影响,因为你做的操作都是writeLayer层的,而镜像是在 busybox,也就是container-init层。

Go实现挂载

func NewWorkSpace(rootPath string, mntPath string, volume string) error {
    // 1. 创建只读层
    err := createReadOnlyLayer(rootPath)
    if err != nil {
        logrus.Errorf("create read only layer, err: %v", err)
        return err
    }
    // 2. 创建读写层
    err = createWriteLayer(rootPath)
    if err != nil {
        logrus.Errorf("create write layer, err: %v", err)
        return err
    }
    // 3. 创建挂载点,将只读层和读写层挂载到指定位置
    err = CreateMountPoint(rootPath, mntPath)
    if err != nil {
        logrus.Errorf("create mount point, err: %v", err)
        return err
    }
    return nil
}

简单来讲就三步,第一步创建只读层,第二步创建读写层,第三步将两者挂载到同一个文件夹下,具体实现也比较简单,就是创建文件夹罢了,这里重点看下怎么挂载吧

func CreateMountPoint(rootPath string, mntPath string) error {
    _, err := os.Stat(mntPath)
    if err != nil && os.IsNotExist(err) {
        err := os.MkdirAll(mntPath, os.ModePerm)
        if err != nil {
            logrus.Errorf("mkdir mnt path, err: %v", err)
            return err
        }
    }

    dirs := fmt.Sprintf("dirs=%s%s:%s%s", rootPath, common.WriteLayer, rootPath, common.BusyBox)
    cmd := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mntPath)
    if err := cmd.Run(); err != nil {
        logrus.Errorf("mnt cmd run, err: %v", err)
        return err
    }
    return nil
}

文章会首发于我微信公众号上,扫码关注,及时获取最新内容

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 3
张qifeng

写的非常好,不知道作者有qq吗?请教一下关于docker目录映射的问题。

  1. docker 启动的容器如果没有使用 -v 设置相关配置文件目录、插件目录映射到宿主主机,那么它默认的数据存储在哪里? 我前几天在elasticsearhc 容器安装了插件,但是版本搞错了,导致容器无法启动,本来想找到容器在宿主主机存储目录删除插件启动就行,但是一直没找到,我这问题困扰了我很久。 想得到您的解答
2年前 评论

@张qifeng 一般是在/var/lib/docker/overlay2/ 目录下面,你可以在该目录下运行 find . -type f | grep 配置文件 来查找试试

2年前 评论

想问一下 作者用的是什么linux发行版,我的wsl以及aliyun上的centos7上都没有aufs

2年前 评论
huifeng (作者) 2年前
huifeng (作者) 2年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!