[mydocker]---一步步实现使用busybox创建容器

1. 准备工作

1.1 准备环境

Tingzhangs-MacBook-Pro:mydocker tingzhangming$ git clone https://github.com/nicktming/mydocker.git
Tingzhangs-MacBook-Pro:mydocker tingzhangming$ git checkout code-3.3
Tingzhangs-MacBook-Pro:mydocker tingzhangming$ git checkout -b dev-4.1

1.2 准备busybox

执行以下两个命令获得busybox, 并放入每个目录下(本文busybox地址:/root/busybox)

docker export `docker run -itd busybox:latest` > busybox.tar
mkdir busybox && tar -xvf busybox.tar -C busybox

1.3 本文最终效果

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# ./mydocker run -it /bin/sh
2019/04/06 15:49:23 rootPath:
2019/04/06 15:49:23 set cmd.Dir by default: /root/busybox
2019/04/06 15:49:23 current path: /root/busybox.
/ # 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
/ # mount
rootfs on / type rootfs (rw)
/dev/disk/by-uuid/23922b2b-365b-4a75-999c-61c297921652 on / type ext4 (rw,noatime,data=ordered)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
/ # exit
root@nicktming:~/go/src/github.com/nicktming/mydocker# 

2. 实现改变init程序执行路径

cmd加入一些参数, cmd.Dir = "/root", 在执行用户程序的时候可以设置该程序在哪个目录下执行.

2.1 修改command/run.go

...
    cmd := exec.Command("/proc/self/exe", "init")

    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
    }
    log.Printf("cmd.Dir:%s\n", "/root")
    cmd.Dir = "/root"
    cmd.ExtraFiles = []*os.File{reader}
    sendInitCommand(command, writer)
...

2.2 修改command/init.go

加入提示信息

...
    pwd, err := os.Getwd()
    if err != nil {
        log.Printf("ERROR: get pwd error!\n")
        return
    }
    log.Printf("current path: %s.\n", pwd)


    if err := syscall.Exec(command, []string{command}, os.Environ()); err != nil {
        log.Printf("syscall.Exec err: %v\n", err)
        log.Fatal(err)
    }

执行如下:

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/ls
2019/04/06 13:44:28 cmd.Dir:/root
2019/04/06 13:44:28 read from commandline:
2019/04/06 13:44:28 read from pipe:/bin/ls
2019/04/06 13:44:28 current path: /root.
aufs  busybox  busybox.tar  cgroup  go  go1.7.3.linux-amd64.tar.gz  memory  memory.c  mnt

执行结果如上所示, ./mydocker run -it /bin/ls是在/root/go/src/github.com/nicktming/mydocker执行的. 但是用户程序/bin/ls是在/root目录下执行的, 所以显示结果也是对的.

root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it /bin/sh
2019/04/06 13:44:55 cmd.Dir:/root
2019/04/06 13:44:55 read from commandline:
2019/04/06 13:44:55 read from pipe:/bin/sh
2019/04/06 13:44:55 current path: /root.
# ls
aufs  busybox  busybox.tar  cgroup  go  go1.7.3.linux-amd64.tar.gz  memory  memory.c  mnt
# exit

2.3 给执行目录参数化

修改command/command.go如下:

    Action: func(c *cli.Context) error {
        tty     := c.Bool("it")
        memory  := c.String("m")
        rootPath  := c.String("r")
        command := c.Args().Get(0)

        res := subsystems.ResourceConfig{
            MemoryLimit: memory,
        }
        cg := cgroups.CroupManger {
            Resource: &res,
            SubsystemsIns: make([]subsystems.Subsystem, 0),
        }
        if memory != "" {
            cg.SubsystemsIns = append(cg.SubsystemsIns, &subsystems.MemorySubsystem{})
        }

        Run(command, tty, &cg, rootPath)
        return nil
    },

对应的需要修改command/run.go如下:

