前言

本文主要会介绍笔者在学习容器的存储原理时所总结的知识点,其中会涉及到Docker支持的容器存储类型、Docker的容器共享数据方式以及Docker数据卷的生命周期管理方面的相关内容。 笔者也会将自己的理解在文中进行阐述,这也算是在和大家交流心得的一个过程。若文中有错误的理解和概念,请大家及时纠正;吸纳大家的建议,对于我来说也是很重要的学习过程之一。


(目录)


1.容器存储类型

Docker提供了两类用于存放数据的资源: 容器文件系统和数据卷。

Tips: 无论使用哪种类型的存储,实际上消耗的还是Host或者是Host外接的存储资源。

1.1 容器文件系统

这种存储资源实际上就是在使用容器镜像自身的文件系统,而Docker通过Docker storage driver来管理这类存储资源。这类存储资源一般会应用在部署了无状态服务/应用的容器上,即容器文件系统适用于存储哪些无需持久化的,临时性质的数。

挂载在容器根目录上并用来为容器进程提供隔离后执行环境的文件系统就是容器镜像文件系统(容器镜像),其也被称作是容器的rootfs(根文件系统)。

Tips: rootfs只包含一个操作系统所需的文件、配置和目录,但并不包括操作系统内核。在 Linux 操作系统中,操作系统只有在开机启动时才会加载指定版本的内核镜像。

Docker创建一个容器进程的步骤为:

  1. 启用 Linux Namespace 配置 即对容器的资源进行隔离。
  2. 设置指定的 Cgroups 参数 即对容器进行资源限制配置。
  3. 切换进程的根目录(Change Root) 即使内核能够加载到rootfs中的系统数据。

可以看到,如果没有容器镜像文件系统的支持,Docker是无法创建出一个新容器进程的。

容器镜像文件系统是使用OverlayFS来实现的,它是容器镜像多种特性的实现原理。

1.1.1 OverlayFS

1.1.1.1 UnionFS

具体来说,OverlayFS实际上又是UnionFS的一种实现方式

UnionFS文件系统主要特点是其能够把位于不同的分区的多个目录一起挂载(mount)到一个目录下。这样的挂载方式就可以实现数据/目录的复用,即相同的数据/目录无需保存多份,可以通过混合mount的形式来实现复用。

unionfs5.png

该文件系统能够有效地减少磁盘上冗余的数据,同时减少冗余的数据在网络上的传输。 OverlayFS作为UnionFS的一种实现方式,在UnionFS上述的这些功能特点的基础之上,OverlayFS还有一些其特有的概念。

1.1.1.2 目录类型

在OverlayFS中,存在几种不同类型的层级。在使用OverlayFS时,需要为这些层级指定相应的目录后才能正常使用OverlayFS文件系统。

An-instance-of-OverlayFS.jpg

OverlayFS中的目录类型有:

  1. merged 挂载点(mount point)目录,即以用户视角看到的目录。用户的实际文件操作在这里进行,即mount操作的目标目录

  2. lower 这一层里的文件是不会被修改的,即lower层是只读的。并且OverlayFS支持一次挂载多个lower层的目录

  3. upper 如果有文件的创建,修改,删除操作,这些操作的执行结果都会在这一层反映出来,即upper是可读写的。upper层内保存了所有对于目标目录的写操作内容。

  4. work 用于存放临时文件的目录。与liunx中的/tmp目录类似。

1.1.1.3 目录层级关系

lower和upper层的文件会映射到merged层中。此时如果在两个层中存在相同的文件,则在merged层中只会看到upper层的对应文件,即upper层会覆盖lower层

1.1.1.4 文件操作实现原理

在OverlayFS中,文件操作主要就是通过对上述几类不同的目录的操作来实现的。

  1. 新建文件 在merged层中新建的文件会出现在upper层对应的目录中。

  2. 删除文件 在删除文件时,OverlayFS会在所有目录层级中搜索目标文件并对其进行删除操作

    1. 目标文件存在upper层中 文件会在upper目录中被删除。

    2. 目标文件存在lower层中 此时在lower目录里的文件不会有变化,OverlayFS会在upper目录中增加了一个特殊文件来标识该文件文件不能出现在 merged/ 里了,即表示文件已经被删除。

  3. 修改操作 OverlayFS会对处于不同目录层级中的目标文件进行不同的修改操作:

    1. 文件存在于upper层中 此时只需直接在upper层对应的目录中修改指定文件即可。
    2. 文件存在于lower层中 那么就会在upper目录中新建一个文件,新文件中包含更新的内容。而在lower中的原文件不会改变。实际上此时是先将存在于lower层中的文件拷贝到upper层中,然后再修改upper层中的文件。

    Tips: 这种修改数据的方式就是copy-on-write,写时复制

  4. 读取操作 在查找需要读取的文件时,overlayFS会先从所有的upper层中查找所需的文件。此时overlayFS会按照这些upper层的顺序来自上而下的查找文件。如果查找到目标文件,则直接返回了否则继续向下逐层查找,直到找到文件或者查找完所有upper层。 如果在所有的upper层中都没有查找到文件,则会在所有的lower层中进行查找。同样,此时overlayFS会按照这些lower层的顺序来自上而下的查找文件。如果查找到目标文件,则直接返回了否则继续向下逐层查找,直到找到文件或者查找完所有lower层。

