Docker 笔记(六)--存储(Volumes、Bind mounts、tmpfs mounts)

1. 背景

记录了Docker 的Volume相关知识。测试docker版本 24.0.7

2. 参考

3. 概述

3.1 在Docker中管理数据

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

  • 当容器不存在时,数据不会持久存在,如果另一个进程需要,则很难将数据从容器中取出。

  • 容器的可写层与运行容器的主机紧密耦合。你不能轻易地将数据移动到其他地方。

  • 写入容器的可写层需要一个存储驱动程序( storage driver )来管理文件系统。存储驱动程序使用Linux内核提供了一个联合文件系统。与使用直接写入主机文件系统的数据卷相比,这种额外的抽象降低了性能。

Docker为容器在主机上存储文件提供了两个选项,这样即使在容器停止后文件也会被持久化:卷(volumes)和绑定挂载(bind mounts)。

Docker还支持将文件存储在主机内存中的容器。此类文件不会持久化。如果您在Linux上运行Docker,tmpfs装载用于将文件存储在主机的系统内存中。如果您在Windows上运行Docker,则命名管道用于将文件存储在主机的系统内存中。

3.2 选择正确的挂载类型

无论您选择使用哪种类型的挂载,容器中的数据看起来都是一样的。它在容器的文件系统中公开为一个目录或一个单独的文件。

辨别卷(volumes)、绑定挂载(bind mounts)和tmpfs(tmpfs mounts)挂载之间差异的一种简单方法,是看数据存在Docker主机上的位置。

在这里插入图片描述

  • 卷(volumes)存储在主机文件系统的一部分中,该文件系统由Docker管理(在Linux上为/var/lib/Docker/Volumes/)。非Docker进程不应修改文件系统的这一部分。卷是在Docker中保存数据的最佳方式。

  • 绑定挂载(bind mounts)可以存储在主机系统的任何位置。它们甚至可能是重要的系统文件或目录。Docker主机或Docker容器上的非Docker进程可以随时修改它们。

  • tmpfs挂载(tmpfs mounts)仅存储在主机系统的内存中,从不写入主机系统的文件系统。

绑定挂载和卷都可以使用-v或 --volume标志挂载到容器中,但两者的语法略有不同。对于tmpfs挂载,可以使用 --tmpfs标志。我们建议将 --mount标志用在容器和服务上,用于绑定挂载(bind mounts)、卷(volumes)或tmpfs挂载(tmpfs mounts),因为语法更清楚。

卷(volumes)

卷由Docker创建和管理。您可以使用docker volume create命令显式创建卷,也可以在创建容器或服务期间创建卷。

创建卷时,它存储在Docker主机上的一个目录中。当您将卷装入容器时,该目录就是装入容器的目录。这与绑定挂载的工作方式类似,只是卷由Docker管理,并与主机的核心功能隔离。

一个定义的卷可以同时安装到多个容器中。当没有正在运行的容器使用卷时,Docker仍然可以使用该卷,并且不会自动删除该卷。您可以使用docker volume prune删除未使用的卷。

挂载卷时,它可能是命名的,也可能是匿名的。匿名卷被赋予一个随机名称,该名称保证在指定的Docker主机中是唯一的。就像命名卷一样,即使删除了使用匿名卷的容器,匿名卷也会持久存在,除非在创建容器时使用 --rm标志,在这种情况下,匿名卷会被销毁。请参阅删除匿名卷。如果相继创建了多个使用匿名卷的容器,则每个容器都会创建自己的卷。匿名卷不会自动在容器之间复用或共享。要在两个或多个容器之间共享匿名卷,必须使用随机卷ID挂载匿名卷。

卷还支持使用卷驱动程序,允许您将数据存储在远程主机或云提供商上,以及其他可能性。

绑定挂载(Bind mounts)

与卷相比,绑定挂载的功能有限制。使用绑定挂载时,主机上的文件或目录会装载到容器中。文件或目录由其在主机上的完整路径引用。Docker主机上不需要先存在该文件或目录。它是按需创建的,如果它还不存在的话。绑定挂载速度很快,但它们依赖于具有特定目录结构的主机文件系统。如果您正在开发新的Docker应用程序,请考虑使用卷。您不能使用Docker CLI命令直接管理绑定挂载。

重点

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

提示

使用大型存储库或monorepos,或者使用不再随代码库扩展的虚拟文件系统?检出(Check out)同步文件共享。它通过使用同步文件系统缓存来增强绑定挂载性能,从而提供快速灵活的主机到虚拟机(host-to-VM)文件共享。

tmpfs挂载

tmpfs挂载不会持久化在磁盘上,无论是在Docker主机上还是在容器内。它可以由容器在容器的生存期内用于存储非持久状态或敏感信息。例如,在内部,Swarm服务使用tmpfs挂载将机密(secrets)装载到服务的容器中。

命名管道

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

3.3 卷(volumes)适合的场景

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

  • 在多个正在运行的容器之间共享数据。如果没有特意创建卷,则会在第一次将卷装入容器时创建该卷。当该容器停止或被移除时,该卷仍然存在。多个容器可以同时装载同一个卷,既可以是读写的,也可以是只读的。只有当明确删除卷时,才会删除这些卷。

  • 当Docker主机不能保证具有指定的目录或文件结构时(不同主机可以使用不同的)。卷帮助您将Docker主机的配置与容器运行时解耦。

  • 当您希望将容器的数据存储在远程主机或云提供商上,而不是本地时。

  • 当您需要将数据从一个Docker主机备份、恢复或迁移到另一个主机时,卷是更好的选择。您可以停止使用卷的容器,然后备份卷的目录(如/var/lib/docker/volumes/<volume-name>)。

  • 当您的应用程序需要在Docker Desktop上进行高性能I/O时。卷存储在Linux虚拟机中,而不是主机中,这意味着读取和写入的延迟和吞吐量要低得多。

  • 当您的应用程序需要Docker Desktop上的完全本机文件系统行为时。例如,数据库引擎需要对磁盘刷新进行精确控制,以保证事务的持久性。卷存储在Linux虚拟机中,可以提供这些保证,而绑定挂载(bind mounts)是远程到macOS或Windows,在那里文件系统的行为略有不同。

