联合文件系统是docker实现的基础,采用了核心思想写时复制的原理,镜像的数据作为底层数据只读,用户的数据采用可写的形式

docker采用的文件系统的形式,可以采用docker info命令查看

  1. docker info

关于详细的讲解和实验可以参考:
https://www.jianshu.com/p/5ec3d4dbf580
https://www.cnblogs.com/sparkdev/p/11237347.html
https://zhuanlan.zhihu.com/p/392508816

以下内容转载自:https://zhuanlan.zhihu.com/p/392508816

一、镜像、容器和层

docker中镜像是层级结构的,即图中的image layers,每一层只是与它之前的层的一组差异。这些层堆叠在彼此的顶部。当我们创建一个新容器时,会在镜像层加一个新的可写层。这一层通常被称为“容器层”。对正在运行的容器所做的所有更改,例如写入新文件、修改现有文件和删除文件,都将写入这个薄的可写容器层。

image.png
那么一个镜像创建多个容器时是怎样的景象呢?如下图所示,添加新数据或修改现有数据的所有写入容器都存储在此可写层中。当容器被删除时,可写层也被删除。底层镜像保持不变。因为每个容器都有自己的可写容器层,所有的变化都存储在这个容器层中,所以多个容器可以共享对同一个底层镜像的访问,同时又拥有自己的数据状态。
image.png
到这里你可能要问了:镜像为什么要分层啊?乱七八糟的!
其实不然,通过镜像的层级结构主要的一个优点是你可以把你的基础镜像进行共享,什么意思呢?比如你现在需要一个Nginx镜像、一个Tomcat镜像它们都可以通过一个base镜像如centos或者ubuntu制作而成,它看起来是这样的
image.png
如此一来通过镜像分层可以大大减少磁盘空间占用,同时降低镜像复构建杂度,何乐而不为


二、联合文件系统OverlayFS

通过上面我们大概了解了镜像、容器和层的关系,那么又有一个问题了:镜像层和可写容器层的文件或者内容是如何来管理的?明明是分层的又是怎么合并的?
接下来我们将介绍UnionFS(联合文件系统),它的厉害之处在于可以将多个目录挂载到一个根目录。OverlayFS 是linux现代联合文件系统的一个代表,合并于Linux内核的3.18版本。从 docker 18.06后docker为OverlayFS提供了两个存储驱动,原始的overlay及overlay2(改善 inode 利用率),overlay2是目前docker推荐和首选存储驱动,通过它来管理镜像层和可写容器层内容。
我们可以在docker info中查看docker存储驱动版本

  1. [root@i-k9pwet2d ~]# docker info
  2. ...
  3. Server Version: 20.10.6
  4. Storage Driver: overlay2
  5. Backing Filesystem: extfs
  6. ...

OverlayFS这种堆叠的文件系统,依赖于其他文件系统之上,比如我们在info 中看到的extfs或者xfs等,它的结构如下图:
image.png
我们的基础层称为“lowerdir”即原始文件所在的位置。
客户端所做的任何修改都将反映在“upperdir”层上:

  • 如果更改文件,新版本将写入其中(file1)。
  • 如果删除文件,将在该层上创建一个删除标记(file2)。
  • 创建一个新文件(file4)。

最后,“merged”是所有层合并后的最终视图。
假如你有一些数据,需要多个进程来访问和修改它。每个进程都要创建一个独立的数据视图,你要存储多份原始数据,数据量大的话显然这会非常低效的。使用OverlayFS将会是very good!
接下来我们来我们搞个实验看看
我建立如下目录结构,workdir在OverlayFS中需要为空,用作内部临时存储。lowerdir包含3个文件file1、file2、file3

  1. [root@i-k9pwet2d overlayfs_test]# tree .
  2. .
  3. ├── client_1
  4. ├── upperdir
  5. └── workdir
  6. ├── client_2
  7. ├── upperdir
  8. └── workdir
  9. ├── lowerdir
  10. ├── file1.txt
  11. └── file2.txt
  12. └── file3.txt
  13. └── merged
  14. ├── client_1
  15. └── client_2
  16. 10 directories, 3 files

