OverlayFS
概览
overlayFS是一种新型联合文件系统(union filesystem),它允许用户将一个文件系统与另一个文件系统重叠(overlay),在上层的文件系统中记录更改,而下层得文件系统保持不变。
Overlay主要使用4类目录完成工作,被联合挂载的两个目录lower和upper,作为统一视图联合挂载点得merged目录,还有作为辅助功能的work目录。作为upper和lower被联合挂载得统一视图。当同一路径下得两个文件分别存在两个目录中时,位于上层的目录upper中得文件会屏蔽位于下层lower中得文件,如果是同路径的文件夹,那么下层目录中的文件和文件夹会被合并到上层。在对可读写的OverlayFS挂载目录中的文件进行读写删等操作过程与挂载两层的aufs(下层是只读层,上层是可读可写层)是类似的,需要注意的一点是,第一次(涉及到修改lower中的数据)以write方式打开一个位于下层目录的文件时OverlayFS会执行一个copy_up将文件从下层复制到上层,与aufs不同的是,这个copy_up的实现不符合POSIX标准。
代码实战:
一开始overlay路径下为空
[root@luyuni-aliyun ~]# tree -L 2 /var/lib/docker/overlay/
/var/lib/docker/overlay/
0 directories, 0 files
把ubuntu镜像pull下来,我们发现这个镜像有4层
[root@luyuni-aliyun ~]# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
692c352adcf2: Pull complete
97058a342707: Pull complete
2821b8e766f4: Pull complete
4e643cc37772: Pull complete
Digest: sha256:55cd38b70425947db71112eb5dddfa3aa3e3ce307754a3df2269069d2278ce47
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest
去overlay目录下面看一下
[root@luyuni-aliyun ~]# tree -L 2 /var/lib/docker/overlay/
/var/lib/docker/overlay/
├── 862976c524a76652a52d8966cf8b4d77ac3cf8ef17f7129d8cfb3685a89b4009
│ └── root
├── 886eb412f86d1926714db6031f17791d4c66cb2db0156b115a867378093d48ae
│ └── root
├── cf9d7c3cfb138d043defcb902c2fa3bc29f371701ade715974b983958ded4f93
│ └── root
└── e814ca9180804d1aee91358457f7ef835b20a722c45115503314e8048a778ff9
└── root
我们可以发现每一层都有一个对应的目录,包含了该层镜像的内容,通过tree命令发现,每个镜像层下面之包含一个root目录(因为docker1.10开始,使用了基于内容的寻址,因此目录名和镜像层的id不一致)
[root@luyuni-aliyun ~]# ls -i /var/lib/docker/overlay/862976c524a76652a52d8966cf8b4d77ac3cf8ef17f7129d8cfb3685a89b4009/root/bin/ls
1573201 /var/lib/docker/overlay/862976c524a76652a52d8966cf8b4d77ac3cf8ef17f7129d8cfb3685a89b4009/root/bin/ls
[root@luyuni-aliyun ~]# ls -i /var/lib/docker/overlay/886eb412f86d1926714db6031f17791d4c66cb2db0156b115a867378093d48ae/root/bin/ls
1573201 /var/lib/docker/overlay/886eb412f86d1926714db6031f17791d4c66cb2db0156b115a867378093d48ae/root/bin/ls
每一层都包含了该层独有的文件以及和其低层共享的数据的硬链接,如ls命令,每一层都使用了一个硬链接来指向实际ls命令的inode号。
docker run 一把刚刚拉下来的镜像
[root@luyuni-aliyun ~]# docker run -itd ubuntu /bin/bash
206cdf00e86a0879d8b6de3979ea51eacafb7bb84e53c3da51acf5b74965c108
docker ps看一下有没有把容器起起来
[root@luyuni-aliyun ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
206cdf00e86a ubuntu "/bin/bash" 4 seconds ago Up 4 seconds dreamy_pascal
看起来没有问题。创建容器是,实际上实在已有的镜像层上创建了一层容器层,容器层在/var/lib/docker/overlay下也存在对应的目录
[root@luyuni-aliyun ~]# tree -L 2 /var/lib/docker/overlay/
/var/lib/docker/overlay/
├── 862976c524a76652a52d8966cf8b4d77ac3cf8ef17f7129d8cfb3685a89b4009
│ └── root
├── 886eb412f86d1926714db6031f17791d4c66cb2db0156b115a867378093d48ae
│ └── root
├── c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547
│ ├── lower-id
│ ├── merged
│ ├── upper
│ └── work
├── c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547-init
│ ├── lower-id
│ ├── upper
│ └── work
├── cf9d7c3cfb138d043defcb902c2fa3bc29f371701ade715974b983958ded4f93
│ └── root
└── e814ca9180804d1aee91358457f7ef835b20a722c45115503314e8048a778ff9
└── root
15 directories, 2 files
我们可以看到新增了c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547(读写层);c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547-init(初始层)。两个目录。初始层中大多是初始化容器环境时,与容器相关的环境信息,如容器主机名,主机host信息以及域名服务文件等。所有对容器做出的改变都记录在读写层
看一眼容器使用了哪层镜像
[root@luyuni-aliyun ~]# cat /var/lib/docker/overlay/c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547/lower-id
e814ca9180804d1aee91358457f7ef835b20a722c45115503314e8048a778ff9
[root@luyuni-aliyun ~]# cat /var/lib/docker/overlay/c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547-init/lower-id
e814ca9180804d1aee91358457f7ef835b20a722c45115503314e8048a778ff9
在刚刚run起来的容器里创建一个文件
root@206cdf00e86a:/# echo "niyulu" > luyunix
root@206cdf00e86a:/# ls -l
total 52
lrwxrwxrwx 1 root root 7 Jul 3 01:56 bin -> usr/bin
drwxr-xr-x 2 root root 4096 Apr 15 11:09 boot
drwxr-xr-x 5 root root 360 Jul 14 09:40 dev
drwxr-xr-x 1 root root 4096 Jul 14 09:40 etc
drwxr-xr-x 2 root root 4096 Apr 15 11:09 home
lrwxrwxrwx 1 root root 7 Jul 3 01:56 lib -> usr/lib
lrwxrwxrwx 1 root root 9 Jul 3 01:56 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 Jul 3 01:56 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 Jul 3 01:56 libx32 -> usr/libx32
-rw-r--r-- 1 root root 7 Jul 14 10:00 luyunix
drwxr-xr-x 2 root root 4096 Jul 3 01:57 media
drwxr-xr-x 2 root root 4096 Jul 3 01:57 mnt
drwxr-xr-x 2 root root 4096 Jul 3 01:57 opt
dr-xr-xr-x 82 root root 0 Jul 14 09:40 proc
drwx------ 2 root root 4096 Jul 3 02:00 root
drwxr-xr-x 5 root root 4096 Jul 6 21:56 run
lrwxrwxrwx 1 root root 8 Jul 3 01:56 sbin -> usr/sbin
drwxr-xr-x 2 root root 4096 Jul 3 01:57 srv
dr-xr-xr-x 13 root root 0 Jul 14 09:40 sys
drwxrwxrwt 2 root root 4096 Jul 3 02:00 tmp
drwxr-xr-x 13 root root 4096 Jul 3 01:57 usr
drwxr-xr-x 11 root root 4096 Jul 3 02:00 var
看看刚才创建的文件去了哪里
[root@luyuni-aliyun ~]# tree -L 3 /var/lib/docker/overlay
/var/lib/docker/overlay
├── 862976c524a76652a52d8966cf8b4d77ac3cf8ef17f7129d8cfb3685a89b4009
│ └── root
│ ├── bin -> usr/bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib -> usr/lib
│ ├── lib32 -> usr/lib32
│ ├── lib64 -> usr/lib64
│ ├── libx32 -> usr/libx32
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin -> usr/sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
├── 886eb412f86d1926714db6031f17791d4c66cb2db0156b115a867378093d48ae
│ └── root
│ ├── bin -> usr/bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib -> usr/lib
│ ├── lib32 -> usr/lib32
│ ├── lib64 -> usr/lib64
│ ├── libx32 -> usr/libx32
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin -> usr/sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
├── c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547
│ ├── lower-id
│ ├── merged
│ │ ├── bin -> usr/bin
│ │ ├── boot
│ │ ├── dev
│ │ ├── etc
│ │ ├── home
│ │ ├── lib -> usr/lib
│ │ ├── lib32 -> usr/lib32
│ │ ├── lib64 -> usr/lib64
│ │ ├── libx32 -> usr/libx32
│ │ ├── luyunix
│ │ ├── media
│ │ ├── mnt
│ │ ├── opt
│ │ ├── proc
│ │ ├── root
│ │ ├── run
│ │ ├── sbin -> usr/sbin
│ │ ├── srv
│ │ ├── sys
│ │ ├── tmp
│ │ ├── usr
│ │ └── var
│ ├── upper
│ │ ├── dev
│ │ ├── etc
│ │ └── luyunix
│ └── work
│ └── work
├── c71bf48d2d255833d53ada016cef3ad087e6d33fbc02d67a42ada6ba69f63547-init
│ ├── lower-id
│ ├── upper
│ │ ├── dev
│ │ └── etc
│ └── work
│ └── work
├── cf9d7c3cfb138d043defcb902c2fa3bc29f371701ade715974b983958ded4f93
│ └── root
│ ├── bin -> usr/bin
│ ├── boot
│ ├── dev
│ ├── etc
│ ├── home
│ ├── lib -> usr/lib
│ ├── lib32 -> usr/lib32
│ ├── lib64 -> usr/lib64
│ ├── libx32 -> usr/libx32
│ ├── media
│ ├── mnt
│ ├── opt
│ ├── proc
│ ├── root
│ ├── run
│ ├── sbin -> usr/sbin
│ ├── srv
│ ├── sys
│ ├── tmp
│ ├── usr
│ └── var
└── e814ca9180804d1aee91358457f7ef835b20a722c45115503314e8048a778ff9
└── root
├── bin -> usr/bin
├── boot
├── dev
├── etc
├── home
├── lib -> usr/lib
├── lib32 -> usr/lib32
├── lib64 -> usr/lib64
├── libx32 -> usr/libx32
├── media
├── mnt
├── opt
├── proc
├── root
├── run
├── sbin -> usr/sbin
├── srv
├── sys
├── tmp
├── usr
└── var
126 directories, 4 files
我们发现读写层的upper目录下多出了一个luyunix文件,就是刚才在容器中创建的文件
总结:
Overlay的原理就是将一层目录重叠与另一层目录之上,也就是说OverlayFS文件系统只会涉及两个目录,而Docker镜像却可能有许多层,为了解决这种不对应的情况,overlay存储驱动在存储镜像层的时候,会把父镜像中的内容复制到当前层,然后再写入当前层,为了节省存储空间,在复制的过程中,普通文件是采用硬连接的方式链接到父镜像层对应的文件,其它类型的文件或文件夹则是按照原来的内容重新创建。所以上层镜像层拥有其依赖镜像层的所有文件,而最上面镜像层则拥有了整个镜像的文件系统,这也就是为什么镜像层对应的目录中只有一个root文件夹。(简单的说,上层拥有下层所有文件,对于一样的文件,直接硬链接,不一样的文件复制一份然后做出改变)
对于另一种目录来说,文件lower-id用来索引该容器使用的镜像层,upper目录包含了容器层的内容,每当启动一个容器时,会将lower-id指向的镜像层目录以及upper目录联合挂载到merged目录,因此,容器内的视角就是merged目录下的内容。而work目录则是用来完成如copy-up的操作