Docker存储驱动之AUFS简介
mzhao86
8年前
<h2>简介</h2> <p>AUFS是曾是Docker默认的首选存储驱动。它非常稳定、有很多真实场景的部署、很强的社区支持。它有以下主要优点:</p> <p>极短的容器启动时间。</p> <p>有效的存储利用率。</p> <p>有效的内存利用率。</p> <p>虽然如此,但由于它没有包含在Linux内核主线中,所有很多Linux发行版并不支持AUFS。</p> <p>以下章节介绍AUFS的特性,并且它们如何作用于Docker。</p> <h2>特性</h2> <h2>镜像分层和部署</h2> <p>AUFS是一种联合文件系统。它使用同一个Linux host上的多个目录,逐个堆叠起来,对外呈现出一个统一的文件系统。AUFS使用该特性,实现了Docker镜像的分层。下图展示出ubuntu:latest的镜像的分层。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/73a3374b0bb756c816ea46cdbda62307.jpg"></p> <p>注意:在Docker1.10之前,layer的ID对应着其在/var/lib/docker下的目录名称,但在Docker1.10之后,不再有这种直接的对应关系。</p> <p>对于一个容器来说, 只有顶层的容器layer是可读写的,而下面的layer都是只读 的。</p> <h2>读写文件</h2> <p>Docker使用AUFS的CoW(Copy-on-Write)技术来实现镜像共享和最小化磁盘空间的使用。AUFS作用于文件层,也就是说AUFS CoW拷贝整个文件——即使文件只修改了一点点的内容。所以,它对容器的性能影响很明显,尤其拷贝多层镜像下的大文件,或者是在一个深层次的目录树中进行搜索。</p> <p>不过,在给定的容器中,这种拷贝到顶层layer的操作,每个文件只会做一次。随后,对该文件的读写操作,都只针对容器顶层可读写layer的拷贝文件。</p> <h2>删除文件</h2> <p>通过上面的介绍,很容易想到。如果要在容器中删除一个非顶层layer的文件,肯定不会在下层layer中直接删除,因为下层layer对于容器来说都是只读的。AUFS存储驱动要删除一个文件,是通过在容器顶层layer增加一个 <em>whiteout文件</em> 来实现的。这个whiteout文件可以隐藏下层只读layer中文件的存在,容器感知不到只读层layer中文的存在。事实上,无论该文件在下层只读layer中是否还存在,容器都认为这个文件被删除了。下图展示了whiteout文件如何工作的:</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/9e06e368e7fb1a6ae0062b94f7f6bffb.jpg"></p> <h2>重命名目录</h2> <p>AUFS未能完美的支持rename(2)重命名操作,会返回EXDEV[“cross-device link not permitted”],即使源路径和目的路径都在同一个AUFS层。因此,你的应用需要能处理EXDEV,可以用“拷贝再删除”的策略来替代rename操作。</p> <p>我在这里做了一个测试,写了一个简单地C程序,该程序将目录test重命名为目录gaga,并打印出rename的结果。该程序在普通服务器上完美运行,那么在容器中呢?开始做测试吧。</p> <h3>创建docker build目录,进入该目录。并在该目录下创建子目录test。</h3> <pre> <code class="language-cpp">$ mkdir build-rename $ cd build-rename $ mkdir test</code></pre> <h3>创建文件test.c</h3> <pre> <code class="language-cpp">$ vim test.c #include<stdio.h> #include <fcntl.h> int main(void) { char oldname[100] = "test", newname[100]="gaga"; int ret = rename(oldname, newname); if (ret == 0) printf("rename ok.\n"); else printf("ret = %d\n", ret); return 0; }</code></pre> <p>编译该程序,生成可执行文件a.out。</p> <pre> <code class="language-cpp">$ gcc test.c</code></pre> <h3>创建Dockerfile</h3> <pre> <code class="language-cpp">$ vim Dockerfile FROM ubuntu WORKDIR /usr/src/app COPY ./* /usr/src/app/ CMD /usr/src/app/a.out</code></pre> <h3>生成镜像。</h3> <pre> <code class="language-cpp">$ docker build -t rename:v1.0 ./</code></pre> <h3>运行容器</h3> <pre> <code class="language-cpp">$ docker run --rm rename:v1.0 ret = -1</code></pre> <p>该容器启动后会执行可执行文件a.out,重命名一个目录。可见结果,rename重命名一个目录的确是返回了失败。</p> <h2>配置AUFS</h2> <h3>准备</h3> <p>只有在OS安装了AUFS的情况下才能使用AUFS存储驱动,一般来说,Debian/Ubuntu都支持AUFS,而Redhat/CentOS都不支持AUFS。所以,你需要先查看下你的系统是否安装了AUFS。</p> <pre> <code class="language-cpp">$ grep aufs /proc/filesystems nodev aufs</code></pre> <p>如果以上命令有输出则表示支持AUFS,否则就说明还未安装AUFS。可执行以下步骤:</p> <p>a. 升级你系统的kernel版本到3.13或者更高,另外建议安装kernel headers;</p> <p>b. 对于Ubuntu/Debian:安装linux-image-extra-*包:</p> <pre> <code class="language-cpp">$ apt-get install linux-image-extra-$(uname -r) \ linux-image-extra-virtual</code></pre> <h3>配置</h3> <p>如果上述操作无误,就可以使用AUFS来作为你Docker Daemon的存储驱动了。</p> <pre> <code class="language-cpp">$ dockerd --storage-driver=aufs &</code></pre> <p>如果想持久化这个配置,可以编辑你的Docker配置文件(如/etc/default/docker,虽然官方已经不建议使用该文件了),并加入--storage-driver=aufs选项到DOCKER_OPTS中。</p> <pre> <code class="language-cpp"># Use DOCKER_OPTS to modify the daemon startup options. DOCKER_OPTS="--storage-driver=aufs"</code></pre> <p>重启docker daemon(systemctl restart docker.service)后,确认默认存储驱动是否配置成功:</p> <pre> <code class="language-cpp">$ docker info | grep "Storage Driver" Storage Driver: aufs</code></pre> <h2>本地存储和AUFS</h2> <p>当dockerd使用AUFS驱动时,驱动把镜像和容器存储在Docker host的本地存储下:/var/lib/docker/aufs。</p> <h3>镜像</h3> <p>镜像层存储在/var/lib/docker/aufs/diff里。Docker 1.10之后,镜像对应的目录名称不再和镜像ID意义对应了。</p> <p>/var/lib/docker/aufs/layers/目录保存了元数据信息,这些元数据显示了image层是如何叠加的。该目录下的每个文件,对应了一个层,而这个文件的内容就是该层之下的层。如:</p> <pre> <code class="language-cpp">$ cat /var/lib/docker/aufs/layers/91e54dfb11794fad694460162bf0cb0a4fa710cfa3f60979c177d920813e267c d74508fb6632491cea586a1fd7d748dfc5274cd6fdfedee309ecdcbc2bf5cb82 c22013c8472965aa5b62559f2b540cd440716ef149756e7b958a1b2aba421e87 d3a1f33e8a5a513092f01bb7eb1c2abf4d711e5105390a3fe1ae2248cfde1391</code></pre> <p>由于base layer之下不再有其它层,所有base layer对应的文件内容是空的。</p> <h3>容器</h3> <p>运行中的容器映射在 /var/lib/docker/aufs/mnt/</p> <pre> <code class="language-cpp">$ ls /var/lib/docker/aufs/mnt/670e0053b2ba02ab33dc24daca293e200aa98e871cefec016a5cbf9d41b7cfbf bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var</code></pre> <p>容器的可写层存储在目录 /var/lib/docker/aufs/diff/,即使容器停止了,容器对应的目录依然存在。只有删除容器时,对应的目录才会删除。</p> <h3>AUFS在Docker中的性能</h3> <p>对于PaaS层来说,AUFS存储驱动是一个很好的选择。因为AUFS有效地在多个运行容器中共享镜像,加速了容器启动时间,减少了容器使用的磁盘空间。</p> <p>AUFS在多个镜像层和容器间分享文件所使用的底层机制,高效地使用了系统的页缓存。</p> <p>同时,AUFS存储驱动也带来了一些容器写性能上的隐患。这是因为,容器第一次对任何文件的修改,都需要先定位到文件的所在的镜像层次,并拷贝到容器最顶层的读写层。尤其当这些文件存在于很底层,或者文件本身非常大时,性能问题尤其严重。</p> <h2>小结</h2> <p>AUFS是Docker在Ubuntu/Debian中的默认存储驱动,虽然后面可能会被替换掉。但暂时来说,它完美地契合Docker的特性。并且,如何合理使用,其性能非常优异。另外,需要注意的是,AUFS对目录的重命名支持得不好,在编写程序时需要注意这点。</p> <p> </p> <p>来自:http://www.cnblogs.com/styshoo/p/6442041.html</p> <p> </p>