项目概述

该项目以《自己动手写Docker》一书作为参考,实现简易容器。
与原书不同的是:

  1. 使用cpp实现。
  2. 严格遵守OCI标准,仅仅实现runtime,不包括其他行为。

容器工具命名为Grid

OCI Runtime Spec

opencontainers/runtime-spec

runtime主要实现以下几个操作:

  1. create <container-id> <path-to-bundle>
  2. start <container-id>
  3. kill <container-id> <signal>
  4. 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的各种命令行库都不能明显帮助减少工作量。至少有以下三种可行的方案:

  1. 直接使用argv
  2. boost option
  3. 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
    • 将刚才获取的bundle,放在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

容器创建好后,便可以开始运行。

在最初的版本里,我们先实现交互式的容器。

大致工作流程如下:

  1. 运行Grid ./Gird start firstcontainer (通过环境变量指定Grid配置,帮助找到容器存储目录)
  2. 加载配置,加载容器信息
  3. 调用 clone(InitProcess, stackTop, CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | SIGCHLD, this); 创建一个子线程,并且在不同的命名空间里。
  4. 父进程 waitid(P_PID, child_process, &siginfo, WEXITED); 等待子进程退出
  5. 子进程
    • 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。