Docker 中的数据管理(译)

原文: Manage data in Docker

在 docker 容器中创建的所有文件,默认是存储在容器的 可写层 (writable layer) 中的。这就意味着:

  • 当容器不复存在的时候,数据没有持久化。并且,如果其他进程需要使用这些数据,从外部获取这些数据是非常困难的。
  • 一个容器的可写层是和容器所在的宿主机紧密耦合在一起的,你很难将这些数据移动到其他位置。
  • 向容器的可写层写入数据需要使用存储驱动 (storage driver) 来管理文件系统。这个存储驱动基于 Linux 内核,提供了一个复合的文件系统。这些额外的抽象,相比于使用 数据卷 (data volume) 直接向宿主机文件系统写入, 性能有所下降。

当需要将容器中的文件存储到宿主机,以便在容器停止后将文件持久化时,docker 有两个选项可供使用:volumebind mount.

Docker 也支持容器将文件存储到宿主机的内存中。而这些文件不会被持久化。如果你是在 Linux 上运行 Docker,可以使用 tmpfs mount 将文件存储在宿主机系统内存中。如果你是在 Windows 上运行 Docker,可以使用 named pipe 将文件存储在宿主机的系统内存里。

继续阅读来了解更多持久化数据和利用内存文件的内容。


选择正确的挂载类型

无论你选择使用哪种挂载类型,数据从容器内看起来都是一样的。这些数据作为容器文件系统中的一个目录或者一个独立文件被暴露出来。

一个展示出 volume、bind mount 和 tmpfs mount 区别的简单办法,就是思考一下数据是放置在 Docker 宿主机上的什么位置。


  • Volume 储存在宿主机文件系统由 Docker 进行管理的部分中 (Linux 系统的目录为 /var/lib/docker/volumes/)。非 Docker 进程不应该修改文件系统的这部分。这是在 Docker 中持久化数据最好的方式。
     
  • Bind mount 有可能存储在宿主机系统的任何地方。它甚至有可能是重要的系统文件或者目录。宿主机上的非 Docker 进程或者 Docker 容器均可以在任意时刻对它进行修改。
     
  • tmpfs mount 只会存储在宿主机的系统内存中,并且永远不会写入到宿主机的文件系统中。


关于挂载类型的更多细节

  • Volume:由 Docker 创建和管理。你可以使用 docker volume create 命令显式地创建一个 volume,或者 Docker 可以在容器或服务创建的时候创建一个 volume。
     
    在创建一个 volume 的时候,它被存储在 Docker 宿主机的一个目录里。当你把这个 volume 挂载到容器时,实际被挂载到容器内的就是这个目录。这和 bind mount 的工作方式相似,除了 volume 是由 Docker 管理的,并且与宿主机的核心功能相隔离。
     
    一个给定的 volume 可以同时被挂载到多个容器内。如果没有正在运行的容器使用 volume 时,这个 volume 对 Docker 来说仍然是可用的,而且不会被自动移除。你可以使用 docker volume prune 移除不再使用的 volume。
     
    当你绑定一个 volume 时,它有可能是命名的 (named) 或者匿名的 (anonymous)。 匿名的 volume 在第一次被挂载到容器内时,没有被赋予一个显式的名称,所以 Docker 会给它指定一个在宿主机上保证唯一的随机名称。除了名称以外,命名 volume 和匿名 volume 的表现完全相同。
     
    volume 还支持使用 卷驱动器 (volume drivers),卷驱动器允许你将数据存储在远程宿主机或云供应商,以及更多其他的可能。
     

  • Bind mount:自早期的 Docker 就可以使用。Bind mount 相比 volume 功能有限。当使用 bind mount 的时候,一个宿主机上的文件或者目录被挂载到容器中。这个文件或者目录通过它在宿主机的完整路径被引用。这个文件或者目录不需要预先存在于 Docker 宿主机上。当它不存在的时候,会按需被创建。Bind mount 性能非常好,但是它依赖于宿主机的文件系统拥有可用的特定目录结构。如果你正在开发新的 Docker 应用,应该考虑使用命名卷 (named volume)。bind mount 不可以直接使用 Docker CLI 命令进行管理。

⚠️ Bind mount 允许访问敏感文件
 
使用 bind mount 的一个副作用,且不说是好是坏,它允许你通过容器内的进程改变宿主机文件系统,包括创建、修改,或者删除重要的系统文件或目录。这是一项可能存在安全问题的强大能力,包括会对宿主机上非 Docker 进程产生影响。

 

  • tmpfs mounttmpfs mount 不会持久化到磁盘,不论是在 Docker 宿主机上还是容器里。容器可以在生命周期内,使用这种方式存储非持久化的状态或敏感信息。举个例子,在 Docker 内部,swarm 服务使用 tmpfs mount 将私密数据 (secrets) 挂载到服务的容器内部。
     
  • named pipenpipe mount 可以用于 Docker 宿主机和容器之间的交流。常见的使用场景是,在容器中运行一个第三方工具,并且使用 named pipe 将它连接到 Docker Engine API。

