Docker基础技术:AUFS

来源:互联网 发布:莱特币挖矿软件2017 编辑:程序博客网 时间:2024/05/03 20:35

http://www.open-open.com/lib/view/open1440483391763.html

Docker基础技术:AUFS
AUFS是一种Union File System,所谓UnionFS就是把不同物理位置的目录合并mount到同一个目录中。UnionFS的一个最主要的应用是,把一张CD/DVD和一个硬盘目录给联合 mount在一起,然后,你就可以对这个只读的CD/DVD上的文件进行修改(当然,修改的文件存于硬盘上的目录里)。

AUFS又叫Another UnionFS,后来叫Alternative UnionFS,后来可能觉得不够霸气,叫成Advance UnionFS。是个叫Junjiro Okajima(岡島順治郎)在2006年开发的,AUFS完全重写了早期的UnionFS 1.x,其主要目的是为了可靠性和性能,并且引入了一些新的功能,比如可写分支的负载均衡。AUFS在使用上全兼容UnionFS,而且比之前的UnionFS在稳定性和性能上都要好很多,后来的UnionFS 2.x开始抄AUFS中的功能。但是他居然没有进到Linux主干里,就是因为Linus不让,基本上是因为代码量比较多,而且写得烂(相对于只有3000行的union mount和10000行的UnionFS,以及其它平均下来只有6000行代码左右的VFS,AUFS居然有30000行代码),所以,岡島不断地改进代码质量,不断地提交,不断地被Linus拒掉,所以,到今天AUFS都还进不了Linux主干(今天你可以看到AUFS的代码其实还好了,比起OpenSSL好N倍,要么就是Linus对代码的质量要求非常高,要么就是Linus就是不喜欢AUFS)。

不过,好在有很多发行版都用了AUFS,比如:Ubuntu 10.04,Debian6.0, Gentoo Live CD支持AUFS,所以,也OK了。

好了,扯完这些闲话,我们还是看一个示例吧(环境:Ubuntu 14.04)

首先,我们建上两个目录(水果和蔬菜),并在这两个目录中放上一些文件,水果中有苹果和蕃茄,蔬菜有胡萝卜和蕃茄。

$ tree.├── fruits│   ├── apple│   └── tomato└── vegetables    ├── carrots    └── tomato

然后,我们输入以下命令:

# 创建一个mount目录$ mkdir mnt# 把水果目录和蔬菜目录union mount到 ./mnt目录中$ sudo mount -t aufs -o dirs=./fruits:./vegetables none ./mnt#  查看./mnt目录$ tree ./mnt./mnt├── apple├── carrots└── tomato

我们可以看到在./mnt目录下有三个文件,苹果apple、胡萝卜carrots和蕃茄tomato。水果和蔬菜的目录被union到了./mnt目录下了。

我们来修改一下其中的文件内容:

$ echo mnt > ./mnt/apple$ cat ./mnt/applemnt$ cat ./fruits/applemnt

上面的示例,我们可以看到./mnt/apple的内容改了,./fruits/apple的内容也改了。

$ echo mnt_carrots > ./mnt/carrots$ cat ./vegetables/carrots $ cat ./fruits/carrotsmnt_carrots

上面的示例,我们可以看到,我们修改了./mnt/carrots的文件内容,./vegetables/carrots并没有变化,反而是./fruits/carrots的目录中出现了carrots文件,其内容是我们在./mnt/carrots里的内容。

也就是说,我们在mount aufs命令中,我们没有指它vegetables和fruits的目录权限,默认上来说,命令行上第一个(最左边)的目录是可读可写的,后面的全都是只读的。(一般来说,最前面的目录应该是可写的,而后面的都应该是只读的)

所以,如果我们像下面这样指定权限来mount aufs,你就会发现有不一样的效果(记得先把上面./fruits/carrots的文件删除了):

$ sudo mount -t aufs -o dirs=./fruits=rw:./vegetables=rw none ./mnt$ echo "mnt_carrots" > ./mnt/carrots $ cat ./vegetables/carrotsmnt_carrots$ cat ./fruits/carrotscat: ./fruits/carrots: No such file or directory

现在,在这情况下,如果我们要修改./mnt/tomato这个文件,那么究竟是哪个文件会被改写?

$ echo "mnt_tomato" > ./mnt/tomato $ cat ./fruits/tomatomnt_tomato$ cat ./vegetables/tomatoI am a vegetable

可见,如果有重复的文件名,在mount命令行上,越往前的就优先级越高。

你可以用这个例子做一些各种各样的试验,我这里主要是给大家一个感性认识,就不展开试验下去了。

那么,这种UnionFS有什么用?

