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

2019-05-11  本文已影响0人  nicktming

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]---网络实现测试

上一篇下一篇

猜你喜欢

热点阅读