容器中通过使用不同的rootfs来模拟了不同的操作系统及应用,不过,如果使用每个镜像都需要一个独立的根文件系统的话,那想必磁盘早已拥挤不堪了;且一个镜像可以同时运行多个容器,每个容器对文件的改动该怎么办?
Linux提供了一种叫做联合文件系统的文件系统,它具备如下特性:
联合挂载:将多个目录按层次组合,一并挂载到一个联合挂载点。
写时复制:对联合挂载点的修改不会影响到底层的多个目录,而是使用其他目录记录修改的操作。
目前有多种文件系统可以被当作联合文件系统,实现如上的功能:overlay2,aufs,devicemapper,btrfs,zfs,vfs等等。而overlay2就是其中的佼佼者,也是docker目前推荐的文件系统:https://docs.docker.com/storage/storagedriver/select-storage-driver/
Overlay2 overlay2是一个类似于aufs的现代的联合文件系统,并且更快。overlay2已被收录进linux内核,它需要内核版本不低于4.0,如果是RHEL或Centos的话则不低于3.10.0-514。
我们可以查看下自己环境docker配置的存储驱动是什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 [root@slions_pc1 ~]# docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 1 Server Version: 18.09.3 Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true Native Overlay Diff: true Logging Driver: json-file Cgroup Driver: cgroupfs Plugins: Volume: local Network: bridge host macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog Swarm: inactive Runtimes: runc Default Runtime: runc Init Binary: docker-init containerd version: d71fcd7d8303cbf684402823e425e9dd2e99285d runc version: N/A init version: fec3683 Security Options: seccomp Profile: default Kernel Version: 3.10.0-957.el7.x86_64 Operating System: CentOS Linux 7 (Core) OSType: linux Architecture: x86_64 CPUs: 2 Total Memory: 3.683GiB Name: slions_pc1 ID: 7W4N:NDNK:BDXL:ADXV:ONCZ:PUA7:KSK2:73JQ:Q3HS:AEEK:VKAL:H3RC Docker Root Dir: /var/lib/docker Debug Mode (client): false Debug Mode (server): false Registry: https://index.docker.io/v1/ Labels: Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false Product License: Community Engine
默认就是overlay2,如果你本地环境不是overlay2,只需编辑/etc/docker/daemon.json
, 添加以下内容并重启docker。
1 2 3 { "storage-driver" : "overlay2" }
docker默认的存储目录是/var/lib/docker
,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [root@slions_pc1 ~]# ls -l /var/lib/docker 总用量 0 drwx------. 2 root root 24 7月 10 11:52 builder drwx------. 4 root root 92 7月 10 11:52 buildkit drwx------. 3 root root 78 7月 12 13:08 containers drwx------. 3 root root 22 7月 10 11:52 image drwxr-x---. 3 root root 19 7月 10 11:52 network drwx------. 6 root root 261 7月 12 14:52 overlay2 drwx------. 4 root root 32 7月 10 11:52 plugins drwx------. 2 root root 6 7月 12 12:07 runtimes drwx------. 2 root root 6 7月 10 11:52 swarm drwx------. 2 root root 6 7月 12 12:07 tmp drwx------. 2 root root 6 7月 10 11:52 trust drwx------. 2 root root 25 7月 10 11:52 volumes
在这里,我们只关心image
和overlay2
就足够了。
在我本地起了一个叫slions的容器,容器id为47c26762a49a
,镜像id 为4cdc5dd7eaad
,这两个id作为了容器和镜像的唯一标识符,我们后面会用到
1 2 3 4 5 6 7 8 9 [root@slions_pc1 ~]# docker run -itd --name slions nginx:latest 47c26762a49a3f397e6796cf499f97acbc606a68c04d08b9cd169d6e33469c99 [root@slions_pc1 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 47c26762a49a nginx:latest "/docker-entrypoint.…" 3 seconds ago Up 2 seconds 80/tcp slions [root@slions_pc1 ~]# docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 4cdc5dd7eaad 5 days ago 133MB
docker会在/var/lib/docker/image
目录下按每个存储驱动的名字创建一个目录,如这里的overlay2
。
1 2 3 [root@slions_pc1 ~]# ll /var/lib/docker/image/ 总用量 0 drwx------. 5 root root 81 7月 12 14:52 overlay2
查看overlay2下的目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@slions_pc1 ~]# tree -L 2 /var/lib/docker/image/overlay2/ /var/lib/docker/image/overlay2/ ├── distribution │ ├── diffid-by-digest │ └── v2metadata-by-diffid ├── imagedb │ ├── content │ └── metadata ├── layerdb │ ├── mounts │ ├── sha256 │ └── tmp └── repositories.json
看名字就能知道这儿是用来存放元数据的, 其中还细分为了imagedb
和layerdb
,因为在docker中,image是由多个layer组合而成的,换句话就是layer是一个共享的层,可能有多个image会指向某个layer。 那如何才能确认image包含了哪些layer呢?答案就在imagedb
这个目录中去找。
比如上面启动的slions容器,我们已知image id为4cdc5dd7eaad
,接着打印/var/lib/docker/image/overlay2/imagedb/content/sha256
这个目录:
1 2 3 [root@slions_pc1 ~]# ll /var/lib/docker/image/overlay2/imagedb/content/sha256 总用量 8 -rw-------. 1 root root 7729 7月 12 15:59 4cdc5dd7eaadff5080649e8d0014f2f8d36d4ddf2eff2fdf577dd13da85c5d2f
4cdc5dd7eaadff5080649e8d0014f2f8d36d4ddf2eff2fdf577dd13da85c5d2f
正是记录slions镜像元数据的文件,接下来cat一下这个文件,得到一个长长的json:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@slions_pc1 ~]# cat /var/lib/docker/image/overlay2/imagedb/content/sha256/4 cdc5dd7eaadff5080649e8d0014f2f8d36d4ddf2eff2fdf577dd13da85c5d2f |jq { ....... "os" : "linux" , "rootfs" : { "type" : "layers" , "diff_ids" : [ "sha256:764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7" , "sha256:ace9ed9bcfafbc909bc3e9451490652f685959db02a4e01e0528a868ee8eab3e" , "sha256:48b4a40de3597ec0a28c2d4508dec64ae685ed0da77b128d0fb5c69cada91882" , "sha256:c553c6ba5f1354e1980871b413e057950e0c02d2d7a66b39de2e03836048fda9" , "sha256:d97733c0a3b64c08bc0dd286926a8eff1b162b4d9fad229eab807c6dc516c172" , "sha256:9d1af766c81806211d5453b711169103e4f5c3c2609e1dfb9ea4dee7e96a7968" ] } }
查看最后的rootfs部分,diff_ids是一个包含了6个元素的数组,这6个元素正是组成nginx
镜像的6个layerID,从上往下看,就是底层到顶层,也就是说764055e...
是image的最底层。既然得到了组成这个image的所有layerID,那么我们就可以带着这些layerID去寻找对应的layer了。
接下来,我们返回到上一层的layerdb
中,先打印一下这个目录:
1 2 3 4 5 6 [root@slions_pc1 ~]# ll /var/lib/docker/image/overlay2/layerdb/ 总用量 4 drwxr-xr-x. 3 root root 78 7月 12 16:00 mounts drwxr-xr-x. 8 root root 4096 7月 12 15:59 sha256 drwxr-xr-x. 2 root root 6 7月 12 15:59 tmp
在这里我们只管mounts
和sha256
两个目录,再打印一下sha256
目录:
1 2 3 4 5 6 7 8 9 [root@slions_pc1 ~]# ll /var/lib/docker/image/overlay2/layerdb/sha256/ 总用量 0 drwx------. 2 root root 85 7月 12 15:59 2c78bcd3187437a7a5d9d8dbf555b3574ba7d143c1852860f9df0a46d5df056a drwx------. 2 root root 85 7月 12 15:59 435c6dad68b58885ad437e5f35f53e071213134eb9e4932b445eac7b39170700 drwx------. 2 root root 85 7月 12 15:59 63d268dd303e176ba45c810247966ff8d1cb9a5bce4a404584087ec01c63de15 drwx------. 2 root root 71 7月 12 15:59 764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7 drwx------. 2 root root 85 7月 12 15:59 b27eb5bbca70862681631b492735bac31d3c1c558c774aca9c0e36f1b50ba915 drwx------. 2 root root 85 7月 12 15:59 bdf28aff423adfe7c6cb938eced2f19a32efa9fa3922a3c5ddce584b139dc864
在这里,我们仅仅发现764055e..
这个最底层的layer,那么剩余的layer为什么会没有呢?那是因为docker1.10版本以后layer层之间的关系通过chainId来进行关联。
公式是:chainID=sha256sum(H(chainID) diffid),也就是764055e..
的上一层的sha256 id是:
1 2 [root@slions_pc1 ~]# echo -n "sha256:764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7 sha256:ace9ed9bcfafbc909bc3e9451490652f685959db02a4e01e0528a868ee8eab3e" | sha256sum - 2c78bcd3187437a7a5d9d8dbf555b3574ba7d143c1852860f9df0a46d5df056a -
依次类推,我们就能找出所有的layerID的组合。 但是上面我们提到,/var/lib/docker/image/overlay2/layerdb
存的只是元数据,那么真实的rootfs到底存在哪里呢?
答案就在cache-id
中。我们打印一下
/var/lib/docker/image/overlay2/layerdb/sha256/764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7/cache-id
:
1 2 [root@slions_pc1 ~]# cat /var/lib/docker/image/overlay2/layerdb/sha256/764055ebc9a7a290b64d17cf9ea550f1099c202d83795aa967428ebdf335c9f7/cache-id 45f0a48f47ef308ead10a1b92856a0d3b8a54294518bd178ca6288c7d54a38be
这个id就是对应/var/lib/docker/overlay2/45f0a48f47ef308ead10a1b92856a0d3b8a54294518bd178ca6288c7d54a38be
。因此,以此类推,更高一层的layer对应的cache-id也能找到对应的rootfs,当这些rootfs的diff目录通过联合挂载的方式挂载到某个目录,就能提供整个容器需要的rootfs了。
overlay存储流程
最下层是一个 lower 层,也就是镜像层,它是一个只读层。右上层是一个 upper 层,upper 是容器的读写层,upper 层采用了写时复制的机制,也就是说只有对某些文件需要进行修改的时候才会从 lower 层把这个文件拷贝上来,之后所有的修改操作都会对 upper 层的副本进行修改。
upper 并列的有一个 workdir,它的作用是充当一个中间层的作用。也就是说,当对 upper 层里面的副本进行修改时,会先放到 workdir,然后再从 workdir 移到 upper 里面去,这个是 overlay 的工作机制。
最上面的是 mergedir,是一个统一视图层。从 mergedir 里面可以看到 upper 和 lower 中所有数据的整合,然后我们 docker exec 到容器里面,看到一个文件系统其实就是 mergedir 统一视图层。
在运行容器后,可以通过mount命令查看其具体挂载信息:
1 2 [root@slions_pc1 ~]# mount -t overlay overlay on /var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/merged type overlay (rw,relatime,seclabel,lowerdir=/var/lib/docker/overlay2/l/XZ6TYQO6XXCBXKH6E4N3N7QTWU:/var/lib/docker/overlay2/l/U6O63XRKJYL3MKFKZCZ33EEDE6:/var/lib/docker/overlay2/l/Y2XZUZZM42C6HPF6BHHLAMGY2G:/var/lib/docker/overlay2/l/P35XQEQ2NQELR5QWTI3POCZPJG:/var/lib/docker/overlay2/l/KRCQJWNO5RDBHNQOQXFCLT2JLX:/var/lib/docker/overlay2/l/5KKQYPGMGGXBSLAIEHV72IJ2QH:/var/lib/docker/overlay2/l/4PZ2UIG6UG3GF2PLSFP6C6X7AO,upperdir=/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/diff,workdir=/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/work)
可以看到:
merged:/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/merged
upperdir:/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/diff
workdir:/var/lib/docker/overlay2/0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/work
lowerdir:/var/lib/docker/overlay2/l/XZ6TYQO6XXCBXKH6E4N3N7QTWU:...:.../overlay2/l/IG6UG3GF2PLSFP6C6X7AO
冒号分隔多个lowerdir,从左到右层次越低。
细心的你肯定发现了,lowerdir有7个,但是怎么看起来这么奇怪呢,其实观察下/var/lib/docker/overlay2/l/
就会发现,正是之前我们得到的那6个rootfs目录的链接文件外加一个新的容器目录文件。
1 2 3 4 5 6 7 8 9 10 11 [root@slions_pc1 overlay2]# ll /var/lib/docker/overlay2/l/ 总用量 0 lrwxrwxrwx. 1 root root 72 7月 12 15:59 4PZ2UIG6UG3GF2PLSFP6C6X7AO -> ../45f0a48f47ef308ead10a1b92856a0d3b8a54294518bd178ca6288c7d54a38be/diff lrwxrwxrwx. 1 root root 72 7月 12 15:59 5KKQYPGMGGXBSLAIEHV72IJ2QH -> ../de169f3af7d5f47b3d83eeb21408ecb637dd5fd4847d31f7644a72830c16db65/diff lrwxrwxrwx. 1 root root 72 7月 12 16:00 AD3GPWKO46I5LBSKACROCGTCFN -> ../0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232/diff lrwxrwxrwx. 1 root root 72 7月 12 15:59 KRCQJWNO5RDBHNQOQXFCLT2JLX -> ../e78f609099df22f4ab410322759923ebab67112a3a88fc323b4bdeb98b00d8d2/diff lrwxrwxrwx. 1 root root 72 7月 12 15:59 P35XQEQ2NQELR5QWTI3POCZPJG -> ../18998b6cffbcc8eb5984c1077e00d02d6d2f150a889c8c856055364bfa16b0a2/diff lrwxrwxrwx. 1 root root 72 7月 12 15:59 U6O63XRKJYL3MKFKZCZ33EEDE6 -> ../192d460630fe2fefa59151b3a7b37032346487ac6af719d3be3f2e1dfd373494/diff lrwxrwxrwx. 1 root root 77 7月 12 16:00 XZ6TYQO6XXCBXKH6E4N3N7QTWU -> ../0b319b849790fe43488a0f5aa76ec318640b05d7d1ec4e162ee80ebc16c43232-init/diff lrwxrwxrwx. 1 root root 72 7月 12 15:59 Y2XZUZZM42C6HPF6BHHLAMGY2G -> ../aca1597583b8862fae70d6bfc7487a041092a7b9d1cf400802d11225b2fb3f5b/diff
overlay实验 创建并挂载测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 [root@slions_pc1 ~]# mkdir tmp [root@slions_pc1 ~]# cd tmp/ [root@slions_pc1 tmp]# mkdir -p lower{1..2}/dir upper/dir worker merge [root@slions_pc1 tmp]# ll 总用量 0 drwxr-xr-x. 3 root root 17 7月 12 16:42 lower1 drwxr-xr-x. 3 root root 17 7月 12 16:42 lower2 drwxr-xr-x. 2 root root 6 7月 12 16:42 merge drwxr-xr-x. 3 root root 17 7月 12 16:42 upper drwxr-xr-x. 2 root root 6 7月 12 16:42 worker [root@slions_pc1 tmp]# for i in 1 2 ;do touch lower$i/foo$i;done [root@slions_pc1 tmp]# tree . ├── lower1 │ ├── dir │ └── foo1 ├── lower2 │ ├── dir │ └── foo2 ├── merge ├── upper │ └── dir └── worker [root@slions_pc1 tmp]# mount -t overlay overlay -o lowerdir=lower1:lower2,upperdir=upper,workdir=worker merge/ [root@slions_pc1 tmp]# mount |grep overlay ... overlay on /root/tmp/merge type overlay (rw,relatime,seclabel,lowerdir=lower1:lower2,upperdir=upper,workdir=worker)
挂载了一个名为overlay的overlay类型的文件系统,挂载点为merged目录。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [root@slions_pc1 tmp]# touch upper/foo3 [root@slions_pc1 tmp]# echo "from lower1" >lower1/dir/aa [root@slions_pc1 tmp]# echo "from lower2" >lower2/dir/aa [root@slions_pc1 tmp]# echo "from lower1 bb" >lower2/dir/bb [root@slions_pc1 tmp]# echo "from upper" >upper/dir/bb [root@slions_pc1 tmp]# tree . ├── lower1 │ ├── dir │ │ └── aa │ └── foo1 ├── lower2 │ ├── dir │ │ ├── aa │ │ └── bb │ └── foo2 ├── merge │ ├── dir │ │ ├── aa │ │ └── bb │ ├── foo1 │ ├── foo2 │ └── foo3 ├── upper │ ├── dir │ │ └── bb │ └── foo3 └── worker └── work [root@slions_pc1 tmp]# cat merge/dir/aa from lower1 [root@slions_pc1 tmp]# cat merge/dir/bb from upper
删除文件测试
删除文件时,会在upper层创建whiteout的特殊文件,overlayfs会自动过过滤掉和whiteout文件自身以及和它同名的lower层文件和目录,达到了隐藏文件的目的,让用户以为文件已经被删除了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 [root@slions_pc1 tmp]# tree . ├── lower1 │ ├── dir │ │ └── aa │ └── foo1 ├── lower2 │ ├── dir │ │ ├── aa │ │ └── bb │ └── foo2 ├── merge │ ├── dir │ │ ├── aa │ │ └── bb │ ├── foo1 │ ├── foo2 │ └── foo3 ├── upper │ ├── dir │ │ └── bb │ └── foo3 └── worker └── work 10 directories, 12 files [root@slions_pc1 tmp]# rm merge/foo1 rm:是否删除普通空文件 "merge/foo1"?y [root@slions_pc1 tmp]# tree . ├── lower1 │ ├── dir │ │ └── aa │ └── foo1 ├── lower2 │ ├── dir │ │ ├── aa │ │ └── bb │ └── foo2 ├── merge │ ├── dir │ │ ├── aa │ │ └── bb │ ├── foo2 │ └── foo3 ├── upper │ ├── dir │ │ └── bb │ ├── foo1 │ └── foo3 └── worker └── work
参考文档 Use the OverlayFS storage driver:https://docs.docker.com/storage/storagedriver/overlayfs-driver/#how-the-overlay2-driver-works
Docker storage drivers:https://docs.docker.com/storage/storagedriver/select-storage-driver/
Docker存储驱动之–overlay2:https://www.jianshu.com/p/3826859a6d6e
深入理解overlayfs(二):使用与原理分析:https://blog.csdn.net/linshenyuan1213/article/details/82527712