unionfs联合文件系统
Docker 中的文件存储驱动叫做 storage driver。
Docker 最早支持的stotage driver是 AUFS,它实际上由一层一层的文件系统组成,这种层级的文件系统叫UnionFS。
联合文件系统(UnionFS):Union 文件系统,是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite serveral directories into a single virtual filesystem)。
Union文件系统是Docker镜像的基础。镜像可以通过分层来进行集成,基于基础镜像可以制作具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。
后来出现的docker版本中,除了AUFS,还支持OverlayFS、Btrfs、Device Mapper、VFS、ZFS等storage driver。
rootfs定位
置于整个容器的最底层。
bootfs和rootfs
bootfs(boot file system)主要包含 bootloader 和 kernel,bootloader主要是引导加载 kernel,Linux刚启动时会加载bootfs文件系统。
在Docker镜像的最底层是引导文件系统bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已经由 bootfs 转交给内核,此时系统也会卸载 bootfs。
rootfs(root file system),在bootfs之上,包含的就是典型Linux系统中的 /dev 、 /proc 、 /bin 、 /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu、CentOS等,是copy-on-write资管管理技术叫写时复制的产物。
关于copy-on-write,请拜读这个大佬的blog,此文需要嚼碎这个技术的实现。
big_old_blog_link
常用实现
AUFS
AUFS 只是 Docker 使用的存储驱动的一种,除了 AUFS 之外,Docker 还支持了不同的存储驱动,包括 aufs、devicemapper、overlay2、zfs 和 vfs 等等,在最新的 Docker 中,overlay2 取代了 aufs 成为了推荐的存储驱动,但是在没有 overlay2 驱动的机器上仍然会使用 aufs 作为 Docker 的默认驱动。
overlayfs
Overlayfs 是一种堆叠文件系统,它依赖并建立在其它的文件系统之上(例如 ext4fs 和 xfs 等等),并不直接参与磁盘空间结构的划分,仅仅将原来底层文件系统中不同的目录进行“合并”,然后向用户呈现。
简单的总结为以下3点:
(1)上下层同名目录合并;
(2)上下层同名文件覆盖;
(3)lower dir文件写时拷贝。
操作步骤
[root@master ~]# mkdir ovs [root@master ~]# cd ovs/ [root@master ovs]# ll 总用量 0 [root@master ovs]# mkdir lower [root@master ovs]# echo a > lower/a [root@master ovs]# echo c > lower/c [root@master ovs]# mkdir upper [root@master ovs]# echo a1 > upper/a [root@master ovs]# echo b > upper/b [root@master ovs]# mkdir merged [root@master ovs]# mkdir work #初始内容如下 [root@master ovs]# tree ./ ./ ├── lower │ ├── a │ └── c ├── merged ├── upper │ ├── a │ └── b └── work └── work [root@master ovs]# mount -t overlay overlay -o lowerdir=./lower,upperdir=./upper,workdir=./work ./merged [root@master ovs]# tree ./ ./ ├── lower │ ├── a │ └── c ├── merged │ ├── a │ ├── b │ └── c ├── upper │ ├── a │ └── b └── work └── work
merged 目录已经可以同时看到 lower和upper中的文件了,而由于文件a同时存在于lower和upper中,因此被覆盖了,只显示了一个a。现在可以查看下merged中a文件的值,发现是upper中的值,mount挂载的时候指定lower为lowerdir类型,upper为upperdir类型。
[root@master ovs]# cat merged/a a1
lowerdir是限制为只读,无法进行任何写的操作。
upperdir支持可读可写,可以进行写的操作。
观察下面步骤,可以推出upper跟lower出现同名文件,在merged目录下同名文件内的值是upper目录文件的内容。
[root@master ovs]# echo a2 lower/a a2 lower/a [root@master ovs]# cat lower/a a [root@master ovs]# cat merged/a a1
生成的/merged可以作为container的rootfs
思考??????
1、若是对merged目录下文件进行操作,lower、upper目录下文件内容会不会变化呢?
[root@master merged]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 2 4月 17 17:10 b -rw-r--r-- 1 root root 2 4月 17 17:02 c [root@master merged]# cat * a1 b c [root@master merged]# echo testb > b [root@master merged]# echo testc > c [root@master merged]# cat * a1 testb testc
lower目录
[root@master lower]# ll 总用量 8 -rw-r--r-- 1 root root 2 4月 17 17:02 a -rw-r--r-- 1 root root 2 4月 17 17:02 c [root@master lower]# cat c c [root@master lower]#
upper目录
[root@master upper]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master upper]# cat b c testb testc
出现了一个有意思的地方,upper目录本身就只有a和b俩个文件,突然就多了一个c文件,对比前面mount tree后的目录架构,这是在操作merged文件的时候生成的,而且lower值没有变动,却都生成在了upper下。
原因:因为lower不可进行写操作,所以采用了 CoW 技术,在对 c 进行修改时,复制了一份数据到 overlay 的 upperdir,从 lower copy 到 upper,也叫做 copy_up。
2、删除文件会发生事情呢?
现在lower目录生成一个f文件。
[root@master lower]# echo f > f [root@master lower]# ll 总用量 12 -rw-r--r-- 1 root root 2 4月 17 17:02 a -rw-r--r-- 1 root root 2 4月 17 17:02 c -rw-r--r-- 1 root root 2 4月 17 17:34 f
查看upper目录和merged目录框架,f文件出现在了merged,此时upper没有f文件。
[root@master ovs]# cd upper/ [root@master upper]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master upper]# cd ../merged/ && ll 总用量 16 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c -rw-r--r-- 1 root root 2 4月 17 17:34 f [root@master merged]# cat f f
删除merged中的f文件,查看upper目录和lower目录,upper中出现了一个大小为0的c类型文件f
[root@master merged]# rm -rf f [root@master merged]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master merged]# ll ../upper/ 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c c--------- 1 root root 0, 0 4月 17 17:53 f [root@master merged]# ll ../lower/ 总用量 12 -rw-r--r-- 1 root root 2 4月 17 17:02 a -rw-r--r-- 1 root root 2 4月 17 17:02 c -rw-r--r-- 1 root root 2 4月 17 17:34 f
删除upper目录的f文件,就会发现,merged目录中又出现了f文件,若是删除lower中f文件,merged和upper中就没有f文件。
[root@master merged]# rm -rf ../upper/f [root@master merged]# ll 总用量 16 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c -rw-r--r-- 1 root root 2 4月 17 17:34 f [root@master merged]# cat f f #删除lower中f文件 [root@master merged]# rm -rf ../lower/f [root@master merged]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master merged]# ll ../upper/ 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c
换成upper目录生成一个文件执行上述流程,发现merged删除了后,upper目录也没有该文件。
[root@master upper]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master upper]# echo g > g [root@master upper]# ll 总用量 16 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c -rw-r--r-- 1 root root 2 4月 17 18:00 g [root@master upper]# [root@master upper]# cd ../merged/ && ll 总用量 16 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c -rw-r--r-- 1 root root 2 4月 17 18:00 g [root@master merged]# cat g g [root@master merged]# rm -rf g [root@master merged]# ll 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master merged]# ll ../upper/ 总用量 12 -rw-r--r-- 1 root root 3 4月 17 17:03 a -rw-r--r-- 1 root root 6 4月 17 17:25 b -rw-r--r-- 1 root root 6 4月 17 17:25 c [root@master merged]# ll ../lower/ 总用量 8 -rw-r--r-- 1 root root 2 4月 17 17:02 a -rw-r--r-- 1 root root 2 4月 17 17:02 c
综上所述:
lowerdir类型目录的文件,在merged删除后,会在upperdir类型目录生成一个c类型的删除标记,删除该标记,merged内又会出现这个文件,lowerdir类型目录整个过程不受影响,若想真的彻底删除文件,只能在lowerdir类型目录下删除。
upperdir类型目录,在merged删除文件后,upperdir类型目录就直接彻底删除文件,不会有标记。
总之:只有可读类型目录下文件在merged中删除后会在upperdir类型目录生成可回退标记,可写文件在merged中删除后就是直接彻底删除。
如何在docker实际中实现overlay?
思考???
1、merged既然是rootfs,rootfs是一个操作系统,那镜像分层打包是否跟它有关系呢??
2、怎样去理解镜像分层跟dockerfile内copy、add、echo和run等等可写操作的关系?
3、dockerfile生成的文件内容在哪呢?
剖析
docker三大要素:images repository container
image是静态体现,image经过run后进行runtime变成container,利用镜像分层解释,image为镜像层,为只读、不可进行写操作,当容器启动时,一个新的可写层将被加载到镜像的顶部,这一层通常被称为容器层。
猜想:是不是一个容器(包含lowerdir和upperdir)进行了一次commit,他是不是变成了静态也就是镜像层,是不是也就是等于一个merged目录进行了commit,它只是最底层也就是layer0,merged又是lowerdir目录和upperdir目录合并生成的,最终的结果是不是每次执行完写操作后commit都会生成不同大小且内容不一样的rootfs,生成了layer1....layerN?
证实一下
镜像分层理论:layer1是在layer0的基础上套了一层可写层也就是upperdir类型操作,进行了增删改查。一次类推
拿centos:7举例:
/var/lib/docker docker默认存储目录
image:镜像相关
overlay2:docker 文件所在目录,也可能不叫这个名字,具体和文件系统有关,比如可能是 aufs 等
layerdb:docker image layer 信息 元数据
imagedb:docker image 信息 元数据
[root@master merged]# docker images | grep centos centos 7 eeb6ee3f44bd 19 months ago 204MB [root@master merged]# cd /var/lib/docker [root@master docker]# cd image/ [root@master image]# ll 总用量 0 drwx------. 5 root root 81 4月 17 18:36 overlay2 [root@master image]# cd overlay2/ [root@master overlay2]# ll 总用量 8 drwx------. 4 root root 58 4月 12 11:52 distribution drwx------. 4 root root 37 4月 12 10:52 imagedb drwx------. 5 root root 45 4月 12 11:54 layerdb -rw------- 1 root root 4185 4月 17 18:36 repositories.json [root@master overlay2]# cd imagedb/ [root@master imagedb]# ll 总用量 0 drwx------. 3 root root 20 4月 12 10:52 content drwx------. 3 root root 20 4月 12 10:52 metadata [root@master imagedb]# pwd /var/lib/docker/image/overlay2/imagedb
查看centos镜像的imagedb content,根据imageid查到所属content,查到diff_ids,type为layers,看到只有一层sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02
[root@master sha256]# ll | grep eeb6ee3f44bd -rw------- 1 root root 2754 4月 17 18:36 eeb6ee3f44bd0b5103bb561b4c16bcb82328cfe5809ab675bb17ab3a16c517c9 [root@master sha256]# cat eeb6ee3f44bd0b5103bb561b4c16bcb82328cfe5809ab675bb17ab3a16c517c9 | jq . | tail } ], "os": "linux", "rootfs": { "type": "layers", "diff_ids": [ "sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02" ] } }
编辑dockerfile,模拟写操作
[root@master test]# cat Dockerfile FROM centos:7 RUN echo "Hello world" > /tmp/newfile [root@master test]# docker build -t hello-centos . [+] Building 0.6s (6/6) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 92B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/centos:7 0.0s => [1/2] FROM docker.io/library/centos:7 0.0s => [2/2] RUN echo "Hello world" > /tmp/newfile 0.6s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:19fd5667d4b3483ce76a4a219da2c1a8303922c370d4942a1c8a8cef12883c1c 0.0s => => naming to docker.io/library/hello-centos 0.0s
查看生成的镜像,会发现在174下又多了一层diff_ids。关注rootfs等于一直在套娃。
[root@master test]# docker images | grep hello hello-centos latest 19fd5667d4b3 About a minute ago 204MB [root@master test]# cd /var/lib/docker docker/ dockershim/ [root@master test]# cd /var/lib/docker/image/overlay2/imagedb/content/sha256/ [root@master sha256]# ll | grep 19fd5667d4b3 -rw------- 1 root root 2132 4月 17 18:48 19fd5667d4b3483ce76a4a219da2c1a8303922c370d4942a1c8a8cef12883c1c [root@master sha256]# cat 19fd5667d4b3483ce76a4a219da2c1a8303922c370d4942a1c8a8cef12883c1c | jq . | tail "moby.buildkit.buildinfo.v1": "eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJhdHRycyI6eyJmaWxlbmFtZSI6IkRvY2tlcmZpbGUifSwic291cmNlcyI6W3sidHlwZSI6ImRvY2tlci1pbWFnZSIsInJlZiI6ImRvY2tlci5pby9saWJyYXJ5L2NlbnRvczo3In0seyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvY2VudG9zOjciLCJwaW4iOiJzaGEyNTY6MTc0ZjU2ODU0OTAzMjZmYzBhMWMwZjU1NzBiODY2MzczMjE4OWIzMjcwMDdlNDdmZjEzZDJjYTU5NjczZGIwMiJ9XX0=", "os": "linux", "rootfs": { "type": "layers", "diff_ids": [ "sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02", "sha256:e59ab628089ad0067e9bf80156b7a5ab67af7de217e9f700835dea340c611247" ] } }
上述已经证实了思考1和2。
结论如下:
1、每一次镜像打包(commit),就会生成一个新的镜像,是在基础的rootfs上加入写操作生成一个不同大小且内容不一样的rootfs,未运行起来的时候就是一个images,运行起来就是一个等待commit的容器。
2、dockerfile中每次copy、add等写操作都是一次commit,每次commit后就会在原有layer上加一层可写层,所以docker build构建慢的话,尝试优化下写操作的行数,尽量一行搞定。
接着见证下思考3
既然要看文件内容,那找到存储的地方即可
文件含义如下:
cache-id:为具体/var/lib/docker/overlay2/<cache-id>存储路径
diff:diffID,用于计算 ChainID
size:当前 layer 的大小
docker使用了chainID的方式来保存layer,layer.ChainID只用本地,根据layer.DiffID计算,并用于layerdb的目录名称。
chainID唯一标识了一组(像糖葫芦一样的串的底层)diffID的hash值,包含了这一层和它的父层(底层),
当然这个糖葫芦可以有一颗山楂,也就是chainID(layer0)==diffID(layer0);
对于多颗山楂的糖葫芦,ChainID(layerN) = SHA256hex(ChainID(layerN-1) + " " + DiffID(layerN))
[root@master layerdb]# pwd /var/lib/docker/image/overlay2/layerdb [root@master layerdb]# ll 总用量 8 drwxr-xr-x 32 root root 4096 4月 17 15:22 mounts drwxr-xr-x 40 root root 4096 4月 17 18:48 sha256 drwxr-xr-x 2 root root 6 4月 17 18:48 tmp [root@master layerdb]# cd sha256/ && ll | grep 174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02 drwx------ 2 root root 71 4月 17 18:36 174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02 [root@master sha256]# cd 174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02 [root@master 174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02]# ll 总用量 616 -rw-r--r-- 1 root root 64 4月 17 18:36 cache-id -rw-r--r-- 1 root root 71 4月 17 18:36 diff -rw-r--r-- 1 root root 9 4月 17 18:36 size -rw-r--r-- 1 root root 615388 4月 17 18:36 tar-split.json.gz [root@master 174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02]# cat diff sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02
由于是layer0,所以 chainID 就是 diffID,然后开始计算 layer1 的 chainID,
"rootfs": { "type": "layers", "diff_ids": [ "sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02", "sha256:e59ab628089ad0067e9bf80156b7a5ab67af7de217e9f700835dea340c611247" ] } [root@master 174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02]# echo -n "sha256:174f5685490326fc0a1c0f5570b8663732189b327007e47ff13d2ca59673db02 sha256:e59ab628089ad0067e9bf80156b7a5ab67af7de217e9f700835dea340c611247" | sha256sum| awk '{print $1}' 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6 [root@master sha256]# cd 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6 [root@master 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6]# pwd /var/lib/docker/image/overlay2/layerdb/sha256/57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6 [root@master 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6]# ll 总用量 20 -rw-r--r-- 1 root root 25 4月 17 18:48 cache-id -rw-r--r-- 1 root root 71 4月 17 18:48 diff -rw-r--r-- 1 root root 71 4月 17 18:48 parent -rw-r--r-- 1 root root 2 4月 17 18:48 size -rw-r--r-- 1 root root 317 4月 17 18:48 tar-split.json.gz [root@master 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6]# cat size 12[root@master 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6]# cat diff sha256:e59ab628089ad0067e9bf80156b7a5ab67af7de217e9f700835dea340c611247[root@master 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6]# [root@master 57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6]# cat cache-id 5ah0zbkrwbuegl2doi4zee804
计算得知layer1为57f8217a884e130476cafd7384920774cd3d4d488d65863046ff1f4f5f28d5c6,直接去改文件目录下查看cashe-id获取存储名称,获取名称为5ah0zbkrwbuegl2doi4zee804。查看/var/lib/docker/overlay2/5ah0zbkrwbuegl2doi4zee804
查询到newfile,内容为Hello world。
[root@master 5ah0zbkrwbuegl2doi4zee804]# pwd /var/lib/docker/overlay2/5ah0zbkrwbuegl2doi4zee804 [root@master 5ah0zbkrwbuegl2doi4zee804]# ll 总用量 8 drwxr-xr-x 4 root root 28 4月 17 18:48 diff -rw-r--r-- 1 root root 26 4月 17 18:48 link -rw-r--r-- 1 root root 28 4月 17 18:48 lower drwx------ 3 root root 18 4月 17 18:48 work [root@master 5ah0zbkrwbuegl2doi4zee804]# cd diff/ [root@master diff]# ll 总用量 0 drwxr-xr-x 2 root root 6 4月 17 18:48 etc drwxrwxrwt 2 root root 21 4月 17 18:48 tmp [root@master diff]# cd tmp/ [root@master tmp]# ll 总用量 4 -rw-r--r-- 1 root root 12 4月 17 18:48 newfile [root@master tmp]# cat newfile Hello world [root@master tmp]# pwd /var/lib/docker/overlay2/5ah0zbkrwbuegl2doi4zee804/diff/tmp
收工~~~