挂载overlay

  1. mount -t overlay overlay \
  2. -o lowerdir=/overlaytest/lowerdir \
  3. -o upperdir=/overlaytest/client_1/upperdir \
  4. -o workdir=/overlaytest/client_1/workdir \
  5. /overlaytest/merged/client_1
  6. mount -t overlay overlay \
  7. -o lowerdir=/overlaytest/lowerdir \
  8. -o upperdir=/overlaytest/client_2/upperdir \
  9. -o workdir=/overlaytest/client_2/workdir \
  10. /overlaytest/merged/client_2

挂载后查看我们的视图,可以看到三个文件已经被合并到merged区了

  1. [root@i-k9pwet2d overlaytest]# tree .
  2. .
  3. ├── client_1
  4. ├── upperdir
  5. └── workdir
  6. └── work
  7. ├── client_2
  8. ├── upperdir
  9. └── workdir
  10. └── work
  11. ├── lowerdir
  12. ├── file1.txt
  13. ├── file2.txt
  14. └── file3.txt
  15. └── merged
  16. ├── client_1
  17. ├── file1.txt
  18. ├── file2.txt
  19. └── file3.txt
  20. └── client_2
  21. ├── file1.txt
  22. ├── file2.txt
  23. └── file3.txt

下一步我们修改merged/client_1下修改我们的都数据

[root@i-k9pwet2d client_1]# echo "data no.1">>file1.txt 
[root@i-k9pwet2d client_1]# rm file2.txt 
[root@i-k9pwet2d client_1]# echo "data4" > file4.txt
[root@i-k9pwet2d client_1]# ls
file1.txt  file3.txt  file4.txt

再看我们的视图,可以看到修改只作用于client_1/upperdir,对我们lowerdir下原始数据以及client_2数据并不影响。

[root@i-k9pwet2d overlaytest]# tree .
.
├── client_1
│   ├── upperdir
│   │   ├── file1.txt
│   │   ├── file2.txt
│   │   └── file4.txt
│   └── workdir
│       └── work
├── client_2
│   ├── upperdir
│   └── workdir
│       └── work
├── lowerdir
│   ├── file1.txt
│   ├── file2.txt
│   └── file3.txt
└── merged
    ├── client_1
    │   ├── file1.txt
    │   ├── file3.txt
    │   └── file4.txt
    └── client_2
        ├── file1.txt
        ├── file2.txt
        └── file3.txt

12 directories, 12 files

三、OverlayFS在容器中的实现

下图显示了 在Docker 中镜像和 容器是如何通过OverlayFS分层与互相构造的映射。图像层是lowerdir,容器层是upperdir。统一视图合并到merged目录,该目录实际上是容器安装点。
image.png
我们查看一个真正运行容器centos的inspect

docker inspect ca9a9e0a35c7

可以看到OverlayFS对应的目录地址,lowerDir即我们的原始数据包含image的rootfs(根文件)以及init相关文件,关于docker init层可以自行检索一下哈,这里不做介绍了。

"GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968-init/diff:/var/lib/docker/overlay2/0d6b94986ba1af1cc75e7c237f78d7e02d40f5ae5ec3f67ddb699ae6d07a2ca8/diff",
                "MergedDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/merged",
                "UpperDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/diff",
                "WorkDir": "/var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/work"
            },
            "Name": "overlay2"
        },

到LowerDir下查看原始rootfs

[root@i-k9pwet2d client_1]# ls /var/lib/docker/overlay2/0d6b94986ba1af1cc75e7c237f78d7e02d40f5ae5ec3f67ddb699ae6d07a2ca8/diff
bin  dev  etc  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

我们进入容器添加和删除文件看看

[root@i-k9pwet2d ~]# docker exec -it ca9a /bin/bash

[root@ca9a9e0a35c7 /]# echo "newfile" >file 

[root@ca9a9e0a35c7 /]# rm /tmp/ks-script-esd4my7v

显然到到LowerDir下查看原始rootfs并不受影响而是把变更写入到了upperdir即容器层

cd  /var/lib/docker/overlay2/444808f5d6566eebf4ea73ea593b2c2076d4347ff57bd98cbc179dbac9265968/diff

[root@i-k9pwet2d diff]# tree .
.
├── file
└── tmp
    └── ks-script-esd4my7v

1 directory, 2 files

以上的操作也就是docker中所谓的CoW(写时复制)策略。在docker中overlay2驱动对联合文件系统操作的更多场景可以参阅官方文档