Bind mount 和 volume 都可以使用 -v--volume 被挂载到容器内,但是两者的语法有一些细微的差别。对于 tmpfs mount,你可以使用 --tmpfs。我们推荐对容器和服务使用 --mount,来进行 bind mount、volume 或者 tmpfs mount 挂载,因为它的语法更加清晰。


Volume 的适用场景

在 Docker 容器中或服务中进行数据持久化时,Volume 是比较推荐的方式。关于 volume 的一些使用示例如下:

  • 在多个运行的容器之间共享数据。如果你没有显式地创建 volume,它则会在第一次挂载进容器的时候被创建。当这个容器停止运行或被移除的时候,这个 volume 仍然存在。多个容器可以同时挂载同一个 volume,不论是读写的还是只读。Volumes 只有你显式移除它的时候才会被删除。
     
  • Docker 宿主机不能保证存在给定的目录或者文件结构。Volume 会帮你把 Docker 宿主机的设置与容器 runtime 进行解耦。
     
  • 你希望将容器的数据储存到远程宿主机或者云提供商,而不是本地。
     
  • 你需要从一个 Docker 宿主机备份、恢复或者迁移数据到另一台机器时,volume 是更好的选择。你可以关停使用 volume 的容器,然后备份 volume 的目录 (比如 /var/lib/docker/volumes/<volume-name>)。
     
  • 当使用 Docker Desktop 运行的应用需要高性能的 I/O 时。 Volume 是存储在 Linux 虚拟机中,而不是宿主机,这意味着读和写都有着更低的延迟和更高的吞吐量。
     
  • 当使用 Docker Desktop 运行的应用需要完全的原生文件系统。举个例子,数据库的引擎需要精确控制磁盘 flush,以此来保证事务持久性。Volume 被存储在 Linux 虚拟机中,因此可以做出如此保证。然而 macOS 和 Windows 的文件系统行为稍微有些不同,因此 bind mount 对这两个系统并不友好。[1]


bind mount 的适用场景

一般来说,你应该尽可能使用 volume。Bind mount 适用于如下几种类型的场景:

  • 向容器共享宿主机上的配置文件。这也是 Docker 为容器提供 DNS 解析的默认方式。Docker 会挂载宿主机的 /etc/resolv.conf 文件到每个容器中,来向容器提供 DNS 解析。
     
  • 在开发环境的 Docker 宿主机和容器之间共享源码或构建构件 (build artifacts)。比如,你可能会将 Maven 的 target/ 目录挂载到容器内,并且每次在 Docker 宿主机上构建 Maven 项目时,容器都能获得重建构件的访问权。
     
    如果使用这种方式将 Docker 用于开发,你需要在用于生产的 Dockerfile 中,将可用于生产的构件 (artifact) 直接拷贝到容器内,而不是依赖于 bind mount。
     
  • Docker 宿主机上,容器 bind mount 所需的文件或者目录结构可以保证始终不变。


tmpfs mount 的适用场景

tmpfs mount 最适合用于既不希望将数据持久化到宿主机,也不希望持久化到容器内的场景。或许是出于安全原因或保护容器的性能,你的应用需要将非持久化的状态数据写入一个较大的 volume 时,适合使用 tmpfs mount。


使用 bind mount 和 volume 的小提示

不论你使用 bind mount 或是 volume,请记住以下几点:

  • 如果你将一个 空卷 (empty volume) 挂载到容器的一个目录中,而这个目录中已经存在文件或者目录了,那么这些已存在的文件或目录会被复制到 volume 中。与之相似的,如果你启动一个容器,并且指定了一个尚不存在的 volume,那么就会新创建出一个空的 volume。要为其他容器预填充所需的数据,这是一种不错的办法。
     
  • 如果你挂载一个 bind mount 或 非空 volume 到容器中的一个目录,这个目录中已经存在了文件或者子目录,这些文件或者目录会被这次挂载遮蔽掉。就像你把文件保存在 Linux 的 /mnt 目录,然后又挂载了一个 U 盘到 /mnt。那么 /mnt 中的内容就会被 U 盘遮蔽掉,直到 U 盘被移除。被遮蔽的文件不会被删除或者修改,但是在 bind mount 或者 volume 被挂载期间会无法访问。


下一步

  • 学习更多关于 volumes 的内容。
  • 学习更多关于 bind mounts 的内容。
  • 学习更多关于 tmpfs mounts 的内容。
  • 学习更多关于 storage drivers 的内容,虽然 storage drivers 和 bind mount 以及 volume 并不相关,但是它允许你向容器的可写层 (writable layer) 存储数据。


*附注

[1] 不确定的中文表达,原文如下:

Volumes are stored in the Linux VM and can make these guarantees, whereas bind mounts are remoted to macOS or Windows, where the file systems behave slightly differently.