历史上,有一个叫Knoppix的Linux发行版,其主要用于Linux演示、光盘教学、系统急救,以及商业产品的演示,不需要硬盘安装,直接把CD/DVD上的image运行在一个可写的存储设备上(比如一个U盘上),其实,也就是把CD/DVD这个文件系统和USB这个可写的系统给联合mount起来,这样你对CD/DVD上的image做的任何改动都会在被应用在U盘上,于是乎,你可以对CD/DVD上的内容进行任意的修改,因为改动都在U盘上,所以你改不坏原来的东西。

我们可以再发挥一下想像力,你也可以把一个目录,比如你的源代码,作为一个只读的template,和另一个你的working directory给union在一起,然后你就可以做各种修改而不用害怕会把源代码改坏了。有点像一个ad hoc snapshot。

Docker把UnionFS的想像力发挥到了容器的镜像。你是否还记得我在介绍Linux Namespace上篇中用mount namespace和chroot山寨了一镜像。现在当你看过了这个UnionFS的技术后,你是不是就明白了,你完全可以用UnionFS这样的技术做出分层的镜像来。

下图来自Docker的官方文档Layer,其很好的展示了Docker用UnionFS搭建的分层镜像。

Docker基础技术:AUFS

关于docker的分层镜像,除了aufs,docker还支持btrfs, devicemapper和vfs,你可以使用 -s 或 –storage-driver= 选项来指定相关的镜像存储。在Ubuntu 14.04下,docker默认Ubuntu的 aufs(在CentOS7下,用的是devicemapper,关于devicemapper,我会以以后的文章中讲解)你可以在下面的目录中查看相关的每个层的镜像:

/var/lib/docker/aufs/diff/<id> 

在docker执行起来后(比如:docker run -it ubuntu /bin/bash ),你可以从/sys/fs/aufs/si_[id]目录下查看aufs的mount的情况,下面是个示例:

#ls /sys/fs/aufs/si_b71b209f85ff8e75/br0      br2      br4      br6      brid1    brid3    brid5    xi_pathbr1      br3      br5      brid0    brid2    brid4    brid6 # cat /sys/fs/aufs/si_b71b209f85ff8e75/*/var/lib/docker/aufs/diff/87315f1367e5703f599168d1e17528a0500bd2e2df7d2fe2aaf9595f3697dbd7=rw/var/lib/docker/aufs/diff/87315f1367e5703f599168d1e17528a0500bd2e2df7d2fe2aaf9595f3697dbd7-init=ro+wh/var/lib/docker/aufs/diff/d0955f21bf24f5bfffd32d2d0bb669d0564701c271bc3dfc64cfc5adfdec2d07=ro+wh/var/lib/docker/aufs/diff/9fec74352904baf5ab5237caa39a84b0af5c593dc7cc08839e2ba65193024507=ro+wh/var/lib/docker/aufs/diff/a1a958a248181c9aa6413848cd67646e5afb9797f1a3da5995c7a636f050f537=ro+wh/var/lib/docker/aufs/diff/f3c84ac3a0533f691c9fea4cc2ceaaf43baec22bf8d6a479e069f6d814be9b86=ro+wh/var/lib/docker/aufs/diff/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158=ro+wh64656667686970/run/shm/aufs.xino

你会看到只有最顶上的层(branch)是rw权限,其它的都是ro+wh权限只读的。

关于docker的aufs的配置,你可以在/var/lib/docker/repositories-aufs这个文件中看到。

AUFS的一些特性

AUFS有所有Union FS的特性,把多个目录,合并成同一个目录,并可以为每个需要合并的目录指定相应的权限,实时的添加、删除、修改已经被mount好的目录。而且,他还能在多个可写的branch/dir间进行负载均衡。

上面的例子,我们已经看到AUFS的mount的示例了。下面我们来看一看被union的目录(分支)的相关权限:

  • rw表示可写可读read-write。
  • ro表示read-only,如果你不指权限,那么除了第一个外ro是默认值,对于ro分支,其永远不会收到写操作,也不会收到查找whiteout的操作。
  • rr表示real-read-only,与read-only不同的是,rr标记的是天生就是只读的分支,这样,AUFS可以提高性能,比如不再设置inotify来检查文件变动通知。

权限中,我们看到了一个术语:whiteout,下面我来解释一下这个术语。

一般来说ro的分支都会有wh的属性,比如 “[dir]=ro+wh”。所谓whiteout的意思,如果在union中删除的某个文件,实际上是位于一个readonly的分支(目录)上,那么,在mount的union这个目录中你将看不到这个文件,但是read-only这个层上我们无法做任何的修改,所以,我们就需要对这个readonly目录里的文件作whiteout。AUFS的whiteout的实现是通过在上层的可写的目录下建立对应的whiteout隐藏文件来实现的。

