7C00.ME/houmu 2015-08-15
Bocker源码赏析:原理篇
Bocker (https://github.com/p8952/bocker) 是 Docker 的 Bash 实现,它只用100余行代码就实现了 Docker 的基本功能。通过阅读 Bocker 的源码,可以帮助我们理解 Docker 的工作原理。
整体结构
Bocker项目文件主要包含3个部分,
1,bocker: 这是一个100多行代码的Bash脚本文件,这是整个项目的核心部分,后面详细说明。
2,Vagrantfile:这是一个Vagrant配置文件,用于配置 bokcer 的运行环境(实际是一个 CentOS 虚拟机)。Bocker 只是个实验性项目,作者给出一个统一的运行环境,省却了因系统差异而带来的额外工作。
运行环境配置工作主要是:
- 安装依赖程序(比如btrfs-progs、libcgroup-tools等);
- 创建一个btrfs分区,挂载到
/var/bocker
; - 配置iptables转发规则,并创建一个虚拟网桥
bridge0
;
在Vagrantfile中还有一组命令是:docker pull一个centos映像,然后undocker导出。我没看出这个工作的有什么用处。
3,test:测试脚本。
bocker源码
bocker源码的结构非常清晰。开头部分是脚本参数设定(set -o
、shopt
)、常量声明、命令行参数处理。中间部分定义个11个bash函数,每个函数对应一个bocker命令,而每个bocker命令对应一个同名且同语义的Docker命令。末尾部分是程序入口,根据不同的命令行参数,调用对应的一个bash函数。
我们按照Docker的基本使用过程来说明bocker的工作原理:pull/init、run、exec、commit、rm。
NOTE:下文中tmp_uuid
、img_uuid
、ps_uuid
是运行时使用uuidgen
或者shuf
动态生成的随机字符串。
pull
pull命令从Docker Hub下载image,由bocker_pull
函数实现。
bocker_pull
函数使用curl
访问Docker Hub API,获取命令行参数指定的image的地址,下载相应的文件(由于Docker image的分层设计,一个image通常对应一组存档文件),解压到 /tmp/<tmp_uuid>
目录,同时把命令行参数中的image和tag按照”image:tag”格式写入/tmp/<tmp_uuid>/img.source
文件。然后,调用bocker_init /tmp/<tmp_uuid>
函数,之后删除/tmp/<tmp_uuid>
目录。
init
init命令从参数指定的目录创建image,由bocker_init
函数实现。它主要做两件事:
- 创建subvolume:
btrfs subvolume create /var/bocker/<img_uuid>
,; - 把指定目录(如
bocker_pull
中的/tmp/<tmp_uuid>
)拷贝到/var/bocker/<img_uuid>
。
run
run命令从指定image创建一个容器,由bocker_run
函数实现。创建容器经过下面一些列操作:
- 使用
ip link
创建虚拟网卡veth0_<ps_uuid>
,并关联到虚拟网桥bridge0
; - 使用
ip netns
创建一个网络名字空间(network namespace,netns)netns_<ps_uuid>
,把刚创建的虚拟网卡关联进来,同时给它设定IP、MAC和路由,然后启动(up)这些虚拟网络设备。 - 创建快照:
btrfs subvolume snapshot /var/bocker/<img_uuid> /var/bocker/<ps_uuid>
- 把容器需要执行的命令写入文件
/var/bocker/<ps_uuid>/<ps_uuid>.cmd
- 创建cgroup:
cgcreate -g cpu,cpuacct,memory:<ps_uuid>
,并用cgset设定cgroup的cpu和memory; - 使用
ip netns exec
,unshare
,chroot
等启动容器,标准输出重定向到/var/bocker/<ps_uuid>
- 清除
veth0_<ps_uuid>
和netns_<ps_uuid>
,执行到这一步表明上一步已经运行结束,容器可以关闭。
exec
exec命令是在一个运行中的容器中执行一个命令,由bocker_exec
函数实现。这个函数非常短,本质是用nsenter
和chroot
实现了目标功能。
commit
commit命令把一个容器提交为一个image,由bokcer_commit
函数实现。这个功能的实现原理是用btrfs subvolume snapshot
功能把容器所在目录快照到目标image所在目录。
rm
rm命令删除一个image或者容器,由bocker_rm
函数实现。它主要做两件事:
- 删除对应image或者容器的subvolume:
btrfs subvolume delete /var/bocker/(<img_uuid>|<ps_uuid>
; - 删除对应的cgroup:
cgdelete -g cpu,cpuacct,memory:(<img_uuid>|<ps_uuid>)
images/ps/logs
除了上述命令以外,bocker还实现了images(列出image)、ps(列出容器)和logs(查看指定容器的日志)等命令,他们的实现比较简单。
从上面的分析可以看出,当使用若干次以后,/var/bocker
会出现类似下面的结构:
/var/bocker/
img_uuid1/
img.source
...
img_uuid2/
img.source
...
ps_uuid1/
ps_uuid1.cmd
ps_uuid1.log
...
ps_uuid2/
ps_uuid2.cmd
ps_uuid2.log
...
...
显然,通过分析目录和文件很容易实现images、ps、logs这些命令。
小结
首先用一张表格列出bocker主要功能的关键命令:
bocker | 关键命令 |
---|---|
pull | curl, tar, +init |
init | ‘btrfs subvolume create’, cp |
run | ‘ip link’, ‘ip netns’, ‘btrfs subvolume snapshot’, cgcreate, cgset, cgexec, unshare, chroot |
exec | nsenter, chroot |
commit | ‘btrfs subvolume snapshot’ |
下表整理和bocker的实现原理:
功能 | 原理 |
---|---|
进程虚拟化(cpu + mem) | cgroups + unshare |
文件系统虚拟化 | chroot + btrfs |
网络虚拟化 | namespace |
bocker和 Docker 的虚拟化实现原理基本上相同,核心都是cgroups + namespace,不过bocker没有实现Docker的分层image机制。虽然bocker使用的btrfs也是Docker支持的联合文件系统(UnionFS),但是btrfs在bocker中的最重要的使用应该是snapshot功能。