[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. 时序图

set_root.png

6. 参考

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

7. 全部内容

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容