Docker数据存储原理

Docker使用存储驱动来存储镜像像层,并将数据存储在容器的可写层中。容器的可写层在容器被删除后将不复存在,但其适合存储运行时生成的临时数据。存储驱动为空间效率进行了优化,但是(取决于存储驱动)写入速度低于本机文件系统的性能,特别是对于使用“写时复制”文件系统的存储驱动。overlay2和overlay驱动都比aufs和devicemapper性能更好。在某些情况下,overlay2也可能比btrfs表现得更好。

1. 镜像与层

Docker镜像是由一系列层成的,每一层代表Dockerfile中的一条指令。除了最后一层(可读写层),每一层都是只读的。

我们可以查看一下nginx的镜像分层如下

那么这个镜像是怎么存储在磁盘上的,可以通过docker image inspect nginx:

  在“Data”中每一个目录指示了nginx镜像是如何存储的。

LowerDir:底层目录; diff(只是存储不同);包含小型linux和装好的软件(倒着看)

"LowerDir": 
"/var/lib/docker/overlay2/0a1039b33f370cea167df86b1a88108e83539169709966cdae50bc4e4b09b437/diff:保存用户文件
/var/lib/docker/overlay2/8e49615cdbf3e7bc49a25884a5556e2944a39ffad3b9f0cde52f6d25b95be43e/diff:保存nginx启动命令
/var/lib/docker/overlay2/b9073eeae08bbfbd94e946227ddadb9fab401aad8a4fde7f53fcd4dad806d509/diff:保存nginx配置文件
/var/lib/docker/overlay2/f25764ef52d28fb7fcfcc275638f7b4d873cf402c4d20393e1dc1aad9f1ee375/diff:保存Linux小型系统

1)小linux系统(FROM apline) + Dockerfile的每一个命令可能都引起了系统的修改,所以和git一样,只记录变化;

2)我们进入到这个镜像启动的容器,容器的文件系统就是镜像的。

这几层是叠在一起的,当创建一个新容器时,将在底层层之上添加一个新的可写层,这一层通常称为容器层。对运行容器所做的所有更改,例如写入新文件、修改现有文件和删除文件,都被写入这个可写的容器层。下图显示了一个基于ubuntu:15.04镜像的容器。

2. 容器与层

容器和镜像的主要区别在于最上面的可写层,所有对容器的写入(添加或修改数据)都存储在这个可写层中。当容器被删除时,可写层也被删除。底镜像保持不变。因为每个容器都有自己的可写容器层,并且所有更改都存储在这个容器层中,所以多个容器可以共享对相同底层镜像的访问,同时拥有自己的数据状态。下图显示了多个容器共享同一个Ubuntu 15.04镜像。

容器会自己建立层;如果想要改东西,把改的内容复制到容器层即可 docker container inspect nginx:

"LowerDir": "/var/lib/docker/overlay2/3f07843c0faeb2be19df1b8e3e7dff616ae1f58abe7339e7d5d244079bbcbd0f-init/diff:
/var/lib/docker/overlay2/224d8df55111a91d25555ac8dfd3a18c01dd023d600cfca012c870f21161d051/diff:
/var/lib/docker/overlay2/0a1039b33f370cea167df86b1a88108e83539169709966cdae50bc4e4b09b437/diff:
/var/lib/docker/overlay2/8e49615cdbf3e7bc49a25884a5556e2944a39ffad3b9f0cde52f6d25b95be43e/diff:
/var/lib/docker/overlay2/b9073eeae08bbfbd94e946227ddadb9fab401aad8a4fde7f53fcd4dad806d509/diff:
/var/lib/docker/overlay2/f25764ef52d28fb7fcfcc275638f7b4d873cf402c4d20393e1dc1aad9f1ee375/diff"(由镜像层映射过来), "MergedDir"(合并目录;容器最终的完整工作目录全内容都在合并层;数据卷在容器层产生;所有的增删改都在容器层): "/var/lib/docker/overlay2/3f07843c0faeb2be19df1b8e3e7dff616ae1f58abe7339e7d5d244079bbcbd0f/merged", "UpperDir"(容器内部数据修改保存在此,容器删除后,数据将不复存在): "/var/lib/docker/overlay2/3f07843c0faeb2be19df1b8e3e7dff616ae1f58abe7339e7d5d244079bbcbd0f/diff", "WorkDir"(工作目录(临时层)): "/var/lib/docker/overlay2/3f07843c0faeb2be19df1b8e3e7dff616ae1f58abe7339e7d5d244079bbcbd0