func Run(command string, tty bool, cg *cgroups.CroupManger, rootPath string)  {
...
    cmd := exec.Command("/proc/self/exe", "init")

    cmd.SysProcAttr = &syscall.SysProcAttr{
        Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS | syscall.CLONE_NEWNET | syscall.CLONE_NEWIPC,
    }
    log.Printf("rootPath:%s\n", rootPath)
    cmd.Dir = rootPath
    if rootPath == "" {
        log.Printf("set cmd.Dir by default: /root/busybox\n")
        cmd.Dir = "/root/busybox"
    }

    cmd.ExtraFiles = []*os.File{reader}
    sendInitCommand(command, writer)
...
}

执行如下所示:

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 14:46:35 rootPath:
2019/04/06 14:46:35 set cmd.Dir by default: /root/busybox
2019/04/06 14:46:35 current path: /root/busybox.
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it -r /root/busybox /bin/sh
2019/04/06 14:47:05 rootPath:/root/busybox
2019/04/06 14:47:05 current path: /root/busybox.
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit

3. 使用pivot_root

关于pivot_root 可以参考 [mydocker]---通过例子理解chroot 和 pivot_root.

pivotRoot(root string) 可以把路径root变为根节点.

func pivotRoot(root string) error {
    if err := syscall.Mount(root, root, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
        return fmt.Errorf("Mount rootfs to itself error: %v", err)
    }
    pivotDir := filepath.Join(root, ".pivot_root")
    if err := os.Mkdir(pivotDir, 0777); err != nil {
        return err
    }
    if err := syscall.PivotRoot(root, pivotDir); err != nil {
        return fmt.Errorf("pivot_root %v", err)
    }
    if err := syscall.Chdir("/"); err != nil {
        return fmt.Errorf("chdir / %v", err)
    }

    pivotDir = filepath.Join("/", ".pivot_root")
    if err := syscall.Unmount(pivotDir, syscall.MNT_DETACH); err != nil {
        return fmt.Errorf("unmount pivot_root dir %v", err)
    }
    return os.Remove(pivotDir)
}

4. 实现容器根目录

由于上面已经实现了2. 实现改变init程序执行路径3. 使用pivot_root, 因此可以使用pivot_root将init程序执行路径变为根目录. 只需要在command/init.go中的Init方法中调用一下pivot_root方法即可. 改动如下:

func Init(command string)  {
    command = readFromPipe()

    pwd, err := os.Getwd()
    if err != nil {
        log.Printf("ERROR: get pwd error!\n")
        return
    }
    log.Printf("current path: %s.\n", pwd)
    pivotRoot(pwd)

    defaultMountFlags := syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
    syscall.Mount("proc", "/proc", "proc", uintptr(defaultMountFlags), "")

    if err := syscall.Exec(command, []string{command}, os.Environ()); err != nil {
        log.Printf("syscall.Exec err: %v\n", err)
        log.Fatal(err)
    }
}

执行结果如下: 指定目录为/root/busybox.

root@nicktming:~/go/src/github.com/nicktming/mydocker# go build .
root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it -r /root/busybox /bin/sh
2019/04/06 15:13:57 rootPath:/root/busybox
2019/04/06 15:13:57 current path: /root/busybox.
/ # ps -l
PID   USER     TIME  COMMAND
    1 root      0:00 /bin/sh
    4 root      0:00 ps -l
/ # pwd
/
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # exit
root@nicktming:~/go/src/github.com/nicktming/mydocker# 

如果指定的目录中不包括有busybox会报错, 因为执行的用户程序/bin/sh会从当前根目录下找, 找不到会报错. 该知识点可以参考[mydocker]---通过例子理解chroot 和 pivot_root.

root@nicktming:~/go/src/github.com/nicktming/mydocker# ./mydocker run -it -r /root /bin/sh
2019/04/06 15:16:41 rootPath:/root
2019/04/06 15:16:41 current path: /root.
2019/04/06 15:16:41 syscall.Exec err: no such file or directory
2019/04/06 15:16:41 no such file or directory

5. 时序图

[mydocker]---一步步实现使用busybox创建容器_第1张图片
set_root.png

6. 参考

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

7. 全部内容

[mydocker]---一步步实现使用busybox创建容器_第2张图片
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]---一步步实现使用busybox创建容器)