随着全面云时代到来,不少公司都走上了容器化道路,老刘所在的公司也不例外。做为一家初创型的互联网公司,容器化的确带来了不少便捷,也下降了公司成本,不过老刘却有一个苦恼,之前天天和他一块儿下班的小王自从公司上云之后天天都比他早下班一个小时,你们手头上的活都差很少,讲道理不该该呀,通过多番试探、跟踪、调查,终于让老刘发现了秘密的所在。node
做为一个开发,天天总少不了要出N个测试版本进行调试,容器化之后每次出版本都须要打成镜像,老刘发现每次他作一个镜像都要20分钟,而小王只要10分钟,对比来对比去只有这个东西不同!linux
Storage-Dirver究竟是何方神圣?为何可以致使构建时间上的差别?如今让咱们来一窥究竟。git
在回答这个问题以前咱们须要先回答三个问题——什么是镜像?什么是镜像构建?什么是storage-driver?github
什么是镜像?docker
说到镜像就绕不开容器,咱们先看一张来自官方对镜像和容器解释的图片:数据库
看完之后是否是更疑惑了,咱们能够这样简单粗暴的去理解,镜像就是一堆只读层的堆叠。那只读层里究竟是什么呢,另一个简单粗暴的解释:里边就是放了一堆被改动的文件。这个解释在不一样的storage-driver下不必定准确可是咱们能够先这样简单去理解。ubuntu
那不对呀,执行容器的时候明明是能够去修改删除容器里的文件的,都是只读的话怎么去修改呢?实际上咱们运行容器的时候是在那一堆只读层的顶上再增长了一个读写层,全部的操做都是在这个读写层里进行的,当须要修改一个文件的时候咱们会将须要修改的文件从底层拷贝到读写层再进行修改。那若是是删除呢,咱们不是没有办法删除底层的文件么?没错,确实没有办法删除,但只须要在上层把这个文件隐藏起来,就能够达到删除的效果。按照官方说法,这就是Docker的写时复制策略。网络
为了加深你们对镜像层的理解咱们来举个栗子,用下面的Dockerfile构建一个etcd镜像:并发
构建完成之后生成了以下的层文件:app
每次进入容器的时候都感受仿佛进入了一台虚机,里面包含linux的各个系统目录。那是否是有一层目录里包含了全部的linux系统目录呢?
bingo答对!在最底层的层目录的确包含了linux的全部的系统目录文件。
上述Dockerfile中有这样一步操做
ADD . /go/src/github.com/coreos/etcd
将外面目录的文件拷到了镜像中,那这一层镜像里究竟保存了什么呢?
打开发现里面就只有
/go/src/github.com/coreos/etcd这个目录,目录下存放了拷贝进来的文件。
到这里是否是有种管中窥豹的感受,接下来咱们再来了解什么是镜像构建,这样基本上可以窥其全貌了。
什么是镜像构建?
经过第一节的内容咱们知道了镜像是由一堆层目录组成的,每一个层目录里放着这一层修改的文件,镜像构建简单的说就是制做和生成镜像层的过程,那这一过程是如何实现的呢?如下图流程为例:
Docker Daemon首先利用基础镜像ubuntu:14.04建立了一个容器环境,经过第一节的内容咱们知道容器的最上层是一个读写层,在这一层咱们是能够写入修改的,Docker Daemon首先执行了RUN apt-update get命令,执行完成之后,经过Docker的commit操做将这个读写层的内容保存成一个只读的镜像层文件。接下来再在这一层的基础上继续执行 ADD run.sh命令,执行完成后继续commit成一个镜像层文件,如此反复直到将全部的Dockerfile都命令都被提交后,镜像也就作好了。
这里咱们就能解释为何etcd的某个层目录里只有一个go目录了,由于构建的过程是逐层提交的,每一层里只会保存这一层操做所涉及改动的文件。
这样看来镜像构建就是一个反复按照Dockerfile启动容器执行命令并保存成只读文件的过程,那为何速度会不同呢?接下来就得说到storage-driver了。
什么是storage-driver?
再来回顾一下这张图:
以前咱们已经知道了,镜像是由一个个的层目录叠加起来的,容器运行时只是在上面再增长一个读写层,同时还有写时复制策略保证在最顶层可以修改底层的文件内容,那这些原理是怎么实现的呢?就是靠storage-driver!
简单介绍三种经常使用的storage-driver:
1. AUFS
AUFS经过联合挂载的方式将多个层文件堆叠起来,造成一个统一的总体提供统一视图,当在读写层进行读写的时,先在本层查找文件是否存在,若是没有则一层一层的往下找。aufs的操做都是基于文件的,须要修改一个文件时不管大小都会将整个文件从只读层拷贝到读写层,所以若是须要修改的文件过大,会致使容器执行速度变慢,docker官方给出的建议是经过挂载的方式将大文件挂载进来而不是放在镜像层中。
2. OverlayFS
OverlayFS能够认为是AUFS的升级版本,容器运行时镜像层的文件是经过硬连接的方式组成一个下层目录,而容器层则是工做在上层目录,上层目录是可读写的,下层目录是只读的,因为大量的采用了硬连接的方式,致使OverlayFS会可能会出现inode耗尽的状况,后续Overlay2对这一问题进行了优化,且性能上获得了很大的提高,不过Overlay2也有和AUFS有一样的弊端——对大文件的操做速度比较慢。
3. DeviceMapper
DeviceMapper和前两种Storage-driver在实现上存在很大的差别。首先DeviceMapper的每一层保存的是上一层的快照,其次DeviceMapper对数据的操做再也不是基于文件的而是基于数据块的。
下图是devicemapper在容器层读取文件的过程:
首先在容器层的快照中找到该文件指向下层文件的指针。
再从下层0xf33位置指针指向的数据块中读取的数据到容器的存储区
最后将数据返回app。
在写入数据时还须要根据数据的大小先申请1~N个64K的容器快照,用于保存拷贝的块数据。
DeviceMapper的块操做看上去很美,实际上存在不少问题,好比频繁操做较小文件时须要不停地从资源池中分配数据库并映射到容器中,这样效率会变得很低,且DeviceMapper每次镜像运行时都须要拷贝全部的镜像层信息到内存中,当启动多个镜像时会占用很大的内存空间。
针对不一样的storage-driver咱们用上述etcd的dockerfile进行了一组构建测试
文件存储系统 |
单次构建时间 |
并发10次平均构建时间 |
DevivceMapper |
44s |
269.5s |
AUFS |
8s |
26s |
Overlay2 |
10s |
269.5s |
注:该数据因dockerfile以及操做系统、文件系统、网络环境的不一样测试结果可能会存在较大差别
咱们发如今该实验场景下DevivceMapper在时间上明显会逊于AUFS和Overlay2,而AUFS和Overlay2基本至关,固然该数据仅能做为一个参考,实际构建还受到具体的Dockerfile内容以及操做系统、文件系统、网络环境等多方面的影响,那要怎么样才能尽可能让构建时间最短提高咱们的工做效率呢?
且看下回分解!