默认情况下,容器内创建的所有文件都存储在可进行写入的容器层中。这意味着:

  • 当该容器不再存在时,数据不会持久保存,如果其他进程需要这些数据,则很难从容器中获取。
  • 容器的可写层与运行容器的宿主机紧密耦合。您无法轻松地将数据移动到其他位置。
  • 向容器的可写层写入数据需要存储驱动程序来管理文件系统。存储驱动程序使用 Linux 内核提供联合文件系统。与直接写入宿主机文件系统的数据卷相比,这种额外的抽象会降低性能。

Docker 为容器提供了两种在宿主机上存储文件的方式,以确保即使在容器停止后文件也能持久保存:卷(Volumes)和绑定挂载(Bind Mounts)

此外,Docker 还支持容器在宿主机的内存中存储文件。这样的文件不会持久保存。如果您在 Linux 上运行 Docker,将使用 tmpfs 挂载在宿主机的系统内存中存储文件。如果您在 Windows 上运行 Docker,则使用命名管道(Named Pipe)在宿主机的系统内存中存储文件。

一、选择正确的挂载类型

无论你选择使用哪种挂载类型,从容器内部看,数据都是相同的。它被暴露为容器文件系统中的目录或单个文件。

要直观了解卷(Volumes)、绑定挂载(Bind Mounts)和 tmpfs 挂载之间的差异,请思考数据在 Docker 宿主机上的存储位置。

  • 卷存储在 Docker 管理的主机文件系统的一部分中(在 Linux 上为 /var/lib/docker/volumes/ )。非 Docker 进程不应该修改这部分文件系统。卷是 Docker 中持久化数据的最佳方式。
  • 绑定挂载可以存储在主机系统的任何位置。甚至可以是重要的系统文件或目录。Docker 主机上的非 Docker 进程或 Docker 容器可以随时修改它们。
  • tmpfs 挂载仅存储在主机系统的内存中,并且永远不会写入到主机系统的文件系统中。

绑定挂载和卷都可以使用 -v--volume 标志挂载到容器中,但每个的语法略有不同。对于 tmpfs 挂载,您可以使用 --tmpfs 标志。建议使用 --mount 标志来挂载容器和服务中的绑定挂载、卷或 tmpfs 挂载,因为该语法更加清晰。

1、Volumes

Docker 负责创建和管理卷(Volumes)。可以使用 docker volume create 命令显式地创建一个卷,或者在创建容器或服务时由 Docker 自动创建卷。

当您创建一个卷时,它会被存储在 Docker 宿主机上的一个目录中。当您将这个卷挂载到容器中时,正是这个目录被挂载到容器内部。这与绑定挂载(bind mounts)的工作方式类似,但不同之处在于卷是由 Docker 管理的,并且与宿主机的核心功能相隔离。

一个给定的卷可以同时挂载到多个容器中。当没有任何正在运行的容器使用某个卷时,该卷仍然对 Docker 可用,并且不会自动被删除。可以使用 docker volume prune 命令来删除未使用的卷。

挂载卷时,它可以是有名称的或匿名的。匿名卷会被赋予一个随机名称,该名称在给定的 Docker 宿主机上保证是唯一的。比如:ff6dcca13e7fdac87e71978aa28c37076c8d1e847f1a96ffff88089d604f79c6。与命名卷一样,即使您移除了使用它们的容器,匿名卷也会持续存在,除非在创建容器时使用了 --rm 标志,此时匿名卷会被销毁。

注意:Docker Desktop 程序中所做出的操作与使用 Docker CLI 不同,Desktop 程序中删除容器会顺带删除该容器所创建的匿名卷,而使用 docker rm <CONTAINER ID> 删除容器时不使用 -v, --volumes Remove anonymous volumes associated with the container 选项时是不会删除匿名卷的。

如果您连续创建多个使用匿名卷的容器,每个容器都会创建自己的卷。匿名卷不会自动在容器之间重用或共享。要在两个或更多容器之间共享匿名卷,您必须使用随机卷 ID 来挂载该匿名卷。

卷还支持使用卷驱动程序,这使得您可以将数据存储在远程主机或云提供商上,以及其他可能性。存储在本地的话,驱动程序为 local

1
2
3
$ docker volume ls 
local 0d5083ffccea93113431c556bb65a161adff3726a89cfb5fc8919813065f828c
local 2a926a831dda47291e6d30083408befc033846bd715e8bfab81cfe5a786ee2fa

2、Bind mounts