看个例子:

假设我们有三个目录和文件如下所示(test是个空目录):

# tree.├── fruits│   ├── apple│   └── tomato├── test└── vegetables    ├── carrots    └── tomato

我们如下mount:

# mkdir mnt# mount -t aufs -o dirs=./test=rw:./fruits=ro:./vegetables=ro none ./mnt# # ls ./mnt/apple  carrots  tomato 

现在我们在权限为rw的test目录下建个whiteout的隐藏文件.wh.apple,你就会发现./mnt/apple这个文件就消失了:

 # touch ./test/.wh.apple# ls ./mntcarrots  tomato

上面这个操作和 rm ./mnt/apple是一样的。

相关术语

šBranch – 就是各个要被union起来的目录(就是我在上面使用的dirs的命令行参数)

  • šBranch根据被union的顺序形成一个stack,一般来说最上面的是可写的,下面的都是只读的。
  • šBranch的stack可以在被mount后进行修改,比如:修改顺序,加入新的branch,或是删除其中的branch,或是直接修改branch的权限

šWhiteout 和 Opaque

  • š如果UnionFS中的某个目录被删除了,那么就应该不可见了,就算是在底层的branch中还有这个目录,那也应该不可见了。
  • šWhiteout就是某个上层目录覆盖了下层的相同名字的目录。用于隐藏低层分支的文件,也用于阻止readdir进入低层分支。
  • šOpaque的意思就是不允许任何下层的某个目录显示出来。
  • š在隐藏低层档的情况下,whiteout的名字是’.wh.<filename>’。
  • š在阻止readdir的情况下,名字是’.wh..wh..opq’或者 ’.wh.__dir_opaque’。
相关问题

看到上面这些,你一定会有几个问题:

其一、你可能会问,要有文件在原来的地方被修改了会怎么样?mount的目录会一起改变吗?答案是会的,也可以是不会的。因为你可以指定一个叫udba的参数(全称:User’s Direct Branch Access),这个参数有三个取值:

  • udba=none – 设置上这个参数后,AUFS会运转的更快,因为那些不在mount目录里发生的修改,aufs不会同步过来了,所以会有数据出错的问题。
  • udba=reval – 设置上这个参数后,AUFS会去查文件有没有被更新,如果有的话,就会把修改拉到mount目录内。
  • udba=notify – 这个参数会让AUFS为所有的branch注册inotify,这样可以让AUFS在更新文件修改的性能更高一些。

其二、如果有多个rw的branch(目录)被union起来了,那么,当我创建文件的时候,aufs会创建在哪里呢? aufs提供了一个叫create的参数可以供你来配置相当的创建策略,下面有几个例子。

create=rr | round−robin 轮询。下面的示例可以看到,新创建的文件轮流写到三个目录中

hchen$ sudo mount -t aufs  -o dirs=./1=rw:./2=rw:./3=rw -o create=rr none ./mnthchen$ touch ./mnt/a ./mnt/b ./mnt/chchen$ tree.├── 1│   └── a├── 2│   └── c└── 3    └── b

create=mfs[:second] | most−free−space[:second] 选一个可用空间最好的分支。可以指定一个检查可用磁盘空间的时间。

create=mfsrr:low[:second] 选一个空间大于low的branch,如果空间小于low了,那么aufs会使用 round-robin 方式。

更多的关于AUFS的细节使用参数,大家可以直接在Ubuntu 14.04下通过 man aufs 来看一下其中的各种参数和命令。

AUFS的性能

AUFS的性能慢吗?也慢也不慢。因为AUFS会把所有的分支mount起来,所以,在查找文件上是比较慢了。因为它要遍历所有的branch。是个O(n)的算法(很明显,这个算法有很大的改进空间的)所以,branch越多,查找文件的性能也就越慢。但是,一旦AUFS找到了这个文件的inode,那后以后的读写和操作原文件基本上是一样的。

所以,如果你的程序跑在在AUFS下,open和stat操作会有明显的性能下降,branch越多,性能越差,但是在write/read操作上,性能没有什么变化。

IBM的研究中心对Docker的性能给了一份非常不错的性能报告(PDF)《An Updated Performance Comparison of Virtual Machinesand Linux Containers》

我截了两张图出来,第一张是顺序读写,第二张是随机读写。基本没有什么性能损失的问题。而KVM在随机读写的情况也就有点慢了(但是,如果硬盘是SSD的呢?)

Docker基础技术:AUFS

 