Docker推荐使用overlay2存储驱动。OverlayFS在单个Linux主机上分层两个目录,并将它们表示为单个目录。这些目录称为层,统一过程称为联合挂载。OverlayFS将下目录称为lowerdir,将上目录称为upperdir。统一视图层通过它自己的名为merge的目录公开。下图展示了Docker镜像和Docker容器是如何分层的。镜像层是下目录,容器层是上目录。统一视图层通过一个名为merged的目录公开,它实际上是容器挂载点。这个图展示了Docker构造如何映射到OverlayFS构造。

其中镜像层和容器层包含相同的文件,容器层获得并覆盖了镜像层中存在的相同文件。overlay驱动只工作于这两个层,它意味着不能将多层镜像实现为多个OverlayFS层。相反,每个镜像层被实现为在/var/lib/docker/overlay下的自己的目录。然后使用硬链接作为一种空间效率高的方法来引用与下层共享的数据。硬链接的使用导致了inode的过度使用,这是遗留覆盖存储驱动的一个已知限制,并且可能需要对后备文件系统进行额外配置。要创建容器时,overlay驱动将表示镜像顶层的目录和容器的新目录组合在一起。镜像的顶层是覆盖层中的下层,并且是只读的。容器的新目录是upperdir,并且是可写的。

Docker使用存储驱动来管理镜像层和可写容器层的内容,每个存储驱动处理实现的方式不同,但是所有驱动程序都使用可堆叠的映像层和写时复制(CoW)策略。

3. 镜像与容器在磁盘上的大小

镜像的大小与构建的基础镜像大小和构建的层数有关系,一旦镜像构建完成,镜像的大小就就等于基础镜像大小加上之上的每一层大小之和。通常我们是通过优化Dockerfile语句以及多阶段构建来“瘦身”我们的镜像。

容器的大小可以可以使用docker ps -s命令查看,我们可以看到有关两列大小相关参数size与virtual size。

size:用于每个容器的可写层的(磁盘上的)数据量。

virtual size:容器使用的只读镜像数据的数据量加上容器的可写层大小。多个容器可能共享部分或全部只读镜像像数据。从同一个镜像开始的两个容器共享100%的只读数据,而两个具有相同层的不同镜像的容器共享这些公共层。因此,不能只是合计虚拟大小。这可能会高估总磁盘使用量,估计的数量可能相当大。

磁盘上所有运行的容器所使用的总磁盘空间是每个容器大小和虚拟大小值的某种组合。例:如果多个容器从相同的镜像开始,那么这些容器在磁盘上的总大小将是容器大小(size)的总和加上一个镜像大小(virtual size)。

注意这总大小并不包括容器占用磁盘空间的以下几种方式:

1)日志驱动存储的日志文件所使用的磁盘空间。如果您的容器生成了大量的日志数据,并且没有配置日志中转,那么这就不是一件简单的事情。

2)容器使用的卷和绑定挂载。

3)用于容器配置文件的磁盘空间,这些配置文件通常较小。

4)写入磁盘的内存(如果启用了交换内存)。

5)检查点,如果您正在使用实验性的检查点/恢复特性。

4. 写时复制

1)写时复制是一种共享和复制文件以获得最大效率的策略。

2)如果一个文件或目录存在于镜像的较低一层中,而另一层(包括可写层)需要对其进行读访问,那么它只使用现有的文件。

3)当另一层第一次需要修改文件时(在构建镜像或运行容器时),将文件复制到该层并进行修改。这将最小化I/O和后续每一层的大小。下面将更深入地解释这些优点。

posted @ 2021-12-01 17:08  ThreeCode  阅读(284)  评论(0编辑  收藏  举报