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
中加入如下两个函数:
PathExists
方法判断文件是否存在.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]---网络实现测试
网友评论