顺序读写

Docker基础技术:AUFS

延伸阅读

  • Introduce UnionFS
  • Union file systems: Implementations, part I
  • Union file systems: Implementations, part 2
  • Another union filesystem approach
  • Unioning file systems: Architecture, features, and design choices

(全文完)



AUFS使用实例

http://blog.csdn.net/lihm0_1/article/details/42030169

 什么是AUFS? 简单说就是一个文件系统,可以把不同的目录联合在一起。这种文件系统不用格式化,直接挂载即可。之所以有这篇博文,是因为Docker中要使用它。在Ubuntu 12.0.4下安装比较简单,其他系统需自己研究。直接apt-get install就可以了,安装后如下:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/test/aufs#cat /etc/issue  
  2. Ubuntu12.04.5 LTS \n \l  
  3. root@localhost:/tmp/test/aufs#dpkg -l | grep aufs  
  4. ii  aufs-tools                          1:3.0+20111101-1ubuntu1            Tools to manage aufs filesystems  

  按传统文件系统使用习惯,分区、格式化、挂载,大概就这三板斧,在AUFS里不用这么麻烦,操作如下:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1#mkdir a b c d  
  2. root@localhost:/tmp/aufs1#echo aaaaaaa > a/a.txt  
  3. root@localhost:/tmp/aufs1#echo bbbbbbb > b/b.txt  
  4. root@localhost:/tmp/aufs1#echo ccccccc > c/c.txt  
  5. root@localhost:/tmp/aufs1#mkdir root  
  6. root@localhost:/tmp/aufs1#ls  
  7. a  b c  d  root  

首先建四个目录 a b c d,然后在a bc 下各生成一个文件,下面做的操作就是要把ab c 三个目录通过AUFS挂载到root目录下

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1#mount -v -t aufs -o br=/tmp/aufs1/a:/tmp/aufs1/b:/tmp/aufs1/c none/tmp/aufs1/root/  
  2. noneon /tmp/aufs1/root type aufs (rw,relatime,si=e7143f80f3aede09)  
  3. root@localhost:/tmp/aufs1#cd root/  
  4. root@localhost:/tmp/aufs1/root#ll  
  5. total20  
  6. drwxr-xr-x4 root root 4096 Dec 19 15:12 ./  
  7. drwxr-xr-x7 root root 4096 Dec 19 15:07 ../  
  8. -rw-r--r--1 root root    8 Dec 19 15:07 a.txt  
  9. -rw-r--r--1 root root    8 Dec 19 15:07 b.txt  
  10. -rw-r--r--1 root root    8 Dec 19 15:07 c.txt  
  11. root@localhost:/tmp/aufs1/root#cat *  
  12. aaaaaaa  
  13. bbbbbbb  
  14. ccccccc  

解释下mount命令各参数含义:

            -t aufs 指定文件系统类型为aufs

            -o 后面是挂载选项,指定我们要挂载哪些目录

            none 说明我们挂载的不是设备文件,因为这里我们是直接挂载目录的

通过cat命令可以查看内容,可以看到就是原文件的内容。

 

AUFS的检测级别可以通过udba指定

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1#  mount -v -t aufs -obr=/tmp/aufs1/a:/tmp/aufs1/b:/tmp/aufs1/c -o udba=none  none /tmp/aufs1/root/  
  2. noneon /tmp/aufs1/root type aufs (rw,relatime,si=e7143f80fc7a7609,udba=none)  

