美文网首页
[mydocker]---一步步实现使用AUFS包装busybo

[mydocker]---一步步实现使用AUFS包装busybo

作者: nicktming | 来源:发表于2019-05-11 09:56 被阅读0次

    1. 准备工作

    1.1 准备环境

    root@nicktming:~/go/src/github.com/nicktming/mydocker# git clone https://github.com/nicktming/mydocker.git
    root@nicktming:~/go/src/github.com/nicktming/mydocker# git checkout code-4.1
    root@nicktming:~/go/src/github.com/nicktming/mydocker# git checkout -b dev-4.2
    

    1.2 本文最终效果

    // 准备busybox镜像
    -----------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# pwd
    /nicktming
    root@nicktming:/nicktming# ls
    busybox.tar
    
    // 根据busybox镜像启动容器
    -----------------------------terminal 02----------------------------------
    root@nicktming:~/go/src/github.com/nicktming/mydocker# git clone https://github.com/nicktming/mydocker.git
    root@nicktming:~/go/src/github.com/nicktming/mydocker# git checkout code-4.2
    root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it -r /nicktming /bin/sh
    2019/04/07 16:31:33 rootPath:/nicktming
    2019/04/07 16:31:33 current path: /nicktming/mnt.
    / # ls
    bin   dev   etc   home  proc  root  sys   tmp   usr   var
    / # ps -l
    PID   USER     TIME  COMMAND
        1 root      0:00 /bin/sh
        5 root      0:00 ps -l
    / # mkdir nicktming && echo "test" > nicktming/test.txt
    / # ls
    bin        dev        etc        home       nicktming  proc       root       sys        tmp        usr        var
    / # cat nicktming/test.txt 
    test
    
    // 查看宿主机中的变化
    -----------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# ls
    busybox  busybox.tar  mnt  writerLayer
    root@nicktming:/nicktming# df -h
    Filesystem      Size  Used Avail Use% Mounted on
    ...
    none             50G  2.7G   45G   6% /nicktming/mnt
    root@nicktming:/nicktming# 
    root@nicktming:/nicktming# cat mnt/nicktming/test.txt 
    test
    root@nicktming:/nicktming# cat writerLayer/nicktming/test.txt 
    test
    
    // 退出容器
    -----------------------------terminal 02----------------------------------
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker# 
    
    // 再次查看宿主机内容
    -----------------------------terminal 01----------------------------------
    oot@nicktming:/nicktming# ls
    busybox  busybox.tar
    root@nicktming:/nicktming# df -h
    Filesystem      Size  Used Avail Use% Mounted on
    ...
    

    2. 存在的问题

    利用busybox创建的容器, 创建文件夹并且创建文件.

    root@nicktming:~/go/src/github.com/nicktming/mydocker# git status
    On branch dev-4.2
    nothing to commit, working directory clean
    root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
    2019/04/06 22:52:43 rootPath:
    2019/04/06 22:52:43 set cmd.Dir by default: /root/busybox
    2019/04/06 22:52:43 current path: /root/busybox.
    / # ls
    bin   dev   etc   home  proc  root  sys   tmp   usr   var
    / # mkdir nicktming && echo "for testing." > nicktming/test.txt
    / # ls
    bin        dev        etc        home       nicktming  proc       root       sys        tmp        usr        var
    / # cat nicktming/test.txt 
    for testing.
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker# 
    

    退出容器后, 查看宿主机的内容.

    root@nicktming:~/busybox# pwd
    /root/busybox
    root@nicktming:~/busybox# ls
    bin  dev  etc  home  nicktming  proc  root  sys  tmp  usr  var
    root@nicktming:~/busybox# cat nicktming/test.txt 
    for testing.
    root@nicktming:~/busybox# 
    

    发现内容在宿主机中也存在, 这样会有一个问题, 其实busybox就是容器的镜像层, 如果多个容器共享该镜像层, 那就会造成容器之间互相看到对方文件, 并且文件覆盖等等问题.

    ----------------------------terminal01-------------------------------------
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
    2019/04/06 23:28:23 rootPath:
    2019/04/06 23:28:23 set cmd.Dir by default: /root/busybox
    2019/04/06 23:28:23 current path: /root/busybox.
    / # ls
    bin   dev   etc   home  proc  root  sys   tmp   usr   var
    / # mkdir nicktming01 && echo "testing 01." > nicktming01/test01.txt
    / # cat nicktming01/test01.txt 
    testing 01.
    // 此时打开terminal02创建另外一个文件夹和文件
    ----------------------------terminal02-------------------------------------
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
    2019/04/06 23:30:38 rootPath:
    2019/04/06 23:30:38 set cmd.Dir by default: /root/busybox
    2019/04/06 23:30:38 current path: /root/busybox.
    / # ls
    bin          dev          etc          home         nicktming01  proc         root         sys          tmp          usr          var
    / # mkdir nicktming02 && echo "testing 02." > nicktming02/test02.txt
    / # cat nicktming02/test02.txt 
    testing 02.
    / # ls
    bin          dev          etc          home         nicktming01  nicktming02  proc         root         sys          tmp          usr          var
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker# 
    // 回到terminal01中可以看到另外一个容器创建的文件夹
    ----------------------------terminal01-------------------------------------
    / # ls
    bin          dev          etc          home         nicktming01  nicktming02  proc         root         sys          tmp          usr          var
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker# 
    

    3. 利用AUFS解决此问题

    正如[mydocker]---一步步实现使用busybox创建容器 所示, 可以利用AUFS解决该问题.

    aufs.png

    正如上图所示, busy为镜像层, 创建一个文件夹writerLayer为容器层, 所有容器内容的操作(增删改文件)都会在此处, 创建一个mnt文件当作挂载点.

    3.1 根据busybox镜像生成容器

    此问题已经在[mydocker]---一步步实现使用busybox创建容器实现. 这里只是做个小改动, 这次使用busybox.tar也就是根据某个镜像来启动容器, 在这里做个镜像就是busybox:latest. busybox.tar 就是由镜像busybox:latest导出的.

    command/run.go中加入如下两个函数:

    1. PathExists方法判断文件是否存在.
    2. getRootPath方法是根据命令行-m提供的目录返回执行init程序的目录. 比如用户输入./mydocker run -it -r /nicktming /bin/sh此时rootPath=/nicktming并且需要/nicktming目录已经准备好了busybox.tar文件, 此时该程序会解压busybox.tar/nicktming/busybox并且把/nicktming/busybox设置为执行init程序的工作目录.
    func getRootPath(rootPath string) string {
        log.Printf("rootPath:%s\n", rootPath)
        defaultPath := "/root"
        if rootPath == "" {
            log.Printf("rootPath is empaty, set cmd.Dir by default: /root/busybox\n")
            return defaultPath
        }
        imageTar := rootPath + "/busybox.tar"
        exist, _ := PathExists(imageTar)
        if !exist {
            log.Printf("%s does not exist, set cmd.Dir by default: /root/busybox\n", imageTar)
            return defaultPath
        }
        imagePath := rootPath + "/busybox"
        exist, _ = PathExists(imageTar)
        if exist {
            os.RemoveAll(imagePath)
        }
        if err := os.Mkdir(imagePath, 0777); err != nil {
            log.Printf("mkdir %s err:%v, set cmd.Dir by default: /root/busybox\n", imagePath, err)
            return defaultPath
        }
        if _, err := exec.Command("tar", "-xvf", imageTar, "-C", imagePath).CombinedOutput(); err != nil {
            log.Printf("tar -xvf %s -C %s, err:%v, set cmd.Dir by default: /root/busybox\n", imageTar, imagePath, err)
            return defaultPath
        }
        return rootPath
    }
    
    func PathExists(path string) (bool, error) {
        _, err := os.Stat(path)
        if err == nil {
            return true, nil
        }
        if os.IsNotExist(err) {
            return false, nil
        }
        return false, err
    }
    

    command/run.go中的run方法中加入cmd.Dir = getRootPath(rootPath).

    func Run(command string, tty bool, cg *cgroups.CroupManger, rootPath string)  {
    ...
        newRootPath := getRootPath(rootPath)
        cmd.Dir = newRootPath + "/busybox"
        cmd.ExtraFiles = []*os.File{reader}
        sendInitCommand(command, writer)
    ...
    }
    

    执行结果如下:

    ------------------------------terminal 02-----------------------------------
    root@nicktming:/nicktming# pwd
    /nicktming
    root@nicktming:/nicktming# ls
    busybox.tar
    ------------------------------terminal 02-----------------------------------
    root@nicktming:~/go/src/github.com/nicktming/mydocker# pwd
    /root/go/src/github.com/nicktming/mydocker
    root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it -r /nicktming /bin/sh
    2019/04/07 01:04:11 rootPath:/nicktming
    2019/04/07 01:04:11 current path: /nicktming/busybox.
    / # ls -l
    total 44
    drwxr-xr-x    2 root     root         12288 Feb 14 18:58 bin
    drwxr-xr-x    4 root     root          4096 Mar 17 16:05 dev
    drwxr-xr-x    3 root     root          4096 Mar 17 16:05 etc
    drwxr-xr-x    2 nobody   nogroup       4096 Feb 14 18:58 home
    dr-xr-xr-x  106 root     root             0 Apr  6 17:04 proc
    drwx------    2 root     root          4096 Apr  6 17:04 root
    drwxr-xr-x    2 root     root          4096 Mar 17 16:05 sys
    drwxrwxrwt    2 root     root          4096 Feb 14 18:58 tmp
    drwxr-xr-x    3 root     root          4096 Feb 14 18:58 usr
    drwxr-xr-x    4 root     root          4096 Feb 14 18:58 var
    / # ps -ef
    PID   USER     TIME  COMMAND
        1 root      0:00 /bin/sh
        5 root      0:00 ps -ef
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker#
    

    3.2 创建挂载点

    上面已经实现了利用某个镜像来创建容器, 并且可以自定义设置Init程序的工作目录.

    按照上图, 可以创建一个挂载点mnt, 创建一个容器可写层writerLayer, 镜像层为busybox. 假设在/nicktming目录下执行.

    1. mkdir -p /nicktming/busybox
    2. tar -xvf /nicktming/busybox.tar -C /nicktming/busybox
    3. mkdir -p /nicktming/mnt && mkdir -p /nicktming/writerLayer
    4. mount -t aufs -o dirs=/nicktming/writerLayer:/nicktming/busybox none /nicktming/mnt
    5. Init程序工作目录为:/nicktming/mnt

    上述步骤对应如下command/run.go中增加如下方法.

    // 将默认路径设置为/nicktming
    const (
        DEFAULTPATH = "/nicktming"
    )
    // 创建 rootPath/busybox  (比如:mkdir -p /nicktming/busybox)
    // tar -xvf rootPath/busybox.tar -C rootPath/busybox (比如: tar -xvf /nicktming/busybox.tar -C /nicktming/busybox)
    func getRootPath(rootPath string) string {
        log.Printf("rootPath:%s\n", rootPath)
        defaultPath := DEFAULTPATH
        if rootPath == "" {
            log.Printf("rootPath is empaty, set cmd.Dir by default: /%s/busybox\n", defaultPath)
            rootPath = defaultPath
        }
        imageTar := rootPath + "/busybox.tar"
        exist, _ := PathExists(imageTar)
        if !exist {
            log.Printf("%s does not exist, set cmd.Dir by default: /%s/busybox\n", defaultPath)
            return defaultPath
        }
        imagePath := rootPath + "/busybox"
        exist, _ = PathExists(imageTar)
        if exist {
            os.RemoveAll(imagePath)
        }
        if err := os.Mkdir(imagePath, 0777); err != nil {
            log.Printf("mkdir %s err:%v, set cmd.Dir by default: /%s/busybox\n", imagePath, err, defaultPath)
            return defaultPath
        }
        if _, err := exec.Command("tar", "-xvf", imageTar, "-C", imagePath).CombinedOutput(); err != nil {
            log.Printf("tar -xvf %s -C %s, err:%v, set cmd.Dir by default: /%s/busybox\n", imageTar, imagePath, err, defaultPath)
            return defaultPath
        }
        return rootPath
    }
    // 创建Init程序工作目录
    func NewWorkDir(rootPath string) error {
        if err := CreateContainerLayer(rootPath); err != nil {
            return fmt.Errorf("CreateContainerLayer(%s) error: %v.\n", rootPath, err)
        }
        if err := CreateMntPoint(rootPath); err != nil {
            return fmt.Errorf("CreateContainerLayer(%s) error: %v.\n", rootPath, err)
        }
        if err := SetMountPoint(rootPath); err != nil {
            return fmt.Errorf("CreateContainerLayer(%s) error: %v.\n", rootPath, err)
        }
        return nil
    }
    // 生成 rootPath/writerLayer文件夹 (比如:mkdir -p /nicktming/writerLayer)
    func CreateContainerLayer(rootPath string) error {
        writerLayer := rootPath + "/writerLayer"
        if err := os.Mkdir(writerLayer, 0777); err != nil {
            log.Printf("mkdir %s err:%v\n", writerLayer, err)
            return fmt.Errorf("mkdir %s err:%v\n", writerLayer, err)
        }
        return nil 
    }
    // 生成 rootPath/mnt文件夹 (比如:mkdir -p /nicktming/mnt)
    func CreateMntPoint(rootPath string) error {
        mnt := rootPath + "/mnt"
        if err := os.Mkdir(mnt, 0777); err != nil {
            log.Printf("mkdir %s err:%v\n", mnt, err)
            return fmt.Errorf("mkdir %s err:%v\n", mnt, err)
        }
        return nil
    }
    // 挂载 (比如:mount -t aufs -o dirs=/nicktming/writerLayer:/nicktming/busybox none /nicktming/mnt)
    func SetMountPoint(rootPath string) error {
        dirs := "dirs=" + rootPath + "/writerLayer:" + rootPath + "/busybox"
        mnt := rootPath + "/mnt"
        if _, err := exec.Command("mount", "-t", "aufs", "-o", dirs, "none", mnt).CombinedOutput(); err != nil {
            log.Printf("mount -t aufs -o %s none %s, err:%v\n", dirs, mnt, err)
            return fmt.Errorf("mount -t aufs -o %s none %s, err:%v\n", dirs, mnt, err)
        }
        return nil
    }
    

    完成上面方法后, 需要给Init程序设置工作目录. 修改command/run.go中的run方法.

    func Run(command string, tty bool, cg *cgroups.CroupManger, rootPath string)  {
    ...
      newRootPath := getRootPath(rootPath)
        cmd.Dir = newRootPath + "/busybox"
        if err := NewWorkDir(newRootPath); err == nil {
            cmd.Dir = newRootPath + "/mnt"
        }
    ...
    }
    

    执行结果如下:

    // 原始状态
    -------------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# pwd
    /nicktming
    root@nicktming:/nicktming# ls
    busybox.tar
    root@nicktming:/nicktming# 
    
    // 创建容器并且写入文件
    -------------------------------terminal 02----------------------------------
    root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
    2019/04/07 14:23:53 rootPath:
    2019/04/07 14:23:53 rootPath is empaty, set cmd.Dir by default: //nicktming/busybox
    2019/04/07 14:23:53 current path: /nicktming/mnt.
    / # ps -l
    PID   USER     TIME  COMMAND
        1 root      0:00 /bin/sh
        4 root      0:00 ps -l
    / # ls
    bin   dev   etc   home  proc  root  sys   tmp   usr   var
    / # mkdir nicktming01 && echo "testing01" > nicktming01/test01.txt
    / # ls
    bin          dev          etc          home         nicktming01  proc         root         sys          tmp          usr          var
    / # cat nicktming01/test01.txt 
    testing01
    
    // 回到宿主机中可以看到对应生成了mnt writerLayer busybox, 并且内容在可写层和mnt中
    -------------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# ls
    busybox  busybox.tar  mnt  writerLayer
    root@nicktming:/nicktming# ls mnt/
    bin  dev  etc  home  nicktming01  proc  root  sys  tmp  usr  var
    root@nicktming:/nicktming# cat mnt/nicktming01/test01.txt 
    testing01
    root@nicktming:/nicktming# ls writerLayer/
    nicktming01  root
    root@nicktming:/nicktming# cat writerLayer/nicktming01/test01.txt 
    testing01
    
    // 退出容器
    -------------------------------terminal 02----------------------------------
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker# 
    
    // 退出容器后busybox中的内容都没有做任何改变 容器层写入的东西保留在了mnt 和 writerLayer中, 并且挂载点依然存在
    -------------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# ls
    busybox  busybox.tar  mnt  writerLayer
    root@nicktming:/nicktming# ls busybox
    bin  dev  etc  home  proc  root  sys  tmp  usr  var
    root@nicktming:/nicktming# ls mnt/
    bin  dev  etc  home  nicktming01  proc  root  sys  tmp  usr  var
    root@nicktming:/nicktming# ls writerLayer/
    nicktming01  root
    root@nicktming:/nicktming# df -h
    Filesystem      Size  Used Avail Use% Mounted on
    ...
    none             50G  2.7G   45G   6% /nicktming/mnt
    

    3.3 清理工作

    3.2 创建挂载点中已经基本上达到了预期的效果, 只是在容器退出的时候挂载点和容器层依然存在, 需要将其卸载并清除.

    清理工作分如下几步: 假设 rootPath=/nicktming

    1. umount /nicktming/mnt
    2. rmdir /nicktming/mnt
    3. rmdir /nicktming/writerLayer

    对应的方法如下, 在command/run.go中加入如下方法.

    func ClearWorkDir(rootPath string)  {
        ClearMountPoint(rootPath)
        ClearWriterLayer(rootPath)
    }
    
    func ClearMountPoint(rootPath string)  {
        mnt := rootPath + "/mnt"
        if _, err := exec.Command("umount", "-f", mnt).CombinedOutput(); err != nil {
            log.Printf("mount -f %s, err:%v\n", mnt, err)
        }
        if err := os.RemoveAll(mnt); err != nil {
            log.Printf("remove %s, err:%v\n", mnt, err)
        }
    }
    
    func ClearWriterLayer(rootPath string) {
        writerLayer := rootPath + "/writerLayer"
        if err := os.RemoveAll(writerLayer); err != nil {
            log.Printf("remove %s, err:%v\n", writerLayer, err)
        }
    }
    

    command/run.go中的run方法中修改如下:

    func Run(command string, tty bool, cg *cgroups.CroupManger, rootPath string)  {
    ...
        newRootPath := getRootPath(rootPath)
        cmd.Dir = newRootPath + "/busybox"
        if err := NewWorkDir(newRootPath); err == nil {
            cmd.Dir = newRootPath + "/mnt"
        }
        defer ClearWorkDir(newRootPath)
    ...
    }
    

    执行结果如下:

    // 准备镜像busybox.tar
    -------------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# pwd
    /nicktming
    root@nicktming:/nicktming# ls
    busybox.tar
    
    // 创建容器
    -------------------------------terminal 02----------------------------------
    root@nicktming:~/go/src/github.com/nicktming/mydocker# pwd
    /root/go/src/github.com/nicktming/mydocker
    root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
    root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
    2019/04/07 15:37:30 rootPath:
    2019/04/07 15:37:30 rootPath is empaty, set cmd.Dir by default: /nicktming/busybox
    2019/04/07 15:37:30 current path: /nicktming/mnt.
    / # ls
    bin   dev   etc   home  proc  root  sys   tmp   usr   var
    / # mkdir nicktming01 && echo "testing01\n" > nicktming01/test01.txt
    / # ls
    bin          dev          etc          home         nicktming01  proc         root         sys          tmp          usr          var
    / # cat nicktming01/test01.txt 
    testing01\n
    
    
    // 查看宿主机内容
    -------------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# ls
    busybox  busybox.tar  mnt  writerLayer
    root@nicktming:/nicktming# cat mnt/nicktming01/test01.txt 
    testing01\n
    root@nicktming:/nicktming# cat writerLayer/nicktming01/test01.txt 
    testing01\n
    root@nicktming:/nicktming# df -h
    Filesystem      Size  Used Avail Use% Mounted on
    ...
    none             50G  2.7G   45G   6% /nicktming/mnt
    
    
    // 退出容器
    -------------------------------terminal 02----------------------------------
    / # exit
    root@nicktming:~/go/src/github.com/nicktming/mydocker# 
    
    // 查看宿主机内容 
    -------------------------------terminal 01----------------------------------
    root@nicktming:/nicktming# ls
    busybox  busybox.tar
    root@nicktming:/nicktming# df -h
    Filesystem      Size  Used Avail Use% Mounted on
    ...
    

    /nicktming/mnt挂载点已经没有, 并且中间文件夹mnt, writerLayer已经被删除了.

    4. 时序图

    4-2.png

    5. 参考

    1. 自己动手写docker.(基本参考此书,加入一些自己的理解,加深对docker的理解)

    6. 全部内容

    mydocker.png

    1. [mydocker]---环境说明
    2. [mydocker]---urfave cli 理解
    3. [mydocker]---Linux Namespace
    4. [mydocker]---Linux Cgroup
    5. [mydocker]---构造容器01-实现run命令
    6. [mydocker]---构造容器02-实现资源限制01
    7. [mydocker]---构造容器02-实现资源限制02
    8. [mydocker]---构造容器03-实现增加管道
    9. [mydocker]---通过例子理解存储驱动AUFS
    10. [mydocker]---通过例子理解chroot 和 pivot_root
    11. [mydocker]---一步步实现使用busybox创建容器
    12. [mydocker]---一步步实现使用AUFS包装busybox
    13. [mydocker]---一步步实现volume操作
    14. [mydocker]---实现保存镜像
    15. [mydocker]---实现容器的后台运行
    16. [mydocker]---实现查看运行中容器
    17. [mydocker]---实现查看容器日志
    18. [mydocker]---实现进入容器Namespace
    19. [mydocker]---实现停止容器
    20. [mydocker]---实现删除容器
    21. [mydocker]---实现容器层隔离
    22. [mydocker]---实现通过容器制作镜像
    23. [mydocker]---实现cp操作
    24. [mydocker]---实现容器指定环境变量
    25. [mydocker]---网际协议IP
    26. [mydocker]---网络虚拟设备veth bridge iptables
    27. [mydocker]---docker的四种网络模型与原理实现(1)
    28. [mydocker]---docker的四种网络模型与原理实现(2)
    29. [mydocker]---容器地址分配
    30. [mydocker]---网络net/netlink api 使用解析
    31. [mydocker]---网络实现
    32. [mydocker]---网络实现测试

    相关文章

      网友评论

          本文标题:[mydocker]---一步步实现使用AUFS包装busybo

          本文链接:https://www.haomeiwen.com/subject/bjpdiqtx.html