与卷相比,绑定挂载(bind mounts)的功能有限。当您使用绑定挂载时,宿主机上的一个文件或目录会被挂载到容器中。这个文件或目录是通过其在宿主机上的完整路径来引用的。该文件或目录在 Docker 宿主机上不必预先存在;如果尚不存在,则会在需要时创建。绑定挂载速度很快,但它们依赖于宿主机文件系统具有特定的目录结构。如果您正在开发新的 Docker 应用程序,请考虑使用命名卷代替。您不能使用 Docker CLI 命令直接管理绑定挂载。

绑定挂载默认允许对宿主机上的文件进行写访问。使用绑定挂载的一个副作用是,您可以通过在容器中运行的进程来更改宿主机文件系统,包括创建、修改或删除重要的系统文件或目录。这是一种强大的能力,可能会带来安全影响,包括对宿主机系统上的非 Docker 进程产生影响。

3、tmpfs

tmpfs 挂载在 Docker 宿主机或容器内都不会持久化到磁盘上。它可以在容器的生命周期内被容器使用,以存储非持久化状态或敏感信息。例如,Swarm 服务会使用 tmpfs 挂载将机密信息挂载到服务的容器中。

4、Named pipes

命名管道可以用于 Docker 主机与容器之间的通信。常见的用例是在容器内部运行第三方工具,并通过命名管道连接到 Docker 引擎 API。

二、使用 Volumes 的最佳实践

卷是 Docker 容器和服务中持久化数据的首选方式。卷的一些用例包括:

  • 在多个运行的容器之间共享数据。如果你没有显式创建卷,那么当卷首次挂载到容器中时,它就会被创建。当该容器停止或被删除时,卷仍然存在。多个容器可以同时以读写或只读模式挂载同一个卷。只有当你显式删除卷时,它才会被移除。
  • 当 Docker 主机不保证具有给定的目录或文件结构时。卷有助于你将 Docker 主机的配置与容器运行时解耦。
  • 当你希望将容器的数据存储在远程主机或云提供商上,而不是本地时
  • 当你需要从一个 Docker 主机备份、恢复或迁移数据到另一个 Docker 主机时,卷是更好的选择,你可以停止使用卷的容器,然后备份卷的目录(如 /var/lib/docker/volumes/<volume-name>)。
  • 当你的应用程序在 Docker Desktop 上需要高性能的 I/O 时。卷存储在Linux虚拟机中,而不是主机上,这意味着读写操作的延迟更低,吞吐量更高。
  • 当你的应用程序在 Docker Desktop 上需要完全原生的文件系统行为时。例如,数据库引擎需要精确控制磁盘刷新以保证事务的持久性。卷存储在Linux虚拟机中,可以提供这些保证,而绑定挂载则被远程到 macOS 或 Windows ,那里的文件系统行为略有不同。

三、使用 Bind mounts 的最佳实践

绑定挂载适用于以下类型的用例:

  • 从主机机器向容器共享配置文件。Docker 默认通过将从主机机器挂载的 /etc/resolv.conf 文件到每个容器中来为容器提供DNS解析。
  • 在 Docker 主机上的开发环境与容器之间共享源代码或构建产物。例如,你可以将 Maven 的target/目录挂载到容器中,每次在 Docker 主机上构建 Maven 项目时,容器都可以访问重新构建的产物。
  • 如果你以这种方式使用 Docker 进行开发,那么你的生产 Dockerfile 将直接把准备好的生产产物直接复制到镜像中,而不是依赖于绑定挂载
  • 当 Docker 主机的文件或目录结构与容器所需的绑定挂载保持一致时。这种情况下,使用绑定挂载可以确保容器能够访问到正确的文件和目录结构。

四、使用 tmpfs mounts 的最佳实践

tmpfs 挂载最适合用于那些你不希望数据在主机或容器内持久保存的情况。这可能是出于安全原因,或者是当你的应用程序需要写入大量非持久状态数据时,为了保护容器的性能。

五、使用绑定挂载或卷时注意

如果你使用绑定挂载或卷,请记住以下几点:

  • 如果你将一个空的卷挂载到容器中的一个目录,而该目录中已存在文件或子目录,那么这些文件或子目录将被传播(复制)到卷中。类似地,如果你启动一个容器并指定了一个尚不存在的卷,那么会为你创建一个空的卷。这是为另一个容器预先填充所需数据的好方法。
  • 如果你将一个绑定挂载或非空卷挂载到容器中的一个目录,而该目录中已存在某些文件或子目录,那么这些文件或子目录将被挂载点遮挡,就像你在 Linux 主机上的 /mnt 目录中保存了文件,然后又将 USB 驱动器挂载到 /mnt 目录一样。/mnt 目录的内容将被 USB 驱动器的内容遮挡,直到 USB 驱动器被卸载。被遮挡的文件不会被删除或修改,但在绑定挂载或卷被挂载时无法访问。

相关链接

OB tags

#Docker