udba有三种级别:none、reval、inotify,对性能的影响依次增加,当然安全性也有所增强。

    None: 这种检测是最快的,但可能导致错误的数据,例如在原始目录修改文件,在aufs中读取,不完全保证正确

    reval:aufs会访问重新原始目录,如果文件有更新,在会反映在aufs中

    Notify: 会在所有原始目录中的所有目录上注册notify事件,这会严重的影响性能,不建议使用。

  AUFS中可以对不同的挂载目录指定不同权限,只读、读写两种权限可以在挂载时指定。

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1#mount -v -t aufs -o br=/tmp/aufs1/a=ro:/tmp/aufs1/b=rw -o udba=none  none /tmp/aufs1/root/  
  2. mount:warning: /tmp/aufs1/root/ seems to be mounted read-only.  
  3. noneon /tmp/aufs1/root type aufs (rw,relatime,si=e7143f87d915ee09,udba=none)  
  4. root@localhost:/tmp/aufs1#cd root/  
  5. root@localhost:/tmp/aufs1/root#ls  
  6. a1.txt  a.txt b.txt  
  7. root@localhost:/tmp/aufs1/root#cat *  
  8. aaaaaaa  
  9. bbbbbbb  
  10. root@localhost:/tmp/aufs1/root#echo AAAA > a.txt  
  11. -bash:a.txt: Read-only file system  
  12. root@localhost:/tmp/aufs1/root#echo BBBB > b.txt  
  13. root@localhost:/tmp/aufs1/root#cat b.txt  
  14. BBBB  
  15. root@localhost:/tmp/aufs1/root#cat /tmp/aufs1/b/b.txt  
  16. BBBB  

     由于aufs并不挂载块设备,所以所有创建的文件会在被挂载的目录中,当有很多目录时,就涉及到了原始目录的选择策略,先看下rr的策略如何使用:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1/b#mount -v -t aufs -o br=/tmp/aufs1/a=rw:/tmp/aufs1/b=rw -o udba=reval -ocreate=rr  none /tmp/aufs1/root  
  2. noneon /tmp/aufs1/root type aufs (rw,relatime,si=e7143f84e8324e09,create=rr)  
  3. root@localhost:/tmp/aufs1/b#cd ../root/  
  4. root@localhost:/tmp/aufs1/root#ll  
  5. total20  
  6. drwxr-xr-x6 root root 4096 Dec 19 16:13 ./  
  7. drwxr-xr-x7 root root 4096 Dec 19 15:07 ../  
  8. -rw-r--r--1 root root    0 Dec 19 15:24 a1.txt  
  9. -rw-r--r--1 root root    8 Dec 19 15:07 a.txt  
  10. -rw-r--r--1 root root    5 Dec 19 15:58 b.txt  
  11. -rw-r--r--1 root root    5 Dec 19 16:02 root.txt  
  12. root@localhost:/tmp/aufs1/root#rm -f root.txt  
  13. root@localhost:/tmp/aufs1/root#touch root1 root2 root3 root4  
  14. root@localhost:/tmp/aufs1/root#tree -l /tmp/aufs1/  
  15. /tmp/aufs1/  
  16. ├──a  
  17. │  ├──a1.txt  
  18. │  ├──a.txt  
  19. │  ├──root1  
  20. │  └──root3  
  21. ├──b  
  22. │  ├──b.txt  
  23. │  ├──root2  
  24. │  └──root4  
  25. ├──c  
  26. │  └──c.txt  
  27. ├──d  
  28. └──root  
  29.     ├──a1.txt  
  30.     ├──a.txt  
  31.     ├──b.txt  
  32.     ├──root1  
  33.     ├──root2  
  34.     ├──root3  
  35.     └──root4  

上面命令首先挂载ab两个目录到root,然后创建四个文件,最后通过tree命令查看,我们看到文件均衡的分布到了ab两个目录中,这是受create=rr命令影响造成的。rr是轮询策略,在所有可用目录中轮询创建文件,如果只有一个目录是可写的,那就没什么意义了。Create有如下选项:

    rr: 轮询选择可写分支

    mfs: 选择剩余空间最多的分支

    mfsrr:首先选择剩余空间最多的分支,然后选择rr方式

    pmfs:选择存在负目录的可写分支

  AUFS支持对现有的AUFS增加新的分支(目录)或删除,deladd等操作,对于一个已有的AUFS,我们如何知道他包含哪些目录呢?

可以通过下面的步骤来实现:

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1#mount -v  
  2. noneon /dockerdata type aufs (rw,relatime,si=e7143f87c2262e09)  
  3. noneon /tmp/test/aufs type aufs (rw,relatime,si=e7143f878b324c09)  
  4. noneon /tmp/aufs1/root type aufs (rw,relatime,si=e7143f84e8324e09,create=rr)  

    记住si的值

[java] view plain copy
 print?在CODE上查看代码片派生到我的代码片
  1. root@localhost:/tmp/aufs1#ls /sys/fs/aufs/si_e7143f84e8324e09/  
  2. br0      br1     xi_path   
  3. root@localhost:/tmp/aufs1#ls /sys/fs/aufs/si_e7143f84e8324e09/br  
  4. br0  br1   
  5. root@localhost:/tmp/aufs1#ls /sys/fs/aufs/si_e7143f84e8324e09/br0  
  6. /sys/fs/aufs/si_e7143f84e8324e09/br0  
  7. root@localhost:/tmp/aufs1#ls /sys/fs/aufs/si_e7143f84e8324e09/br1  
  8. /sys/fs/aufs/si_e7143f84e8324e09/br1  
  9. root@localhost:/tmp/aufs1#cat /sys/fs/aufs/si_e7143f84e8324e09/br1  
  10. /tmp/aufs1/b=rw  

可以看到原始目录和权限信息。


0 0