项目概述
该项目以《自己动手写Docker》一书作为参考,实现简易容器。
与原书不同的是:
- 使用cpp实现。
- 严格遵守OCI标准,仅仅实现runtime,不包括其他行为。
容器工具命名为Grid
。
OCI Runtime Spec
opencontainers/runtime-spec
runtime主要实现以下几个操作:
create <container-id> <path-to-bundle>
start <container-id>
kill <container-id> <signal>
delete <container-id>
bundle可以被认为是镜像、Rootfs等概念,即包含了一个容器所必须的文件系统部分。
Create
获取一个bundle示例,可以通过如下指令:
1 2 3 4 5
| docker create --name box busybox docker export box -o busybox.tar mkdir busybox tar -xf busybox.tar -C busybox docker rm box
|
export将busybox镜像提取成tar包,解压后就可以认为是一个bundle。在bundle下还需要有一个配置文件config.json
。可以参照标准,创建一个。
现在,假定我们可以执行./Grid create busybox /tmp/busybox
。
我们需要的代码模块有命令行参数解析,Json文件解析。
命令行参数
由于runtime的输入都是positional args,cpp的各种命令行库都不能明显帮助减少工作量。至少有以下三种可行的方案:
- 直接使用argv
- boost option
- GFlag
Json文件解析
Jsoncpp的接口较为简单,易于使用。
AUFS
成功解析命令行参数和配置文件后,接下来完成Create命令需要做的事情。严格来说,并没有任何关于实现的限定,不管是虚拟机,还是Namespace+AUFS,都是可以达成runtime要求的实现。
参照Docker的实现,我们需要创建一个AUFS,并且将容器状态持久化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| cd /home/Grid tree -d
. ├── containers │ ├── firstcontainer │ │ ├── mntFolder │ │ │ ├── bin │ │ │ ├── dev │ │ │ │ ├── pts │ │ │ │ └── shm │ │ │ ├── etc │ │ │ │ └── network │ │ │ │ ├── if-down.d │ │ │ │ ├── if-post-down.d │ │ │ │ ├── if-pre-up.d │ │ │ │ └── if-up.d │ │ │ ├── home │ │ │ ├── proc │ │ │ ├── root │ │ │ ├── sys │ │ │ ├── tmp │ │ │ ├── usr │ │ │ │ └── sbin │ │ │ └── var │ │ │ ├── spool │ │ │ │ └── mail │ │ │ └── www │ │ └── writeLayer │ └── secondcontainer │ ├── mntFolder │ │ ├── bin │ │ ├── dev │ │ │ ├── pts │ │ │ └── shm │ │ ├── etc │ │ │ └── network │ │ │ ├── if-down.d │ │ │ ├── if-post-down.d │ │ │ ├── if-pre-up.d │ │ │ └── if-up.d │ │ ├── home │ │ ├── proc │ │ ├── root │ │ ├── sys │ │ ├── tmp │ │ ├── usr │ │ │ └── sbin │ │ └── var │ │ ├── spool │ │ │ └── mail │ │ └── www │ └── writeLayer └── images ├── busybox │ ├── bin │ ├── dev │ │ ├── pts │ │ └── shm │ ├── etc │ │ └── network │ │ ├── if-down.d │ │ ├── if-post-down.d │ │ ├── if-pre-up.d │ │ └── if-up.d │ ├── home │ ├── proc │ ├── root │ ├── sys │ ├── tmp │ ├── usr │ │ └── sbin │ └── var │ ├── spool │ │ └── mail │ └── www └── ubuntu
|
我们对容器和镜像的存储如上图所示,分为container和images两个目录。
- images
- containers
- 创建一个新的容器,containers目录下就会新创建一个目录,名字和容器id相同。
- 容器id目录下有writeLayer,mntFolder和status.json
wrtieLayer和mntFolder是两个空目录,然后执行
mount -t aufs -o br:/home/Grid/fristcontainer/writeLayer=rw:/home/Grid/images/busybox=ro none /home/Grid/fristcontainer/mntFolder
在简易版本里,可以通过调用外部命令实现mount,未来可以改进为直接使用system call。
1 2 3 4 5 6 7 8
| #include <stdlib.h>
std::string branches = "br:" + writelayer + "=rw:" + rolayer + "=ro"; std::string command = "mount -t aufs -o " + branches + " none " + mntfolder;
if (system(command.c_str()) == -1) { throw std::runtime_error("aufs mount fail!"); }
|
最后,在status.json里写入
1 2 3 4 5 6 7 8 9 10
| { "Bundle" : "/home/ubuntu/Grid/images/busybox", "Created" : true, "Creating" : false, "ID" : "firstcontainer", "OCIVersion" : "", "Pid" : 0, "Running" : false, "Stopped" : false }
|
该文件可用任意格式,仅用作维护内部状态,在调用start、kill时,修改status内容。
Start
容器创建好后,便可以开始运行。
在最初的版本里,我们先实现交互式的容器。
大致工作流程如下:
- 运行Grid
./Gird start firstcontainer
(通过环境变量指定Grid配置,帮助找到容器存储目录)
- 加载配置,加载容器信息
- 调用
clone(InitProcess, stackTop, CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | SIGCHLD, this);
创建一个子线程,并且在不同的命名空间里。
- 父进程
waitid(P_PID, child_process, &siginfo, WEXITED);
等待子进程退出
- 子进程
- Pivot_root
- mount proc, tmp
- execve
config.json in bundle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| { "process": { "terminal": true, "consoleSize": { "height": 25, "width": 80 }, "user": { "uid": 1, "gid": 1, "umask": 63, "additionalGids": [5, 6] }, "env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm" ], "cwd": "/root", "args": [ "sh" ], "apparmorProfile": "acme_secure_profile", "selinuxLabel": "system_u:system_r:svirt_lxc_net_t:s0:c124,c675", "noNewPrivileges": true, "capabilities": { "bounding": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "permitted": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "inheritable": [ "CAP_AUDIT_WRITE", "CAP_KILL", "CAP_NET_BIND_SERVICE" ], "effective": [ "CAP_AUDIT_WRITE", "CAP_KILL" ], "ambient": [ "CAP_NET_BIND_SERVICE" ] }, "rlimits": [ { "type": "RLIMIT_NOFILE", "hard": 1024, "soft": 1024 } ] } }
|
第一个版本只需要支持terminal, env, args。