1.1.2 容器镜像分层

容器镜像 = 镜像层 + 容器层 所有的镜像层和容器层共同构成了容器的rootfs(根目录),这里是使用unionFS的多目录挂载机制来实现的。

3116751445d182687ce496f2825117e5.jpg.webp

1.1.2.1 容器层

当容器启动时,一个新的可写层被加载到镜像的顶部,而这一层就是容器层。 所有在容器中内部的CURD操作,都只会发生在容器层中。

Tips: 即容器层对应的是OverlayFS中的upper类型。

容器层细分还可以分为init层和可读写层。Init 层用来存放被临时修改过的 /etc/hosts 等文件,可读写层则是依据写时复制原则来存放容器内部修改的数据。另外,由于用户执行docker commit操作只会提交可读写层,因此像是对容器的一些初始化配置/修改都应该在Init层进行写入/修改;这样的做法可以避免这些因环境因素而改变、 只对当前容器有效的参数被提交和压缩进容器镜像中。

1.1.2.2 镜像层

容器层之下的所有层级为镜像层,镜像层是只读的

Tips: 即镜像层对应的是OverlayFS中的lower目录类型。

Dockerfile中的每条语句执行后都会生成一个对应的镜像层。即使原语本身并没有明显地修改文件的操作(比如ENV),它对应的层也会存在。只不过在外界看来,这个层是空的。

1.1.3 容器文件系统操作

基于上述介绍的容器镜像结构,对于容器文件系统CURD操作的实现细节如下:

  1. 添加文件 在容器中创建文件时,新文件被添加到容器层中

  2. 读取文件 在读取某个文件时,Docker会从优先在容器层中进行查找;如果查找到文件则直接返回文件。如果没有在容器层中查找到文件,则Docker会上往下依次在各个镜像层中查找目标文件。

  3. 修改文件 在修改文件前,需要先查找目标文件的所在层。这里的查找逻辑同上述的读取文件逻辑。 针对文件所处的层,会有对应的修改方式

    1. 文件在容器层中 则直接在容器层中修改文件即可。
    2. 文件在镜像层中 此时Docker会上往下依次在各个镜像层中查找目标文件。一旦找到,会立即将目标文件复制到容器层中,然后进行修改。

      Tips: 实际上就是利用了写时复制的机制。

  4. 删除文件 与修改文件相同,在删除文件之前,Docker需要先查找目标文件的所在层。这里的查找逻辑同上述的读取文件逻辑。 针对文件所处的层,会有对应的修改方式

    1. 文件在容器层中 则直接在容器层中删除文件即可。
    2. 文件在镜像层中 此时Docker会上往下依次在各个镜像层中查找目标文件;一旦找到,Docker会在容器层中标记/记录对目标文件的删除操作。所以此时实现的是标记删除,存储在镜像层中的源文件是不会被删除的。

1.2 数据卷(Data Volume)

Data Volume本质上依旧是host或者host所挂载外部存储的存储空间。 基于上述事实,导致Docker容器无法针对Data Volume进行容量以及权限的调整。即只能在host一侧进行调整。

Data Volume的特点有以下几点:

  1. 必须是目录或者文件 不能是裸磁盘/物理设备
  2. 容器对Data Volume有读写权限
  3. 生命周期与容器独立 容器销毁后Data Volume不会跟随其一同销毁,而是永久保留数据

对于Data Volume,Docker提供了两种使用方式:bind mount和Docker managed volume。

Tips: 两种挂载方式实际上是利用了Linux Bind Mount机制。

1.2.1 Linux Bind Mount机制