3.4 绑定挂载(bind mounts)适合的场景

通常,应尽可能使用卷。绑定挂载适用于以下类型的用例:

  • 将配置文件从主机共享到容器。这就是Docker默认情况下为容器提供DNS解析的方式,通过将/etc/resolv.conf从主机装载到每个容器中。

Sharing source code or build artifacts between a development environment on the Docker host and a container. For instance, you may mount a Maven target/ directory into a container, and each time you build the Maven project on the Docker host, the container gets access to the rebuilt artifacts.

  • 在Docker主机上的开发环境和容器之间共享源代码或打包文件(build artifacts)。例如,您可以将Maven目标/目录装载到容器中,每次在Docker主机上构建Maven项目时,容器都可以访问重建的打包文件(build artifacts)。

  • 如果您以这种方式使用Docker进行开发,那么您生产Dockerfile将直接把准备用于生产的包打到镜像中,而不是依赖于绑定挂载。

  • 当Docker主机的文件或目录结构保证与容器的绑定挂载(bind mounts)所需的一致时。

3.5 tmpfs挂载(tmpfs mounts)适合的场景

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

3.6 使用绑定装挂载(bind mounts)或卷(volumes)的提示

如果使用绑定挂载(bind mounts)或卷(volumes),请记住以下几点:

如果将空卷装载到容器中已存在目录,那么在目录中的已有文件或目录会传播(复制)到卷中。同样,如果启动一个容器并指定一个不存在的卷,则会为您创建一个空卷。这其实是预填充另一个容器所需数据的好方法。

如果将绑定挂载或非空卷装载到容器中存在某些文件或目录的目录中,则这些文件或目录会被挂载遮挡,这就像您将文件保存到Linux主机上的/mnt中,然后将USB驱动器挂载到/mnt中一样。/mnt的内容将被USB驱动器的内容遮挡,直到USB驱动器被卸载。遮挡的文件不会被删除或更改,但在绑定挂载或卷装载期间无法访问。

4 卷(Volumes)

官方文档-Volumes

卷是保存Docker容器生成和使用的数据的首选机制。与绑定挂载(bind mounts)依赖于主机的目录结构和操作系统不同,卷(volumes)完全由Docker管理。与绑定挂载相比,卷有几个优点:

  • 卷比绑定挂载更易于备份或迁移。

  • 可以使用Docker CLI命令或Docker API来管理卷。

  • 卷可以在Linux和Windows容器上工作。

  • 可以在多个容器之间更安全地共享卷。

  • 卷驱动程序允许您将卷存储在远程主机或云提供商上,加密卷的内容,或添加其他功能。

  • 新卷的内容可以由容器预先填充。

  • Docker Desktop上的卷比Mac和Windows主机上的绑定挂载具有高得多的性能。

此外,卷通常是比在容器的可写层中持久化数据更好的选择,因为卷不会增加使用它的容器的大小,而且卷的内容存在于指定容器的生命周期之外。
在这里插入图片描述
如果容器生成非持久状态数据,请考虑使用tmpfs装载,以避免将数据永久存储在任何位置,并通过避免写入容器的可写层来提高容器的性能。

卷使用rprivate绑定传播,并且绑定传播对于卷是不可配置的。

4.1 选择-v或–mount标志

一般来说, --mount更明确、更详细。最大的区别是-v语法将所有选项组合在一个字段中,而 --mount语法将它们分开。以下是每个标志的语法比较。

如果需要指定卷驱动程序选项,则必须使用 --mount。

  • -v或 --volume:由三个字段组成,用冒号(:)分隔。字段必须按正确的顺序排列,并且每个字段的含义并不明显。

    • 在命名卷的情况下,第一个字段是卷的名称,并且在指定的主机上是唯一的。对于匿名卷,将省略第一个字段。

    • 第二个字段是在容器中装载文件或目录的路径。

    • 第三个字段是可选的,是一个逗号分隔的选项列表,如ro。下面将讨论这些选项。

  • - -mount:由多个键值对组成,用逗号分隔,每个键值对由一个<key>=<value>元组组成。 --mount语法比-v或 --volume更详细,但键的顺序并不重要,而且标志的值更容易理解。

    • 挂载的类型,能是绑定(bind)、卷(volume)或tmpfs。本章节讨论卷,因此类型始终指的是卷。

    • 挂载的来源。对于命名卷,这是卷的名称。对于匿名卷,此字段被省略。可以指定为source或src。

    • 目标把文件或目录挂载在容器中的路径。可以指定为destination、dst或target。

    • 只读选项(如果存在)会导致绑定挂载以只读方式装载到容器中。可以指定为只读或ro(读写)。

    • 卷选择选项可以指定多次,它采用由选项名称及其值组成的键值对。

警告

