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 只是个实验性项目,作者给出一个统一的运行环境,省却了因系统差异而带来的额外工作。

运行环境配置工作主要是:

在Vagrantfile中还有一组命令是:docker pull一个centos映像,然后undocker导出。我没看出这个工作的有什么用处。

3,test:测试脚本。

bocker源码

bocker源码的结构非常清晰。开头部分是脚本参数设定(set -oshopt)、常量声明、命令行参数处理。中间部分定义个11个bash函数,每个函数对应一个bocker命令,而每个bocker命令对应一个同名且同语义的Docker命令。末尾部分是程序入口,根据不同的命令行参数,调用对应的一个bash函数。

我们按照Docker的基本使用过程来说明bocker的工作原理:pull/init、run、exec、commit、rm。

NOTE:下文中tmp_uuidimg_uuidps_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函数实现。它主要做两件事:

  1. 创建subvolume:btrfs subvolume create /var/bocker/<img_uuid>,;
  2. 把指定目录(如bocker_pull中的/tmp/<tmp_uuid>)拷贝到/var/bocker/<img_uuid>

run

run命令从指定image创建一个容器,由bocker_run函数实现。创建容器经过下面一些列操作:

  1. 使用ip link创建虚拟网卡veth0_<ps_uuid>,并关联到虚拟网桥bridge0
  2. 使用ip netns创建一个网络名字空间(network namespace,netns)netns_<ps_uuid>,把刚创建的虚拟网卡关联进来,同时给它设定IP、MAC和路由,然后启动(up)这些虚拟网络设备。
  3. 创建快照:btrfs subvolume snapshot /var/bocker/<img_uuid> /var/bocker/<ps_uuid>
  4. 把容器需要执行的命令写入文件/var/bocker/<ps_uuid>/<ps_uuid>.cmd
  5. 创建cgroup:cgcreate -g cpu,cpuacct,memory:<ps_uuid> ,并用cgset设定cgroup的cpu和memory;
  6. 使用ip netns execunsharechroot等启动容器,标准输出重定向到 /var/bocker/<ps_uuid>
  7. 清除veth0_<ps_uuid>netns_<ps_uuid>,执行到这一步表明上一步已经运行结束,容器可以关闭。

exec

exec命令是在一个运行中的容器中执行一个命令,由bocker_exec函数实现。这个函数非常短,本质是用nsenterchroot实现了目标功能。

commit

commit命令把一个容器提交为一个image,由bokcer_commit函数实现。这个功能的实现原理是用btrfs subvolume snapshot功能把容器所在目录快照到目标image所在目录。

rm

rm命令删除一个image或者容器,由bocker_rm函数实现。它主要做两件事:

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功能。