Linux Bind Mount的特点有两个:

  1. 允许将一个目录或者文件(而不是整个设备)挂载到一个指定目录(挂载点)上
  2. 在该挂载点上进行的任何操作只会发生在被挂载的目录或者文件上,而`原挂载点的内容则会被隐藏起来且不受影响。

实现原理实际上是一个 inode 替换的过程

95c957b3c2813bb70eb784b8d1daedc6.png.webp

在 Linux 操作系统中,inode用于保存文件的元数据,可以将其理解为存放文件内容的“对象”。而访问这个inode 所使用的“指针”叫目录项,即dentry。 系统命令 # mount --bind /home /test,会将 /home 挂载到 /test 上。其实相当于将 /test 的 dentry的指向目标改为指向/home 的 inode。这样当修改 /test 目录时,实际修改的是 /home 目录的 inode。一旦执行 umount 命令,/test 目录原先的内容就会恢复;因为修改真正发生在的是在 /home 目录里。

1.2.2 bind mount

这种方式是将host已经存在的目录mount到容器内的指定目录上,因此需要先在host上建立相应的目录或文件后才能进行mount操作并存储数据。由于其实现原理为Linux Bind Mount机制,所以使用bind mount所挂载的容器内目录会被host目录中的数据所覆盖(而非合并)。

在使用细节上,一个host上的目录可以被多个容器进行bind mount。同时,bind mount还支持挂载指定的单一文件;这种操作一般适用于”只需要往容器中添加文件,而不希望覆盖整个目录”的场景。另外bind mount支持更改Data Volume的读写权限,默认是读写权限,可更改为只读权限。

这种类型的Data Volume可以实现host与容器之间的数据共享以及容器间的数据共享。同时基于这些特性,bind mount可看作为是一种静态挂载的Data Volume的操作

而bind mount的缺点在于可移植性较差。因为其这种静态绑定的特性,需要在每一台host上都要按照相应的规则建立对应的目录或文件才可支持容器使用Data Volume,这会大大增加维护集群的成本。

1.2.3 Docker managed volume

Docker managed volume与bind mount的唯一区别就是只需要指定容器内的挂载点即可,即Docker来负责维护挂载源。Docker会为容器在host上建立和分配相应的目录来作为挂载源,一般会建立在/var/lib/Docker/volumes/目录下。

实现细节上,如果指定容器内挂载点(目录)中已经存在数据(即容器镜像内自带的数据),此时Docker会自动将这部分数据复制到Data Volume中。其次,Docker managed volume没有实现对于目录权限的管控,因此Data Volume默认为“全放开”的读写权限。

基于上述特性,Docker managed volume可以看作是一种动态挂载Data Volume的操作

而Docker managed volume方式的可移植性同样也是较差的。因为在迁移前,需要先查清Docker managed volume动态建立的哪些目录并需要手动备份这些数据。之后还需在新的host上或者是同host上中找到容器对应的新的Data Volume,并将备份的数据拷贝到新的Data Volume中。


2.数据共享

在Docker中数据共享可分为两个方面:

  1. 容器与host的数据共享
  2. 容器之间的数据共享

2.1 容器与host的数据共享

bind mount:

直接在host上针对Data Volume所对应的源数据目录进行相应的读写操作即可。

Docker managed volume:

需要使用到Docker cp工具或者是自行查找对应的数据源目录进行cp操作。

2.2 容器之间的数据共享

bind mount:

使多个容器都mount同一个host目录即可。

其次,还可使用volume container来进行数据共享。 volume container实际上就是将一个或多个Data Volume提前挂载在一个容器上,并允许其他的容器与该容器共享这些volume。基于其这种特性,使得volume container能够统一管理Data Volume(因为volume都mount在它上),并且其他容器只需要与volume container进行关联,而非与相关的volume进行关联;即实现了容器与host的解耦。 另外,可使用data-packed volume container来进行数据共享。这种volume container实际上就是提前将数据都压缩到了容器镜像中。


3.数据卷的生命周期管理

3.1 数据备份与恢复

对于Data Volume的数据备份实际上就是针对相应的host文件系统中的指定目录进行备份。而恢复数据则就是将数据拷贝到Data Volume所对应的host文件系统中的目录内即可。

3.2 迁移

即使新的容器在挂载Data Volume时,还保证其能够对应原有的host文件系统中的目录即可。

3.3 删除

bind mount:

Docker在删除容器时不会删除相应的host文件目录,因此需要从host层面介入删除数据。

Docker managed volume:

可以在删除容器时加入-v的参数,这样Docker就会自动删除相应的host文件目录了。