如果卷驱动程序接受逗号分隔的列表作为选项,则必须从外部CSV解析器转义该值。要转义卷的选项(volume-opt),请用双引号(“)将其括起来,并用单引号(')将整个挂载参数括起来。

例如,本地驱动程序接受挂载选项作为 o 参数中逗号分隔的列表。此示例显示了正确方法。

$ docker service create \
 --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
 --name myservice \
 <IMAGE>

下面的示例尽可能同时显示 --mount和 -v语法,其中–mount优先。

--mount 和 -v 之间的差异

与绑定挂载(bind mounts)不同,卷(volumes)的所有选项都可用于 --mount和-v标志。

与服务一起使用的卷时,仅支持–装载。

4.2 创建和管理卷

与绑定挂载不同,您可以在任何容器的范围之外创建和管理卷。

创建卷:
在这里插入图片描述
列出卷:
在这里插入图片描述
查看卷:
在这里插入图片描述
移除卷:
在这里插入图片描述

4.3 启动带有卷的容器

如果您启动的容器包含一个尚不存在的卷,Docker会为您创建该卷。以下示例将卷myvol2装载到容器中的**/app/**中。

下面的-v和 --mount示例产生了相同的结果。除非在运行第一个容器后删除devtest容器和myvol2卷,否则不能同时运行它们。

--mount -v
在这里插入图片描述


使用docker inspect devtest验证docker是否创建了卷并正确安装。查找Mounts部分:

docker  inspect  --format='{{json .Mounts}}'  devtest

在这里插入图片描述
这表明挂载是一个卷(volume),它显示了正确的源和目标,并且是可读写的。

停止容器并移除卷。注意,删除卷是一个单独的步骤。
在这里插入图片描述

4.4 Docker Compose使用卷

下面的示例显示了一个带有卷的Docker Compose服务:
在这里插入图片描述
第一次运行docker compose up会创建一个卷。当您随后运行该命令时,Docker会重用相同的卷。

您也可以使用docker volume create直接在Compose外部创建一个卷,然后在Compose.yaml内部引用它,如下所示:
在这里插入图片描述有关将卷与Compose一起使用的详细信息,请参阅Compose规范中的“卷”部分。

启动带卷的服务

启动服务并定义卷时,每个服务容器都使用自己的本地卷。如果使用本地卷驱动,则任何容器都无法共享此数据。但是,有些卷驱动可以支持共享存储。

以下示例启动一个nginx服务,该服务有四个副本,每个副本都使用一个名为myvol2的本地卷。
在这里插入图片描述docker service ps devtest-service来验证该服务是否正在运行:
在这里插入图片描述您可以删除该服务以停止正在运行的任务:
在这里插入图片描述删除该服务不会删除该服务创建的任何卷。卷移除是一个单独的步骤。

在服务上使用的语法差异

docker服务创建命令不支持-v或 --volume标志。将卷装入服务容器时,必须使用 --mount标志。

使用容器时填充卷

如果启动一个容器并创建新卷,该容器内要挂载的目录中已存在文件或目录,比如/app/目录,Docker会将这些目录中已存在的内容复制到卷中。然后,容器挂载并使用该卷,使用该卷的其他容器也可以访问这些预先填充的内容。

为了显示这一点,以下示例启动一个nginx容器,并用容器的/usr/share/nginx/html目录的内容填充新卷nginx-vol。这是Nginx存储其默认HTML内容的地方。

--mount和-v示例具有相同的最终结果。
在这里插入图片描述


在运行这些示例中的任何一个之后,运行以下命令来清理容器和卷。注意,删除卷是一个单独的步骤。
在这里插入图片描述

4.5 使用只读卷

对于一些开发应用程序,容器需要能够写入绑定挂载,以便将更改回传给Docker主机。其他时候,容器只需要对数据进行读取访问。多个容器可以挂载同一个卷,其中部分容器以读写方式挂载,而另一些容器以只读方式挂载。

以下示例更改了上面的示例。它将目录作为只读卷挂载,方法是在容器内的挂载点之后,在选项列表中添加ro(默认为空)。如果存在多个选项,可以使用逗号将它们分隔开。

--mount和-v示例具有相同的结果。
在这里插入图片描述


使用docker inspect nginxest来验证docker是否正确创建了只读装载。查看Mounts部分:

docker container inspect -f '{{json .Mounts}}' nginxtest | python -m json.tool

在这里插入图片描述


在运行这些示例中的任何一个之后,运行以下命令来清理容器和卷。注意,删除卷是一个单独的步骤。
在这里插入图片描述

4.6 在多台机器间共享数据

在构建容错应用程序时,您可能需要配置同一服务的多个副本以访问相同的文件。
在这里插入图片描述
在开发应用程序时,有几种方法可以实现这一点。一种是向应用程序添加逻辑,将文件直接存储在类似AmazonS3的云对象存储系统上。另一种方法是将文件存储在卷上,卷负责实现外部存储系统,如NFS或AmazonS3等。

卷驱动程序允许您从应用程序逻辑中抽象底层存储系统。例如,如果您的服务使用带有NFS驱动程序的卷,您可以更新服务以使用不同的驱动程序。例如,在不更改应用程序逻辑的情况下将数据存储在云中。

4.7 使用卷驱动程序

使用docker volume create创建卷,或者启动容器( docker run)创建卷时,可以指定卷驱动程序。以下示例使用vieux/sshfs卷驱动程序,首先是在创建独立卷,然后是在启动容器时创建卷。

初始设置

以下示例假设您有两个节点,第一个节点是Docker主机,可以使用SSH连接到第二个节点。

在Docker主机上,安装vieux/sshfs插件:
在这里插入图片描述

使用卷驱动程序创建卷

此示例指定SSH密码,但如果两个主机配置了共享密钥,则可以排除该密码。每个卷驱动程序可能有零个或多个可配置选项,您可以使用 -o 标志指定每个选项。
在这里插入图片描述

// 创建一个独立(standalone)卷 sshvolume
[root@centos7-10 nginx-sj]# docker volume create --driver vieux/sshfs \
> -o sshcmd=test@10.211.55.22:/home/test \
> -o password=testpassword \
> sshvolume
sshvolume
// 查看卷sshvolume
[root@centos7-10 nginx-sj]# docker volume inspect sshvolume 
[
    {
        "CreatedAt": "0001-01-01T00:00:00Z",
        "Driver": "vieux/sshfs:latest",
        "Labels": null,
        "Mountpoint": "/mnt/volumes/43b08b4a9b1f4257bfb96d01b940519e",
        "Name": "sshvolume",
        "Options": {
            "password": "testpassword",
            "sshcmd": "test@10.211.55.22:/home/test"
        },
        "Scope": "local"
    }
]
// 启动容器直接使用已创建的卷sshvolume
[root@centos7-10 nginx-sj]# docker exec -it sshfs-container /bin/bash
root@5e6b0fa247c5:/# cd app
root@5e6b0fa247c5:/app#  echo 'this is storage test!' >> /app/storage_test
root@5e6b0fa247c5:/app# cat storage_test 
this is storage test!

启动容器同时使用卷驱动程序创建卷

以下示例指定SSH密码。但是,如果两个主机配置了共享密钥,则可以排除密码。每个卷驱动程序可以具有零个或多个可配置选项。

注意

如果卷驱动程序要求您传递任何选项,则必须使用 --mount标志来挂载卷,而不是-v。
在这里插入图片描述

// 宿主机root@centos7-10上启动容器,同时创建卷ssh到10.211.55.22上
[root@centos7-10 nginx-sj]# docker run -d \
> --name sshfs-container \
> --volume-driver vieux/sshfs \
> --mount src=sshvolume,target=/app,volume-opt=sshcmd=test@10.211.55.22:/home/test,volume-opt=password=testpassword \
> nginx:latest
// 使用--volume-driver选项会给出警告,但不影响使用
WARN[0000] `--volume-driver` is ignored for volumes specified via `--mount`. Use `--mount type=volume,volume-driver=...` instead. 
fc23c394b1b4bc986d12f6d16c71042529079fa4e0c797228550779513e80d60
// 更新语法,执行效果与上一条命令一样
[root@centos7-10 ~]# docker run -itd \
> --name sshfs-container \
> --mount type=volume,volume-driver=vieux/sshfs,src=sshvolume,target=/app/,volume-opt=sshcmd=test@10.211.55.22:/home/test,volume-opt=password=testpassword \
> nginx:latest
1c8d833a7ae352723e0ddaa063bb3e9d1d22fe5cfde2c5acccfd5a1bc126513c
[root@centos7-10 ~]# 
// 写入一个文件到卷sshvolume
[root@centos7-10 ~]#  docker exec -it sshfs-container /bin/bash
root@1c8d833a7ae3:/# cd app
root@1c8d833a7ae3:/app# ls
root@1c8d833a7ae3:/app# echo "this is ssh_volume test">> storage_test
root@1c8d833a7ae3:/app# cat storage_test 
this is ssh_volume test
// 宿主机root@centos7-22,IP 10.211.55.22,/home/test/目录下查看卷写入文件
[root@centos7-22]# cat /home/test/storage_test 
this is ssh_volume test

创建一个使用NFS卷的服务

以下示例显示如何在创建服务时创建NFS卷。它使用10.0.0.10作为NFS服务器,使用 /var/docker-nfs 作为NFS服务器上的共享目录。请注意,指定的卷驱动程序是 local

NFSv3

$ docker service create -d \
  --name nfs-service \
  --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,volume-opt=o=addr=10.0.0.10' \
  nginx:latest

NFSv4

$ docker service create -d \
    --name nfs-service \
    --mount 'type=volume,source=nfsvolume,target=/app,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/var/docker-nfs,"volume-opt=o=addr=10.0.0.10,rw,nfsvers=4,async"' \
    nginx:latest

创建CIFS/Samba卷

您可以直接在Docker中挂载Samba共享,而无需在主机上配置装载点。
在这里插入图片描述如果指定主机名而不是IP,则需要addr选项。这让Docker可以按主机名查找。

块存储设备

您可以将块存储设备(如外部驱动器或驱动器分区)装载到容器中。以下示例显示如何创建文件并将其用作块存储设备,以及如何将块设备装载为容器卷。

重点

以下步骤只是一个示例。此处所示的解决方案不建议作为一般做法。除非你对自己正在做的事情有信心,否则不要尝试这种方法。

块设备如何工作
在后台, --mount标志使用本地存储驱动程序调用Linux挂载系统调用,并原封不动地转发您传递给它的选项。Docker在Linux内核支持的本机挂载特性之上没有实现任何附加功能。

如果您熟悉Linux mount命令,您可以按照以下方式将 --mount选项视为转发到mount命令:
在这里插入图片描述进一步解释这一点,请看以下mount命令示例。此命令将/dev/loop5设备挂载到系统上的/external-drive路径上。
在这里插入图片描述
从正在运行的容器的角度来看,下面的docker run命令实现了类似的结果。使用这个 --mount选项运行一个容器,就和上一个示例中执行mount命令方式一样,设置挂载。
在这里插入图片描述不能直接在容器内运行mount命令,因为容器无法访问**/dev/loop5设备。这就是docker run**命令使用 –mount选项的原因。

示例:在容器中挂载块设备

以下步骤创建一个ext4文件系统并将其挂载到一个容器中。所用文件系统的支持情况取决于您所使用的Linux内核版本。

  1. 创建一个文件并为其分配一些空间:
    在这里插入图片描述

  2. 在disk.raw文件上格式化一个文件系统: 在这里插入图片描述

  3. 创建loop设备:
    在这里插入图片描述注意
    losetup创建一个临时的loop设备,该设备在系统重新启动后被删除,或者使用losetup -d手动删除。

  4. 运行一个容器,把loop设备作为卷挂载:
    在这里插入图片描述当容器启动时,路径/external-drive将disk.raw文件作为块设备从主机文件系统中挂载。

When you’re done, and the device is unmounted from the container, detach the loop device to remove the device from the host system:

  1. 当你做完后,请将设备从容器中卸载,拆卸loop设备,将它从主机系统中移除:
    在这里插入图片描述

4.8 备份、恢复或迁移数据卷

卷对于备份、恢复和迁移非常有用。使用 --volumes-from标志创建一个新容器来挂载该卷。

备份卷

例如,创建一个名为dbstore的新容器:
在这里插入图片描述dbdata是目标目录,docker在主机/var/lib/docker/volumes/下自动生成源目录

接下来的命令执行:

  • 启动一个新容器并从dbstore容器装载卷
  • 将本地主机目录挂载为/backup
  • 传递一个命令,将dbdata卷的内容打包到/backup目录中的backup.tar文件。
    在这里插入图片描述
    • $(pwd) : 当前所在目录
    • /backup: 目标目录

当命令完成并且容器停止时,它将创建dbdata卷的备份。(容器dbstore的/dbdata --> 临时容器/backup/backup.tar --> 主机当前所在目录/backup.tar)

从备份还原卷

使用刚刚创建的备份,您可以将其恢复到同一个容器,或者恢复到您在其他地方创建的另一个容器。

例如,创建一个名为dbstore2的新容器:
在这里插入图片描述然后,解压备份文件到新容器数据卷中:
在这里插入图片描述您可以使用上面的技术,使用您喜欢的工具自动执行备份、迁移和恢复测试。(主机当前所在目录/backup.tar --> 临时容器/backup/backup.tar --> 容器dbstore2的/dbdata )

删除卷

删除容器后,Docker数据卷会持续存在。有两种类型的卷需要考虑:

  • 命名卷具有来自容器外部的特定来源,例如,awesome:/bar

  • 匿名卷没有特定的来源。因此,当容器被删除时,您可以指示Docker引擎守护进程(Docker Engine daemon)删除它们。

删除匿名卷

要自动删除匿名卷,请使用 --rm选项。例如,此命令创建一个匿名/foo卷。当您移除容器时,Docker引擎会移除/foo卷,但不会移除令人awesome卷。
在这里插入图片描述

注意

如果另一个容器使用 --volumes-from绑定卷,则卷定义会被复制,并且在删除第一个容器后,匿名卷还会保留。

验证操作见下:

// 第一步,先创建一个容器dbstore2 ,挂载了匿名卷
[root@centos7-10 ~]# docker run -itd --rm -v /dbdata --name dbstore2 ubuntu22:2024022601 /bin/bash
43a880666da4ef47b07dda170f195547a62bbf945d096503c10ef759b55de762 
// 第二步,再创建一个容器(id:65df23f9a79a),引用容器dbstore2的匿名卷
[root@centos7-10 ~]# docker run -itd --volumes-from dbstore2 ubuntu22:2024022601 /bin/bash
65df23f9a79a3f3d0bb81a56810db77448096587f75be18e5bb75789bb06255f
[root@centos7-10 ~]# 
[root@centos7-10 ~]# docker ps -a
CONTAINER ID   IMAGE                 COMMAND                   CREATED              STATUS              PORTS                                       NAMES
65df23f9a79a   ubuntu22:2024022601   "/bin/bash"               6 seconds ago        Up 5 seconds                                                    fervent_montalcini
43a880666da4   ubuntu22:2024022601   "/bin/bash"               About a minute ago   Up About a minute                                               dbstore2
// 查看这个容器也挂在了/dbdata目录
[root@centos7-10 ~]# 
[root@centos7-10 ~]# docker exec -it 65df23f9a79a /bin/bash
root@65df23f9a79a:/# ls 
bin  boot  dbdata  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
// 第三步,停止并删除掉容器dbstore2,启动时使用了--rm参数,停止自动删除容器
[root@centos7-10 ~]# docker stop dbstore2
43a880666da4
// 第四步,此时查看,后创建的容器(id:65df23f9a79a),仍然挂载匿名卷
[root@centos7-10 ~]# docker exec -it 65df23f9a79a /bin/bash
root@65df23f9a79a:/# ls 
bin  boot  dbdata  dev  etc  home  lib  lib32  lib64  libx32  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@65df23f9a79a:/# cd /dbdata/

删除所有卷

删除所有不使用的卷,并释放空间:
在这里插入图片描述

5 绑定挂载(Bind mounts)

官方文档-Bind mounts

绑定挂载从Docker的早期就已经存在了。与卷相比,绑定装载的功能有限。使用绑定装载时,主机上的文件或目录会装载到容器中。文件或目录映射到容器所在主机上的绝对路径。相比之下,当您使用卷时,会在主机上Docker的存储目录中创建一个新目录,Docker会管理该目录的内容。

Docker主机上不需要先存在该文件或目录。如果它还不存在,它会按需创建。绑定挂载的性能非常高,但它们依赖于具有特定目录结构的主机文件系统。如果您正在开发新的Docker应用程序,请考虑使用命名卷(volumes)。您不能使用Docker CLI命令直接管理绑定挂载(bind mounts)。
在这里插入图片描述

提示

使用大型存储库或monorepos,或者使用不再随代码库扩展的虚拟文件系统?检出(Check out)同步文件共享。它通过使用同步文件系统缓存来增强绑定挂载性能,从而提供快速灵活的主机到虚拟机(host-to-VM)文件共享。

5.1 选择-v或–mount标志

一般来说, --mount更明确、更详细。最大的区别是-v语法将所有选项组合在一个字段中,而–mount语法将它们分开。以下是每个标志的语法比较。

提示

新用户应该使用 --mount语法。有经验的用户可能更熟悉-v或 --volume语法,但我们鼓励使用 --mount,因为研究表明它更容易使用。

  • -v或 --volume:由三个字段组成,用冒号(:)分隔。字段必须按正确的顺序排列,并且每个字段的含义并不明显。

    • 在绑定挂载的情况下,第一个字段是主机上文件或目录的路径。

    • 第二个字段是在容器中装载文件或目录的路径。

    • 第三个字段是可选的,是以逗号分隔的选项列表,如rozZ。下面将讨论这些选项。

  • - -mount:由多个键值对组成,用逗号分隔,每个键值对由一个<key>=<value>元组组成。 --mount语法比-v或 --volume更详细,键的顺序也不重要,而且标志的值更容易理解。

    • 装载的类型,可以是绑定(bind mounts)、卷(volumes)或tmpfs。本章节讨论绑定挂载,因此类型始终为绑定。

    • 挂载的源(source)。对于绑定挂载(bind mounts),这是Docker daemon主机上的文件或目录的路径。可以指定为source或src。

    • 目标把文件或目录挂载在容器中的路径。可以指定为destination、dst或target。

    • 只读选项(如果存在)会导致绑定挂载以只读方式装载到容器中。

    • 绑定扩展选项(如果存在)将更改绑定方式。可以是rprivate、private、rshared、shared、rslave、slave中的一种。

    • --mount标志不支持用于修改selinux标签的z或z选项。

下面的示例尽可能显示 --mount和-v语法,并首先显示 --mount。

-v和 --mount 之间的差异

因为-v和 --volume标志已经是Docker的一部分很长时间了,所以它们的形式无法更改。这意味着 -v和 --mount之间有差异。

如果使用-v或–volume绑定挂载Docker主机上尚不存在的文件或目录,-v将为您创建端点。它总是被创建为一个目录。

如果您使用 --mount绑定挂载Docker主机上还不存在的文件或目录,Docker不会自动为您创建它,而是会生成一个错误。

5.2 使用绑定装载启动容器

考虑这样一种情况:您有一个目录源代码,当您构建源代码时,编译完的包会保存到另一个目录source/target/中。您希望包可用于/app/中的容器,并且希望每次在开发主机上构建源代码时,容器都能访问新的构建产出包。使用以下命令将目标/目录绑定挂载到/app/的容器中。从源目录中运行命令。$(pwd)子命令扩展到Linux或macOS主机上的当前工作目录。如果您使用的是Windows,请参阅Windows上的路径转换

下面的 --mount和-v示例产生了相同的结果。除非在运行第一个容器后删除devtest容器,否则不能同时运行它们。

在这里插入图片描述

// 使用 --mount 方式运行容器devtest并创建bind mounts
[root@centos7-10 ~]# docker run -d \
>   -it \
>   --name devtest \
>   --mount type=bind,source="$(pwd)"/nginx-sj,target=/app \
>   nginx:latest
// 使用 -v 方式运行容器devtest2并创建bind mounts。注意容器名称不一样,devtest2
[root@centos7-10 ~]# docker run -d \
> -it \
> --name devtest2 \
> -v "$(pwd)"/nginx-sj:/app \
> nginx:latest
1346618e5ce51d57a6ba9007b617d6fc9cbec3be585d86dd5b70b164d2e3e532

使用docker inspect devtest来验证绑定装载是否已正确创建。查找Mounts部分:
在这里插入图片描述

[root@centos7-10 ~]# docker inspect -f "{{json .Mounts}}" devtest | python -m json.tool
[
    {
        "Destination": "/app",
        "Mode": "",
        "Propagation": "rprivate",
        "RW": true,
        "Source": "/root/nginx-sj",
        "Type": "bind"
    }
]

这表明装载是绑定装载,它显示了正确的源和目标,它表明装载是读写的,并且传播(Propagation)设置为rprivate

停止容器:
在这里插入图片描述

挂载到容器上的非空目录中

如果将目录绑定挂载到容器上的非空目录中,则绑定挂载会遮挡目录的已存在内容。这可能是有益的,例如当您想在不构建新映像的情况下测试应用程序的新版本时。然而。不过这可能令人惊讶,并且这种行为与docker卷(volume)的行为不同(卷会自动拷贝目标已存在内容到创建的卷中),详见上述“4.4中使用容器时填充卷”。

此示例被设计为极端,但将容器的/usr/目录的内容替换为主机上的/tmp/目录。在大多数情况下,这将导致容器无法正常工作。

--mount和-v示例具有相同的最终结果。
在这里插入图片描述


容器已创建,但未启动。删除它:
在这里插入图片描述

5.3 使用只读绑定装载

对于一些开发应用程序,容器需要写入绑定挂载( bind mount),因此更改会传递回Docker主机。其他时候,容器只需要读取访问权限。

此示例修改了上面的例子,通过在在容器内的挂载点之后添加ro选项(默认为空),将目录装载为只读绑定装载。如果存在多个选项,请用逗号将它们隔开。

--mount和-v示例具有相同的结果。

在这里插入图片描述

[root@centos7-10 ~]# docker run -d \
> -it \
> --name devtest \
> -v "$(pwd)"/nginx-sj:/app:ro \
> nginx:latest
9cf29ec75e928e523d7c86c732be156dbba3ddf87e0529e098d0fe403fd11dde

使用docker inspect devtest来验证绑定装载是否已正确创建。查找Mounts部分:

在这里插入图片描述

[root@centos7-10 ~]# docker inspect --format="{{json .Mounts}}" devtest | python -m json.tool 
[
    {
        "Destination": "/app",
        "Mode": "ro",
        "Propagation": "rprivate",
        "RW": false,
        "Source": "/root/nginx-sj",
        "Type": "bind"
    }
]

停止容器:
在这里插入图片描述

5.4 递归装载

当绑定挂载(bind mount)一个路径,路径已包含挂载时,默认情况下,这些子挂载也包含在绑定挂载中。这个行为是可配置的,使用 --mount的bind-recursive选项。此选项仅支持 --mount标志,不支持-v或 --volume。

如果绑定挂载是只读的,Docker引擎会尽最大努力使子挂载也是只读的。这被称为递归只读挂载。递归只读挂载需要Linux内核5.12或更高版本。如果您运行的是较旧的内核版本,默认情况下,子挂载会自动以读写方式挂载。在5.12之前的内核版本上,尝试使用bind-recursive=readonly选项将子挂载设置为只读,会导致错误。

绑定递归选项支持的值为:

说明
enabled (default)若内核是v5.12或更高版本,则只读挂载将递归子挂载变为只读。否则,子挂载是读写模式。
disabled子挂载将被忽略(不包括在绑定挂载中)。
writable子挂载将是读写模式
readonly子挂载将是只读模式. 要求内核是v5.12或更高版本.

注意bind-recursive=readonly 选项要求在docker 25的版本且Linux内核版本>=5.12,详见 Docker Engine 25版本发行说明
在这里插入图片描述

// 测试机内核版本5.15
root@ubuntu22-25:~# uname -a
Linux ubuntu22-25 5.15.0-94-generic #104-Ubuntu SMP Tue Jan 9 15:25:40 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
// 创建主机系统子挂载目录
root@ubuntu22-25:~# mkdir /home/submounts/
// 创建主机系统父挂载目录
root@ubuntu22-25:~# mkdir /root/parentmounts/mountdir
// 写入测试文件testfile
root@ubuntu22-25:~# echo "this is test of submounts" >> /home/submounts/testfile
// 将子目录submounts挂载到父目录下的mountdir
root@ubuntu22-25:~# mount --bind /home/submounts/ /root/parentmounts/mountdir
root@ubuntu22-25:~# ll /root/parentmounts/mountdir/
total 12
drwxr-xr-x 2 root root 4096 Mar 24 20:38 ./
drwxr-xr-x 3 root root 4096 Mar 24 20:47 ../
-rw-r--r-- 1 root root   26 Mar 24 20:38 testfile

当前使用Docker Engine version:24.0.7,无法测试bind-recursive=readonly选项。

root@ubuntu22-25:~# docker run -d -it --name recursive_mount --mount type=bind,source=/root/parentmounts,target=/test_mount,readonly ubuntu22:latest /bin/bash
47c141f5e6cfc413cb39cdb55c0c544183c8eb8fd87f46f36c24882d3626ce02
// 容器挂载目录是只读的("RW": false)
root@ubuntu22-25:~# docker inspect -f "{{json .Mounts}}" recursive_mount | python3 -m json.tool
[
    {
        "Type": "bind",
        "Source": "/root/parentmounts",
        "Destination": "/test_mount",
        "Mode": "",
        "RW": false,
        "Propagation": "rprivate"
    }
]

验证容器内父级挂载只读,不能写入信息

// 容器外验证挂载目录是只读的
root@ubuntu22-25:~# docker exec -it recursive_mount /bin/bash -c "cd /test_mount/ && echo 'readonly?'>>test"
/bin/bash: line 1: test: Read-only file system
// 或者登录容器内验证挂载目录是只读的,和上一条命令效果一样
root@ubuntu22-25:~# docker exec -it recursive_mount /bin/bash
root@47c141f5e6cf:/# cd test_mount/
root@47c141f5e6cf:/test_mount# ls
mountdir
root@47c141f5e6cf:/test_mount# echo "readonly" >> readonly
bash: readonly: Read-only file system

验证容器内子级挂载可读写。只有升级到docker engine 25以上版本才能控制

  • 后续有机会再补测。
root@47c141f5e6cf:/# cd /test_mount/mountdir/
root@47c141f5e6cf:/test_mount/mountdir# ll
total 12
drwxr-xr-x 2 root root 4096 Mar 24 20:38 ./
drwxr-xr-x 3 root root 4096 Mar 24 20:47 ../
-rw-r--r-- 1 root root   35 Mar 24 22:07 testfile
root@47c141f5e6cf:/test_mount/mountdir# echo "submount is read-write" >> testfile 
root@47c141f5e6cf:/test_mount/mountdir# cat testfile 
this is test of submounts
readonly
submount is read-write
root@47c141f5e6cf:/test_mount/mountdir# 

5.5 配置绑定传播

Bind propagation defaults to rprivate for both bind mounts and volumes. It is only configurable for bind mounts, and only on Linux host machines. Bind propagation is an advanced topic and many users never need to configure it.

绑定挂载和卷的绑定传播默认为rprivate。它只能针对绑定挂载进行配置,并且只能在Linux主机上进行配置。绑定传播是一个高级概念,许多用户从不需要配置它。

Bind propagation refers to whether or not mounts created within a given bind-mount can be propagated to replicas of that mount. Consider a mount point /mnt, which is also mounted on /tmp. The propagation settings control whether a mount on /tmp/a would also be available on /mnt/a. Each propagation setting has a recursive counterpoint. In the case of recursion, consider that /tmp/a is also mounted as /foo. The propagation settings control whether /mnt/a and/or /tmp/a would exist.

绑定传播是指在指定绑定挂载中创建的装载(内容)是否可以传播副本给其它挂载。考虑一个挂载点**/mnt**,它也挂载在**/tmp上**。传播设置控制**/tmp/a上的装载是否也可以在/mnt/a上使用。每个传播设置都有一个递归对照(counterpoint)。在递归的情况下,请考虑/tmp/a也被挂载为/foo**。传播设置控制**/mnt/a//tmp/a**是否存在。

警告

挂载传播不适用于Docker Desktop。

传播设置说明
shared原始挂载的子挂载暴露给复制副本挂载,复制副本挂载的子挂装也传播到原始挂载。
slave类似于shared,但仅在一个方向上。如果原始挂载公开了一个子挂载,则复制副本挂载可以看到它。但是,如果复制副本挂载公开了一个子挂载,则原始挂载无法看到它。
private该挂载是私有的。其中的子挂载不会暴露于复制副本挂载,复制副本挂载的子挂载也不会暴露于原始挂载。
rshared与shared相同,但传播也扩展到嵌套在任何原始或复制副本挂载点内的装载和从挂载。
rslave与slave挂载相同,但传播也延伸到嵌套在任何原始或复制副本挂载点内的挂载和从挂载。
rprivate默认值。与private挂载相同,这意味着原始挂载点或复制副本挂载点内的任何位置都不会向任一方向传播装载点.

在装载点上设置绑定传播之前,主机文件系统需要已经支持绑定传播。

有关绑定传播的更多信息,请参阅共享子树的Linux内核文档

以下示例将目标/目录装载到容器中两次,第二次装载同时设置ro选项和rslave绑定传播选项。

--mount和-v示例具有相同的结果。
在这里插入图片描述


现在,如果您创建/app/foo/,/app2/foo/也存在。

说明:这里我反复测试了几次,也没能弄清楚shared、private、slave、rshared、rprivate、rslave的用法,我设置了mount节点的propagation,可是一直不生效。设置了mount --make-private 挂载点,结果仍然可以在挂载点与它的子挂载点之间同步文件变更。我没想明白这个主要用在什么业务场景,只能先放放,后续有时间再测。

以下是Linux mount命令帮助中对shared、private、slave、rshared、rprivate、rslave的解释。

[root@centos7-10 a]# mount --help

用法:
 mount [-lhV]
 mount -a [选项]
 mount [选项] [--source] <> | [--target] <目录>
 mount [选项] <> <目录>
 mount <操作> <挂载点> [<目标>]

......

操作:
 -B, --bind              挂载其他位置的子树(-o bind)
 -M, --move              将子树移动到其他位置
 -R, --rbind             挂载其他位置的子树及其包含的所有挂载
 --make-shared           将子树标记为 共享
 --make-slave            将子树标记为 从属
 --make-private          将子树标记为 私有
 --make-unbindable       将子树标记为 不可绑定
 --make-rshared          递归地将整个子树标记为 共享
 --make-rslave           递归地将整个子树标记为 从属
 --make-rprivate         递归地将整个子树标记为 私有
 --make-runbindable      递归地将整个子树标记为 不可绑定

5.6 配置selinux标签

如果使用selinux,则可以添加zZ选项来修改挂载到容器中的主机文件或目录的selinux标签。这会影响主机上的文件或目录本身,并可能产生超出Docker范围的后果。

z选项表示绑定挂载内容在多个容器之间共享。

Z选项表示绑定装载内容是私有的和非共享的。

对这些选项要格外小心。使用Z选项绑定挂载系统目录(如/home或/usr)会使主机无法运行,您可能需要手动重新标记主机文件。

重点

将绑定挂载与服务一起使用时,selinux标签(:Z and : z)以及:ro将被忽略。参见moby/moby#32579 详细信息。

本例设置z选项以指定多个容器可以共享绑定挂载的内容:

无法使用 --mount标志修改selinux标签。
在这里插入图片描述

5.7 将绑定挂载与compose一起使用

带有绑定挂载的单个Docker Compose服务如下所示:
在这里插入图片描述
有关将绑定类型的卷与Compose一起使用的详细信息,请参阅Compose reference on volumes。以及Compose reference on volume configuration

6 tmpfs 挂在

卷和绑定挂载允许您在主机和容器之间共享文件,这样即使在容器停止后也可以持久保存数据。

如果你在Linux上运行Docker,你有第三个选择:tmpfs挂载。当您创建一个带有tmpfs装载的容器时,该容器可以在容器的可写层之外创建文件。

与卷和绑定挂载不同,tmpfs装载是临时的,并且仅持久化在主机内存中。当容器停止时,tmpfs装载将被删除,写入其中的文件将不会持久化。
在这里插入图片描述
这对于临时存储不希望持久存在主机或容器可写层中的敏感文件非常有用。

6.1 tmpfs装载的限制

与卷和绑定挂载不同,您不能在容器之间共享tmpfs装载。

只有当您在Linux上运行Docker时,此功能才可用。

在tmpfs上设置权限可能会导致它们在容器重新启动后重置。在某些情况下,设置uid/gid可以作为一种变通方法。

6.2 选择–tmpfs或–mount标志

一般来说,–mount更明确、更详细。最大的区别是–tmpfs标志不支持任何可配置的选项。

  • --tmpfs:在不允许指定任何可配置选项的情况下装载tmpfs挂载,并且只能与独立容器一起使用。

  • –mount:由多个键值对组成,用逗号分隔,每个键值对由一个<key>=<value>元组组成。–mount语法比–tmpfs更详细:

  • 装载的类型,可以是绑定、卷或tmpfs。本章节讨论tmpfs,因此类型始终为tmpfs。

  • 目标将tmpfs装载在容器中的路径作为其值。可以指定为destination、dst或target。

  • tmpfs大小和tmpfs模式选项。请参阅tmpfs选项

下面的示例尽可能显示–mount和–tmpfs语法,并首先显示–mount。

- -tmpfs和 --mount行为之间的差异

--tmpfs标志不允许您指定任何可配置的选项。

--tmpfs标志不能与群服务一起使用。您必须使用--mount。

6.3 在容器中使用tmpfs装载

要在容器中使用tmpfs装载,请使用 --tmpfs标志,或者使用type=tmpfs和destination选项的 --mount标志。tmpfs装载没有来源。以下示例在Nginx容器中的/app处创建tmpfs装载。第一个示例使用 --mount标志,第二个使用 --tmpfs标志。

在这里插入图片描述


通过查看docker inspect输出的Mounts(安装)部分,验证该安装是否为tmpfs安装:
在这里插入图片描述停止并移除容器:
在这里插入图片描述

指定tmpfs选项

tmpfs挂载允许两个配置选项,但这两个选项都不是必需的。如果需要指定这些选项,则必须使用 --mount标志,因为 --tmpfs标志不支持这些选项。

说明
tmpfs-sizetmpfs装载的大小(以字节为单位)。默认情况下不受限制。
tmpfs-modetmpfs的八进制文件模式。例如,700或0770。默认为1777或可全局写入.

以下示例将tmpfs模式设置为1770,因此它在容器中不具有全局可读性。
在这里插入图片描述

7 存储错误疑难解答

本主题讨论使用Docker卷或绑定挂载时可能发生的错误。

7.1 错误:无法删除文件系统

一些基于容器的实用程序(如Google cAdvisor)将Docker系统目录(如/var/lib/Docker/)装入容器中。例如,cadvisor的文档指示您按如下方式运行cadvisor容器:
在这里插入图片描述

当您绑定mount/var/lib/doker/时,这将实际地将所有其他正在运行的容器的所有资源,作为文件系统挂载到容器中的/var/lib/docker/。当您尝试删除这些容器时,删除尝试可能会失败,并出现以下错误:

在这里插入图片描述

如果绑定的容器在/var/lib/docker/中的文件系统句柄上使用了statfs或fstatfs,并且没有关闭它们,就会出现问题。

通常,我们建议不要以这种方式绑定挂载/var/lib/docker。但是,cAdvisor需要这种绑定装载才能实现核心功能。

如果您不确定是哪个进程导致错误中提到的路径繁忙并阻止将其删除,则可以使用lsof命令查找其进程。例如,对于上面的错误:
在这里插入图片描述
要解决此问题,请停止绑定mounts /var/lib/docker的容器,然后再次尝试删除其他容器。

  • 30
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值