概要
1 aufs文件下系统是什么?有什么特性?
2 aufs文件系统与docker镜像的关系。
3 dockfile如何工作?
4 docker镜像储存到哪里?储存的什么?
5 docker镜像的储存过程简介。
aufs文件下系统是什么?有什么特性?
aufs文件系统全名为advanced multi-layered unification filesystem.
它能够将不同类型的文件系统透明地层叠在一起,实现一个高效的分层文件系统。简言之,它能将不同的目录挂在到统一目录下。我们从几个简单的实验窥探一下aufs文件系统的挂载特性。
实验系统环境
OS : Ubuntu 14.04.2 LTS (GNU/Linux 3.16.0-30-generic x86_64)
docker : client/server 1.12.1
挂载命令:mount -t aufs -o br=path/to/dir/A:path/to/dir/A none path/to/dir/uniondir
mount命令中,-t表示目标文件系统类型,-o表示挂载参数, none表示挂载的不是设备文件。这些都是mount命令的参数。br是aufs的参数,表示分支,多个分支之间用冒号分隔。
挂载实验 ``` mkdir a b uniondir echo 1 > a/1.txt echo 2 > b/2.txt tree #├── a #│ └── 1.txt #├── b #│ └── 2.txt #└── uniondir sudo mount -t aufs -o br=/home/zyf/aufstest/a:/home/zyf/aufstest/b none /home/zyf/aufstest/uniondir tree #├── a #│ └── 1.txt #├── b #│ └── 2.txt #└── uniondir # ├── 1.txt # └── 2.txt ``` 由此可见我们将a目录的1.txt和b目录的2.txt挂载到了uniondir下。 在挂在时候,我们指定a目录在前,所以a目录是逻辑的上层。br参数没有加读写权限之前,逻辑上层(a目录)为读写权限。其余层(b目录)为制度权限。所以如果我们在uniondir创建3.txt,此文件会创建在a目录中。 ``` cd uniondir vim 3.txt cd ../ & tree #├── a #│ ├── 1.txt #│ └── 3.txt #├── b #│ └── 2.txt #└── uniondir # ├── 1.txt # ├── 2.txt # └── 3.txt ``` 同样的,我们如果在一开始在a目录中创建2.txt。在挂在时候会优先挂载2.txt。 因为读写权限的不同,修改1.txt和2.txt内容对a、b的影响也不同: ``` echo 'add sth' >> /root/test/uniondir/1.txt echo 'add sth' >> /root/test/uniondir/2.txt cd ../ & tree #├── a #│ ├── 1.txt #| | 2.txt #│ └── 3.txt #├── b #│ └── 2.txt #└── uniondir # ├── 1.txt # ├── 2.txt # └── 3.txt ``` 可见,对可读写分支(目录a),修改直接作用到分支上; 而对于只读分支(目录b),修改操作会触发一次写时复制(COW, Copy On Write) 即先将待修改文件从目录b复制到目录a,然后在目录a修改相应文件。 注意写时复制机制可能在修改大文件时带来性能损耗,虽然每个文件最多只会复制一次。
aufs文件系统与docker镜像的关系。
docker镜像在构建的时候利用了aufs文件系统的挂载原理,对文件系统进行构建。
首先,存在一个 docker 容器在启动时其内部进程可见的文件系统视角,或者称为docker 容器的根目录rootfs。目录下含有 docker 容器所需要的系统文件、工具、容器文件等。(只读属性)
接着,在特定的镜像中存在每一个镜像独有的文件系统(读写属性)
docker这时候利用aufs进行两者挂载。形成一个镜像。可以由示意图表示。
docker 镜像复用的增量特性docker 众所周知是一种很省资源的容器。这一特性很大程度得益于aufs系统特性使得docker镜像进行复用。
从上图中我们可以看到,无论是unbuntu这种OS系统,还是mysql,mongo这种具体应用。docker利用的aufs文件系统在文件系统挂载时的(COW机制)对镜像进行复用。
所以我们假设 docker build 构建出来的镜像名分别为 image 1 和 image 2,由于两个 Dockerfile 均基于ubuntu:14.04,因此,image 1 和 image 2 这两个镜像均复用了镜像 ubuntu:14.04。 假设 RUN apt-get update 修改的文件系统内容为 20 MB,最终本地三个镜像的大小关系应该如下 :
ubuntu:14.04: 200 MB image 1:200 MB(ubuntu:14.04 的大小)+ 20 MB = 220 MB image 2:200 MB(ubuntu:14.04 的大小)+ 100 MB = 300 MB
如果仅仅是单纯的累加三个镜像的大小,那结果应该是:200 + 220 + 300 = 720 MB,但是由于镜像复用的存在,实际占用的磁盘空间大小是:200 + 20 + 100 + 320 MB,足足节省了 400 MB 的磁盘空间。在此,足以证明镜像复用的巨大好处。
dockfile如何工作?
既然说到了image,那不妨再谈一下dockerfile。具体是如何工作的。
Dockerfile 由多条指令构成,随着深入研究 Dockerfile 与镜像的关系,会发现,Dockerfile 中的每一条指令都会对应于 Docker 镜像中的一层。
如下 Dockerfile 为例:
FROM ubuntu:14.04 ADD run.sh / VOLUME /data CMD ["./run.sh"]
通过 docker build 以上 Dockerfile 的时候,会在 ubuntu:14.04 镜像基础上,添加三层独立的镜像,依次对应于三条不同的命令。镜像示意图如下:
有了 Dockerfile 与镜像关系的初步认识之后,我们再进一步联系到每一层镜像的大小。在层级化管理的 Docker 镜像中,有不少层大小都为 0。那些镜像层大小不为 0 的情况,归根结底的原因是:构建 Docker 镜像时,对当前的文件系统造成了修改更新。而修改更新的情况主要有两种:
ADD 或 COPY 命令:ADD 或者 COPY 的作用是在 docker build 构建镜像时向容器中添加内容,只要内容添加成功,当前构建的那层镜像就是添加内容的大小,如以上命令 ADD run.sh /,新构建的那层镜像大小为文件 run.sh 的大小。
RUN 命令:RUN 命令的作用是在当前空的镜像层内运行一条命令,倘若运行的命令需要更新磁盘文件,那么所有的更新内容都在存储在当前镜像层中。举例说明:RUN echo DaoCloud 命令不涉及文件系统内容的修改,故命令运行完之后当前镜像层的大小为 0;RUN wget http://abc.com/def.tar 命令会将压缩包下载至当前目录下,因此当前这一层镜像的大小为:对文件系统内容的增量修改部分,即 def.tar 文件的大小(在成功执行的情况下)。
docker镜像储存到哪里?储存的什么?
docker将镜像,存储到了/var/lib/docker/下面。
Docker 镜像层的内容一般在 Docker 根目录的 aufs 路径下,为 /var/lib/docker/aufs/diff/,具体情况如下:
root@hmp-pc:/var/lib/docker/aufs/diff# ls | xargs ls 0a838d1341807dc3c97008ffbbc4c97e309f8323b9a6ed44db57b5dba1e37d1c: etc 169bc4f555486d96269d377a6c4363fc70b3f280815630123c12fed6cc077200: etc sbin usr var 1d4c362445794e698d4b115de91a610894c0038b94df399d7c6ef363ed70550c: bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var 223ff0b1885840f717348ce856a6569f43476eadcc174ebfd77015159d7eaa78:
另外出了镜像文件之外,还需要对每一个镜像层储存的json文件(一般json储存在了docker/graph目录中,此系统没有找到。)在此系统储存道了/var/lib/docker/image/aufs下的imagedb中
root@hmp-pc:/var/lib/docker/image/aufs/imagedb/content/sha256# vim 0027e029bd6c37ffc292b52d447d0a44d25a03c88415090a942074cdf228e216 {"architecture":"amd64","config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/bash"]............
docker镜像的储存过程。
镜像储存过程在docker源码中分成了以下四部。他们分别是:
- 验证镜像ID
- 创建镜像路径
- 储存镜像内容
- 注册镜像ID
在创建镜像路径中使用了mount union的挂载计数。如下图所示
在储存镜像内容中,实现的是
解压镜像内容 layerData 至 diff 目录;收集镜像所占空间大小,并记录;将 jsonData 信息写入临时文件。这三部操作。最终镜像储存到了/var/lib/docker/auf中。
参考资料:
关于镜像储存
http://docs.daocloud.io/allen-docker/docker-image-details
关于aufs文件系统
http://www.infoq.com/cn/articles/analysis-of-docker-file-system-aufs-and-devicemapper
关于镜像储存过程与镜像理解