From 9a68b6e6f16e9998d72cae4262ee9d68190a38a5 Mon Sep 17 00:00:00 2001 From: titor-z Date: Fri, 20 Dec 2024 00:07:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=8A=9F=E8=83=BD=E5=9F=BA?= =?UTF-8?q?=E6=9C=AC=E9=A1=B5=E9=9D=A2=E5=AE=8C=E6=88=90=EF=BC=8C=E5=8F=AA?= =?UTF-8?q?=E5=B7=AE=E5=AD=98=E5=82=A8=E9=A9=B1=E5=8A=A8=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=A4=A7=E7=B1=BB=E6=95=B4=E4=BD=93=E6=B2=A1=E6=9C=89=E5=81=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/storage/container-image-store.md | 39 ++ docs/storage/mount.md | 340 +++++++++++++ docs/storage/tmpfs-bind.md | 101 ++++ docs/storage/volume.md | 601 ++++++++++++++++++++++- docs/storage/volumes-shared-storage.webp | Bin 0 -> 22132 bytes 5 files changed, 1080 insertions(+), 1 deletion(-) create mode 100644 docs/storage/container-image-store.md create mode 100644 docs/storage/mount.md create mode 100644 docs/storage/tmpfs-bind.md create mode 100644 docs/storage/volumes-shared-storage.webp diff --git a/docs/storage/container-image-store.md b/docs/storage/container-image-store.md new file mode 100644 index 0000000..aafd883 --- /dev/null +++ b/docs/storage/container-image-store.md @@ -0,0 +1,39 @@ +# 使用 Docker 引擎的 containerd 映像存储 + +:::warning 注意 +containerd 镜像存储是 Docker Engine 的一项实验性功能。 +如果您使用的是 Docker Desktop,请参阅 [containerd 映像存储与 Docker Desktop 页面](https://docs.docker.com/desktop/features/containerd/)。 +::: + +containerd 是行业标准的容器运行时,它使用快照程序而不是传统的存储驱动程序来存储映像和容器数据。 +虽然 `overlay2` 驱动程序仍然是 Docker Engine 的默认驱动程序,但您可以选择使用 containerd snapshotter 作为实验性功能。 + +要了解有关 containerd 映像存储及其优势的更多信息,请参阅 [containerd 映像存储](https://docs.docker.com/desktop/features/containerd/)。 + +## 在 Docker Engine 上启用 containerd 映像存储 +切换到 containerd 快照程序会导致您暂时丢失使用经典存储驱动程序创建的映像和容器。 +这些资源仍然存在于您的文件系统上,您可以通过关闭 containerd snapshotters 功能来检索它们。 + +以下步骤说明如何启用 containerd snapshotters 功能。 +1. 将以下配置添加到您的 `/etc/docker/daemon.json` 配置文件: +```json:line-numbers +{ + "features": { + "containerd-snapshotter": true + } +} +``` +2. 保存文件。 +3. 重新启动守护程序以使更改生效。 +```bash +sudo systemctl restart docker +``` + +重新启动守护程序后,运行 `docker info` 显示您正在使用 containerd snapshotter 存储驱动程序。 + +```bash +docker info -f '{{ .DriverStatus }}' +[[driver-type io.containerd.snapshotter.v1]] +``` + +Docker Engine 默认使用 `overlayfs` containerd snapshotter。 \ No newline at end of file diff --git a/docs/storage/mount.md b/docs/storage/mount.md new file mode 100644 index 0000000..6e97467 --- /dev/null +++ b/docs/storage/mount.md @@ -0,0 +1,340 @@ +--- +outline: [2,4] +--- +# 绑定挂载 +使用绑定挂载时,主机上的文件或目录将从主机挂载到容器中。 +相比之下,当您使用卷时,会在主机上的 Docker 存储目录中创建一个新目录,并且 Docker 会管理该目录的内容。 + +## 何时使用 bind 挂载 +绑定挂载适用于以下类型的用例: +- 在 Docker 主机上的开发环境和容器之间共享源代码或构建构件。 +- 当您想在容器中创建或生成文件并将文件持久化到主机的文件系统上时。 +- 将配置文件从主机共享到容器。就是这样 默认情况下,Docker 通过挂载 `/etc/resolv.conf` 从主机发送到每个容器中。 +绑定挂载也可用于构建:您可以将主机中的挂载源代码绑定到构建容器中,以测试、lint 或编译项目。 + +## 在现有数据上绑定挂载 +如果你将挂载文件或目录绑定到容器中存在文件或目录的目录中,则预先存在的文件会被挂载遮挡。 +这类似于将文件保存到 Linux 主机上的 `/mnt` 中,然后将 USB 驱动器挂载到 `/mnt` 中。 +`/mnt` 的内容将被 USB 驱动器的内容遮挡,直到卸载 USB 驱动器。 + +对于容器,没有直接的方法可以删除挂载以再次显示被遮挡的文件。最好的选择是重新创建没有挂载的容器。 + +## 注意事项和约束 +- 默认情况下,绑定挂载对主机上的文件具有写入访问权限。 + + 使用绑定挂载的一个副作用是,您可以通过容器中运行的进程更改主机文件系统,包括创建、修改或删除重要的系统文件或目录。此功能可能会产生安全隐患。例如,它可能会影响主机系统上的非 Docker 进程。 + + 您可以使用 `readonly` 或 `ro` 选项来防止容器写入挂载。 + +- 绑定挂载是到 Docker 守护程序主机创建的,而不是客户端。 + + 如果您使用的是远程 Docker 守护程序,则无法创建绑定挂载来访问容器中客户端计算机上的文件。 + + 对于 Docker Desktop,守护程序在 Linux VM 中运行,而不是直接在本机主机上运行。Docker Desktop 具有透明处理绑定挂载的内置机制,允许您与虚拟机中运行的容器共享本机主机文件系统路径。 + +- 具有绑定挂载的容器与主机紧密绑定。 + + 定挂载依赖于具有特定可用目录结构的主机文件系统。这种依赖性意味着,如果在没有相同目录结构的不同主机上运行,则具有绑定挂载的容器可能会失败。 + +## Syntax 语法 +要创建绑定挂载,您可以使用 `--mount` 或 `--volume` 标志。 + +```bash +docker run --mount type=bind,src=,dst= +docker run --volume : +``` + +通常,`--mount` 是首选。主要区别在于 `--mount` flag 更明确,并支持所有可用选项。 + +如果您使用 `--volume` 绑定挂载 Docker 主机上尚不存在的文件或目录,Docker 会自动在主机上为您创建该目录。它始终创建为目录。 + +如果主机上不存在指定的挂载路径,则 `--mount` 不会自动创建目录。相反,它会产生一个错误: + +```bash +docker run --mount type=bind,src=/dev/noexist,dst=/mnt/foo alpine +docker: Error response from daemon: invalid mount config for type "bind": bind source path does not exist: /dev/noexist. +``` + +### --mount 的选项 +`--mount` 标志由多个键值对组成,用逗号分隔,每个键值对由一个 `=` 元组组成。键的顺序并不重要。 +```bash +docker run --mount type=bind,src=,dst=[,=...] +``` +`--mount type=bind` 的有效选项包括: + +| 选项 | 描述 | +| - | - | +| `source`, `src` | 文件或目录在主机上的位置。这可以是绝对路径或相对路径。 | +| `destination`, `dst`, `target` | 文件或目录在容器中挂载的路径。必须是绝对路径。 | +| `readonly`, `ro` | 如果存在,则会导致 bind 挂载为 [以只读方式挂载到容器中](https://docs.docker.com/engine/storage/bind-mounts/#use-a-read-only-bind-mount)。 | +| `bind-propagation` | 如果存在,则更改 [绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | + +###### 例 +```bash +docker run --mount type=bind,src=.,dst=/project,ro,bind-propagation=rshared +``` + +### --volume 的选项 +`--volume` 或 `-v` 标志由三个字段组成,用冒号字符 (` : `) 分隔。字段的顺序必须正确。 +```bash +docker run -v :[:opts] +``` + +第一个字段是主机上要将 mount 绑定到容器的路径。第二个字段是文件或目录在容器中挂载的路径。 + +第三个字段是可选的,是以逗号分隔的选项列表。带有绑定挂载的 `--volume` 的有效选项包括: + +| 选项 | 描述 | +| - | - | +| `readonly`, `ro` | 如果存在,则会导致 bind 挂载为 [以只读方式挂载到容器中](https://docs.docker.com/engine/storage/bind-mounts/#use-a-read-only-bind-mount)。 | +| `z`, `Z` | 配置 SELinux 标签。看 [配置 SELinux 标签](https://docs.docker.com/engine/storage/bind-mounts/#configure-the-selinux-label) | +| `rprivate` (default) | 将此挂载的 bind propagation 设置为 `rprivate。看` [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | +| `private` | 将此挂载的 bind propagation 设置为 `private。看` [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | +| `rshared` | 将此挂载的 bind propagation 设置为 rshared。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | +| `shared` | 将此挂载的 bind propagation 设置为 shared。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | +| `rslave` | 为此挂载将 bind propagation 设置为 rslave。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | +| `slave` | 将此挂载的 bind propagation 设置为 slave。看 [配置绑定传播](https://docs.docker.com/engine/storage/bind-mounts/#configure-bind-propagation)。 | + +###### 例 +```bash +docker run -v .:/project:ro,rshared +``` + +## 使用绑定挂载启动容器 +考虑这样一种情况:您有一个目录 `source`,并且在构建源代码时,工件被保存到另一个目录 `source/target/` 中。 +您希望构件可用于位于 `/app/` 的容器,并且希望容器每次在开发主机上构建源代码时都能访问新的构建。 +使用以下命令绑定挂载 `target/` 目录中的 `/app/` 中。 +从 `source` 目录中。子命令 `$(pwd)` 扩展为当前正在运行的 目录。 如果您使用的是 Windows,另请参阅 [Windows 上的路径转换](https://docs.docker.com/desktop/troubleshoot-and-support/troubleshoot/topics/)。 + +以下 `--mount` 和 `-v` 示例产生相同的结果。除非在运行第一个容器后删除 `devtest` 容器,否则无法同时运行这两个容器。 + +:::code-group +```bash [--mount] +docker run -d \ + -it \ + --name devtest \ + --mount type=bind,source="$(pwd)"/target,target=/app \ + nginx:latest +``` +```bash [-v] +docker run -d \ + -it \ + --name devtest \ + -v "$(pwd)"/target:/app \ + nginx:latest +``` +::: + +使用 `docker inspect devtest` 验证是否已正确创建绑定挂载。查找 `Mounts` 部分: + +```bash:line-numbers +"Mounts": [ + { + "Type": "bind", + "Source": "/tmp/source/target", + "Destination": "/app", + "Mode": "", + "RW": true, + "Propagation": "rprivate" + } +], +``` +这表明挂载是`bind`挂载,它显示了正确的源和目标,它表明挂载是读写的,并且传播设置为 `rprivate`。 + +停止并移除容器: +```bash +docker container rm -fv devtest +``` + +### 挂载到容器上的非空目录中 +如果将目录绑定挂载到容器上的非空目录中,则 目录的现有内容被 bind 挂载遮挡。 +这可以是 有益的,例如当您想要测试应用程序的新版本时 而无需构建新映像。 +然而,它也可能令人惊讶,而且这 行为与 [卷](https://docs.docker.com/engine/storage/volumes/)。 + +这个例子是人为的极端,但将容器的 `/usr/` 目录的内容替换为主机上的 `/tmp/` 目录。在大多数情况下,这会导致容器无法正常工作。 + +`--mount` 和 `-v` 示例具有相同的最终结果。 + +:::code-group +```bash [--mount] +docker run -d \ + -it \ + --name broken-container \ + --mount type=bind,source=/tmp,target=/usr \ + nginx:latest + +docker: Error response from daemon: oci runtime error: container_linux.go:262: +starting container process caused "exec: \"nginx\": executable file not found in $PATH". +``` +```bash [-v] +docker run -d \ + -it \ + --name broken-container \ + -v /tmp:/usr \ + nginx:latest + +docker: Error response from daemon: oci runtime error: container_linux.go:262: +starting container process caused "exec: \"nginx\": executable file not found in $PATH". +``` +::: + +容器已创建,但未启动。删除它: + +```bash +docker container rm broken-container +``` + +## 使用只读绑定挂载 +对于某些开发应用程序,容器需要写入绑定挂载,因此更改将传播回 Docker 主机。在其他时候,容器只需要读取访问权限。 + +此示例修改了前一个示例,但通过将 `ro` 添加到选项列表(默认为空)中,在容器内的挂载点之后,将目录挂载为只读绑定挂载。如果存在多个选项,请用逗号分隔它们。 + +:::code-group +```bash [--mount] +docker run -d \ + -it \ + --name devtest \ + --mount type=bind,source="$(pwd)"/target,target=/app,readonly \ + nginx:latest +``` +```bash [-v] +docker run -d \ + -it \ + --name devtest \ + -v "$(pwd)"/target:/app:ro \ + nginx:latest +``` +::: + +使用` docker inspect devtest `验证是否已正确创建绑定挂载。查找 `Mounts` 部分: +```bash:line-numbers +"Mounts": [ + { + "Type": "bind", + "Source": "/tmp/source/target", + "Destination": "/app", + "Mode": "ro", + "RW": false, + "Propagation": "rprivate" + } +], +``` +停止并移除容器: +```bash +docker container rm -fv devtest +``` + +## 递归挂载 +当您绑定挂载本身包含 mounts 的路径时,默认情况下,这些子挂载也包含在 bind 挂载中。 +此行为是可配置的,使用 `--mount` 的 `bind-recursive` 选项。此选项仅支持 `--mount` 标志,而不支持 `-v` 或 `--volume`。 + +如果绑定挂载是只读的,则 Docker 引擎会尽力而为 将 submounts 也设为只读。 +这称为递归 只读挂载。递归只读挂载需要 Linux 内核版本 5.12 或之后。 +如果你运行的是较旧的内核版本,则 submounts 是 默认情况下,自动挂载为读写。 +尝试将 submounts 设置为 在 5.12 之前的内核版本上为只读,使用 `bind-recursive=readonly` 选项,则会导致错误。 + +`bind-recursive` 选项支持的值为: +| 选项 | 描述 | +| - | - | +| `enabled` (默认) | 如果内核为 v5.12 或更高版本,则只读挂载将递归为只读。否则,子挂载是读写的。| +| `disabled` (禁用) | 子挂载将被忽略(不包括在 bind 挂载中)。 | +| `writable` (写) | Submounts 是读写的。 | +| `readonly` (只读) | Submounts 是只读的。需要内核 v5.12 或更高版本。 | + +## 配置绑定传播 +绑定挂载和卷的绑定传播默认为 `rprivate`。它只能针对绑定挂载进行配置,并且只能在 Linux 主机上进行配置。绑定传播是一个高级主题,许多用户从不需要配置它。 + +绑定传播是指在给定绑定挂载中创建的挂载是否可以传播到该挂载的副本。 +考虑一个挂载点 `/mnt`,它也挂载在 `/tmp` 上。传播设置控制 `/tmp/a` 上的挂载是否也可以在 `/mnt/a` 上使用。 +每个传播设置都有一个递归对位点。在递归的情况下,请考虑 `/tmp/a` 也挂载为 `/foo`。传播设置控制 `/mnt/a` 和/或 `/tmp/a` 是否存在。 + +:::warning 注意 +挂载传播不适用于 Docker Desktop。 +::: + +| 传播设置 | 描述 | +| - | - | +| `shared ` (共享) | 原始挂载的子挂载会暴露给副本挂载,而副本挂载的子挂载也会传播到原始挂载。 | +| `slave` (奴隶) | 类似于共享挂载,但仅在一个方向上。如果原始挂载公开了子挂载,则副本挂载可以看到它。但是,如果副本挂载公开了子挂载,则原始挂载无法看到它。 | +| `private` (私人) | 挂载是私有的。其中的子挂载不会暴露给副本挂载,而副本挂载的子挂载也不会暴露给原始挂载。 | +| `rshared` | 与 shared 相同,但传播也扩展到嵌套在任何原始或副本挂载点中的挂载点或从这些挂载点扩展。 | +| `rslave` | 与 slave 相同,但传播也扩展到嵌套在任何原始或复制副本挂载点中的挂载点。 | +| `rprivate` | 默认值。与 private 相同,这意味着原始挂载点或副本挂载点中的任何位置都不会沿任一方向传播。 | + +在挂载点上设置绑定传播之前,主机文件系统需要已经支持绑定传播。 + +有关绑定传播的更多信息,请参阅 [共享子树的 Linux 内核文档](https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt)。 + +以下示例将 `target/` 目录挂载到容器中两次,第二次挂载同时设置 `ro` 选项和 `rslave` 绑定传播选项。 + +`--mount` 和 `-v` 示例具有相同的结果。 + +:::code-group +```bash [--mount] +docker run -d \ + -it \ + --name devtest \ + --mount type=bind,source="$(pwd)"/target,target=/app \ + --mount type=bind,source="$(pwd)"/target,target=/app2,readonly,bind-propagation=rslave \ + nginx:latest +``` +```bash [-v] +docker run -d \ + -it \ + --name devtest \ + -v "$(pwd)"/target:/app \ + -v "$(pwd)"/target:/app2:ro,rslave \ + nginx:latest +``` +::: + +现在,如果你创建 `/app/foo/`,`/app2/foo/` 也存在。 + +## 配置 SELinux 标签 +如果您使用 SELinux,则可以添加 `z` 或 `Z` 选项来修改要挂载到容器中的主机文件或目录的 SELinux 标签。 +这会影响主机本身的文件或目录,并可能产生超出 Docker 范围的后果。 + +- `z` 选项指示绑定挂载内容在多个容器之间共享。 +- 该 `Z` 选项指示绑定挂载内容是私有且未共享的。 + +使用这些选项时要格外小心。使用 `Z` 选项绑定挂载系统目录(如 `/home` 或 `/usr`)会导致主机无法操作,您可能需要手动重新标记主机文件。 + +:::danger 重要 +当将 bind 挂载与服务一起使用时,SELinux 标签( `:Z` 和 `:z`)以及 `:ro` 将被忽略。 +看 [moby/moby #32579](https://github.com/moby/moby/issues/32579) 了解详情。 +::: + +此示例设置 `z` 选项以指定多个容器可以共享绑定挂载的内容: + +无法使用 `--mount` 标志修改 SELinux 标签。 + +```bash +docker run -d \ + -it \ + --name devtest \ + -v "$(pwd)"/target:/app:z \ + nginx:latest +``` + +## 将绑定挂载与 Docker Compose 结合使用 +具有绑定挂载的单个 Docker Compose 服务如下所示: + +```yaml:line-numbers +services: + frontend: + image: node:lts + volumes: + - type: bind + source: ./static + target: /opt/app/static +volumes: + myapp: +``` + +有关在 Compose 中使用 `bind` 类型的卷的更多信息,请参阅 [在卷上编写引用](https://docs.docker.com/reference/compose-file/services/#volumes)。 +和 [Compose 参考 关于卷配置](https://docs.docker.com/reference/compose-file/services/#volumes)。 + +## 后续步骤 +- 了解 [卷](/docs/storage/volumes)。 +- 了解 [tmpfs 挂载](/docs/storage/tmpfs-bind.md)。 +- 了解 [存储驱动程序](/docs/storage/drivers/)。 \ No newline at end of file diff --git a/docs/storage/tmpfs-bind.md b/docs/storage/tmpfs-bind.md new file mode 100644 index 0000000..7b4b9a5 --- /dev/null +++ b/docs/storage/tmpfs-bind.md @@ -0,0 +1,101 @@ +--- +outline: [2,4] +--- + +# tmpfs 挂载 +[Volumes](https://docs.docker.com/engine/storage/volumes/) 和 [绑定挂载](https://docs.docker.com/engine/storage/bind-mounts/)允许您在主机和容器之间共享文件,以便即使在容器停止后也可以保留数据。 + +如果您在 Linux 上运行 Docker,则还有第三个选项:tmpfs mounts。当您使用 tmpfs 挂载创建容器时,容器可以在容器的可写层之外创建文件。 + +与 volumes 和 bind 挂载相反,tmpfs 挂载是临时的,仅保留在主机内存中。当容器停止时,tmpfs 挂载将被删除,并且写入其中的文件不会被持久化。 + +tmpfs 挂载最适合用于不希望数据保留在主机上或容器内的情况。这可能是出于安全原因,或者是为了在应用程序需要写入大量非持久性状态数据时保护容器的性能。 + +:::danger 重要 +tmpfs 挂载直接映射到 [tmpfs](https://en.wikipedia.org/wiki/Tmpfs) 的 Linux 内核。因此,临时数据可以写入交换文件,从而持久化到文件系统中。 +::: + +## 挂载到现有数据上 +如果在容器中存在文件或目录的目录中创建 tmpfs 挂载,则预先存在的文件将被挂载遮挡。 +这类似于将文件保存到 Linux 主机上的 `/mnt` 中,然后将 USB 驱动器挂载到 `/mnt` 中。 +`/mnt` 的内容将被 USB 驱动器的内容遮挡,直到卸载 USB 驱动器。 + +对于容器,没有直接的方法可以删除挂载以再次显示被遮挡的文件。最好的选择是重新创建没有挂载的容器。 + +## tmpfs 挂载的限制 +- 与卷和绑定挂载不同,您不能在容器之间共享 tmpfs 挂载。 +- 仅当在 Linux 上运行 Docker 时,此功能才可用。 +- 在 tmpfs 上设置权限可能会导致它们 [容器重启后重置](https://github.com/docker/for-linux/issues/138)。在某些情况下 [设置 UID/GID](https://github.com/docker/compose/issues/3425#issuecomment-423091370) 可以作为一种解决方法。 + +## 语法 +要使用 `docker run` 命令挂载 tmpfs,您可以使用 `--mount` 或 `--tmpfs` 标志。 +```bash +docker run --mount type=tmpfs,dst= +docker run --tmpfs +``` + +通常,`--mount` 是首选。主要区别在于 `--mount` flag 更明确,并支持所有可用选项。 + +`--tmpfs` 标志不能用于 swarm 服务。您必须使用 `--mount`。 + +### --mount 的选项 +`--mount` 标志由多个键值对组成,用逗号分隔,每个键值对由一个 `=` 元组组成。键的顺序并不重要。 + +```bash +docker run --mount type=tmpfs,dst=[,=...] +``` + +`--mount type=tmpfs` 的有效选项包括: +| 选项 | 描述 | +| - | - | +| `destination`, `dst`, `target` (目标) | tmpfs 挂载的大小(以字节为单位)。如果未设置,则 tmpfs 卷的默认最大大小为主机总 RAM 的 50%。 | +| `tmpfs-size` (tmpfs 大小) | tmpfs 挂载的大小(以字节为单位)。如果未设置,则 tmpfs 卷的默认最大大小为主机总 RAM 的 50%。 | +| `tmpfs-mode` (tmpfs 模式) | tmpfs 的文件模式(以八进制为单位)。例如,`700` 或 `0770`。默认为 `1777` 或 world-writable。 | + +###### 例 +```bash +docker run --mount type=tmpfs,dst=/app,tmpfs-size=21474836480,tmpfs-mode=1770 +``` + +### --tmpfs 的选项 +`--tmpfs` 标志不允许您指定任何选项。 + +## 在容器中使用 tmpfs 挂载 +要在容器中使用 `tmpfs` 挂载,请使用 `--tmpfs` 标志,或使用 `--mount` 标志与 `type=tmpfs` 和 `destination` 选项一起使用。 +没有 `tmpfs` 挂载的源。以下示例在 `/app` 中。第一个示例使用 `--mount` 标志,第二个示例使用 `--tmpfs` 标志。 + +:::code-group +```bash [--mount] +docker run -d \ + -it \ + --name tmptest \ + --mount type=tmpfs,destination=/app \ + nginx:latest +``` +```bash [--tmpfs] +$ docker run -d \ + -it \ + --name tmptest \ + --tmpfs /app \ + nginx:latest +``` +::: + +通过查看 `docker inspect` 输出的 `Mounts` 部分来验证挂载是否为 `tmpfs` 挂载: + +```bash +docker inspect tmptest --format '{{ json .Mounts }}' +[{"Type":"tmpfs","Source":"","Destination":"/app","Mode":"","RW":true,"Propagation":""}] +``` + +停止并移除容器: + +```bash +docker stop tmptest +docker rm tmptest +``` + +## 后续步骤 +- 了解 [卷](/docs/storage/volumes)。 +- 了解 [绑定挂载](/docs/storage/mount.md)。 +- 了解 [存储驱动程序](/docs/storage/drivers/)。 \ No newline at end of file diff --git a/docs/storage/volume.md b/docs/storage/volume.md index 47eed6f..88c74d1 100644 --- a/docs/storage/volume.md +++ b/docs/storage/volume.md @@ -1 +1,600 @@ -# 卷 \ No newline at end of file +--- +outline: [2,4] +--- +# 卷 + +卷是容器的持久性数据存储,由 Docker 创建和管理。 +您可以使用 `docker volume create` 显式创建卷 命令,或者 Docker 可以在容器或服务创建期间创建卷。 + +创建卷时,它存储在 Docker 主机上的目录中。 +当您将卷挂载到容器中时,此目录就是挂载到容器中的内容。 +这与绑定挂载的工作方式类似,不同之处在于卷由 Docker 管理,并且与主机的核心功能隔离。 + +## 何时使用卷 + +卷是保存 由 生成和已使用的数据的首选机制 通过 Docker 容器。 +而 [bind mounts](https://docs.docker.com/engine/storage/bind-mounts/) 取决于主机的目录结构和操作系统,卷完全由 Docker 管理。 +对于以下使用案例,卷是一个不错的选择: +- 卷比绑定挂载更容易备份或迁移。 +- 您可以使用 Docker CLI 命令或 Docker API 管理卷。 +- 卷适用于 Linux 和 Windows 容器。 +- 可以更安全地在多个容器之间共享卷。 +- 新卷的内容可以由容器或内部版本预先填充。 +- 当您的应用程序需要高性能 I/O 时。 + +如果您需要从主机访问文件,卷不是一个好的选择,因为 卷完全由 Docker 管理。 +用 [绑定挂载](https://docs.docker.com/engine/storage/bind-mounts/) 如果您需要从容器和主机访问文件或目录。 + +卷通常是比将数据直接写入容器更好的选择。 +因为卷不会增加使用它的容器的大小。 +使用 音量也更快;写入容器的可写层需要一个 [storage 驱动程序](https://docs.docker.com/engine/storage/drivers/)来管理文件系统。 +存储驱动程序使用 Linux 内核提供联合文件系统。 +与使用直接写入主机文件系统的卷相比,这种额外的抽象会降低性能。 + +如果您的容器生成非持久性状态数据,请考虑使用 [tmpfs 挂载](https://docs.docker.com/engine/storage/tmpfs/)以避免将数据永久存储在任何位置, +并通过避免写入容器的可写层来提高容器的性能。 + +卷使用 `rprivate` 绑定传播,并且无法为卷配置绑定传播。 + +## 卷的生命周期 + +卷的内容存在于给定容器的生命周期之外。销毁容器时,可写层也会随之销毁。 +使用卷可确保即使删除了使用卷的容器,数据也会保留。 + +给定的卷可以同时挂载到多个容器中。当没有正在运行的容器正在使用卷时, +该卷仍可供 Docker 使用,并且不会自动删除。您可以使用 `docker volume prune` 删除未使用的卷。 + +## 在现有数据上挂载卷 + +如果将非空卷挂载到容器中存在文件或目录的目录中,则预先存在的文件将被挂载遮挡。 +这类似于将文件保存到 Linux 主机上的 `/mnt` 中,然后将 USB 驱动器挂载到 `/mnt` 中。 +`/mnt` 的内容将被 USB 驱动器的内容遮挡,直到卸载 USB 驱动器。 + +对于容器,没有直接的方法可以删除挂载以再次显示被遮挡的文件。最好的选择是重新创建没有挂载的容器。 + +如果将空卷挂载到容器中存在文件或目录的目录中,则默认情况下,这些文件或目录将传播(复制)到卷中。 +同样,如果您启动容器并指定一个尚不存在的卷,则会为您创建一个空卷。这是预填充其他容器所需的数据的好方法。 + +要防止 Docker 将容器的预先存在的文件复制到空卷中,请使用 `volume-nocopy` 选项, +请参阅 [--mount 的选项](https://docs.docker.com/engine/storage/volumes/#options-for---mount)。 + +## 命名卷和匿名卷 + +卷可以是 named 或 anonymous。匿名卷被赋予一个随机名称,该名称保证在给定的 Docker 主机中是唯一的。 +与命名卷一样,即使您删除了使用它们的容器,匿名卷也会保留,除非您在创建容器时使用 `--rm` 标志,其中 如果销毁与容器关联的匿名卷。 +看 [删除匿名卷](https://docs.docker.com/engine/storage/volumes/#remove-anonymous-volumes)。 + +如果您连续创建多个容器,每个容器都使用匿名卷,则每个容器都会创建自己的卷。 +匿名卷不会自动在容器之间重复使用或共享。要在两个或多个容器之间共享匿名卷,您必须使用随机卷 ID 挂载匿名卷。 + +## Syntax 语法 + +要使用 `docker run` 命令挂载卷,您可以使用 `--mount` 或 `--volume` 标志。 + +```bash +docker run --mount type=volume,src=,dst= +docker run --volume : +``` + +通常,`--mount` 是首选。主要区别在于 `--mount` flag 更明确,并支持所有可用选项。 + +如果要执行以下操作,则必须使用 `--mount`: +- 指定 [卷驱动程序选项](https://docs.docker.com/engine/storage/volumes/#use-a-volume-driver) +- 挂载 [volume 子目录](https://docs.docker.com/engine/storage/volumes/#mount-a-volume-subdirectory) +- 将卷挂载到 Swarm 服务 + +### --mount 的选项 +`--mount` 标志由多个键值对组成,用逗号分隔,每个键值对由一个 `=` 元组组成。键的顺序并不重要。 + +```bash +docker run --mount type=volume[,src=],dst=[,=...] +``` + +`--mount type=volume` 的有效选项包括: + +| 选项 | 描述 | +| - | - | +| `source`, `src` | 挂载的源。对于命名卷,这是卷的名称。对于匿名卷,此字段被省略。 | +| `destination`, `dst`, `target` | 文件或目录在容器中挂载的路径。 | +| `volume-subpath` | 卷中要挂载到容器中的子目录的路径。在将卷挂载到容器之前,该子目录必须存在于卷中。看 [挂载 volume 子目录](https://docs.docker.com/engine/storage/volumes/#mount-a-volume-subdirectory)。 | +| `readonly`, `ro` | 如果存在,则会导致卷为 [以只读方式挂载到容器中](https://docs.docker.com/engine/storage/volumes/#use-a-read-only-volume)。 | +| `volume-nocopy` | 如果存在,则如果卷为空,则不会将目标中的数据复制到卷中。默认情况下,如果目标内容为空,则会将目标内容复制到已挂载的卷中。 | +| `volume-opt` | 可以多次指定,采用由选项名称及其值组成的键值对。 | + +###### 例子 +```bash +docker run --mount type=volume,src=myvolume,dst=/data,ro,volume-subpath=/foo +``` + +### --volume 的选项 + +`--volume` 或 `-v` 标志由三个字段组成,用冒号字符 (` : `) 分隔。字段的顺序必须正确。 +```bash +docker run -v [:][:opts] +``` + +对于命名卷,第一个字段是卷的名称,在给定主机上是唯一的。对于匿名卷,将省略第一个字段。第二个字段是文件或目录在容器中挂载的路径。 + +第三个字段是可选的,是以逗号分隔的选项列表。`--volume` 的有效选项包括: + +| 选项 | 描述 | +| - | - | +| `readonly`, `ro` | 如果存在,则会导致卷为 [以只读方式挂载到容器中](https://docs.docker.com/engine/storage/volumes/#use-a-read-only-volume)。 | +| `volume-nocopy` | 如果存在,则如果卷为空,则不会将目标中的数据复制到卷中。默认情况下,如果目标内容为空,则会将目标内容复制到已挂载的卷中。 | + +###### 例子 +```bash +docker run -v myvolume:/data:ro +``` + +## 创建和管理卷 +与绑定挂载不同,您可以在任何容器的范围之外创建和管理卷。 + +创建卷: +```bash +docker volume create my-vol +``` +列出卷: +```bash +docker volume ls + +local my-vol +``` + +检查卷: +```bash +docker volume inspect my-vol +[ + { + "Driver": "local", + "Labels": {}, + "Mountpoint": "/var/lib/docker/volumes/my-vol/_data", + "Name": "my-vol", + "Options": {}, + "Scope": "local" + } +] +``` + +删除卷: +```bash +docker volume rm my-vol +``` + +## 使用卷启动容器 +如果您使用尚不存在的卷启动容器,Docker 会为您创建该卷。以下示例将卷 myvol2 挂载到 /app/ 中。 + +下面的 `-v` 和 `--mount` 示例产生相同的结果。除非在运行第一个容器和 `myvol2` 卷后删除 `devtest` 容器和 `myvol2` 卷,否则无法同时运行它们。 + +:::code-group +```bash [--mount] +docker run -d \ + --name devtest \ + --mount source=myvol2,target=/app \ + nginx:latest +``` + +```bash [-v] +docker run -d \ + --name devtest \ + -v myvol2:/app \ + nginx:latest +``` +::: + +使用 `docker inspect devtest` 验证 Docker 是否创建了卷并正确挂载了卷。查找 `Mounts` 部分: + +```bash:line-numbers +"Mounts": [ + { + "Type": "volume", + "Name": "myvol2", + "Source": "/var/lib/docker/volumes/myvol2/_data", + "Destination": "/app", + "Driver": "local", + "Mode": "", + "RW": true, + "Propagation": "" + } +], +``` + +这表明挂载是一个卷,它显示了正确的源和目标,并且挂载是读写的。 + +停止容器并删除卷。请注意,卷删除是一个单独的步骤。 + +```bash +docker container stop devtest + +docker container rm devtest + +docker volume rm myvol2 +``` + +## 将卷与 Docker Compose 结合使用 +以下示例显示了一个具有卷的 Docker Compose 服务: +```yaml:line-numbers +services: + frontend: + image: node:lts + volumes: + - myapp:/home/node/app +volumes: + myapp: +``` +首次运行 `docker compose up` 会创建一个卷。Docker 在随后运行命令时重用相同的卷。 + +您可以使用 `docker volume create` 直接在 Compose 外部创建卷,然后在` compose.yaml `中引用它,如下所示: +```yaml:line-numbers +services: + frontend: + image: node:lts + volumes: + - myapp:/home/node/app +volumes: + myapp: + external: true +``` + +有关通过 Compose 使用卷的更多信息,请参阅 [卷](https://docs.docker.com/reference/compose-file/volumes/) 部分。 + +### 使用卷启动服务 +当您启动服务并定义卷时,每个服务容器都使用自己的本地卷。 +如果您使用本地 volume 驱动程序。但是,某些卷驱动程序确实支持共享存储。 + +以下示例启动具有四个副本的` nginx `服务,每个副本都使用名为 `myvol2` 的本地卷。 + +```bash +docker service create -d \ + --replicas=4 \ + --name devtest-service \ + --mount source=myvol2,target=/app \ + nginx:latest +``` + +使用 `docker service ps devtest-service` 验证服务是否正在运行: +```console +docker service ps devtest-service + +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +4d7oz1j85wwn devtest-service.1 nginx:latest moby Running Running 14 seconds ago +``` + +您可以删除该服务以停止正在运行的任务: +```bash +docker service rm devtest-service +``` +删除服务不会删除该服务创建的任何卷。卷删除是一个单独的步骤。 + +### 使用容器填充卷 +如果您启动一个创建新卷的容器,并且该容器在要挂载的目录中有文件或目录(例如 `/app/`),则 Docker 会将该目录的内容复制到该卷中。然后,容器挂载并使用该卷,使用该卷的其他容器也可以访问预填充的内容。 + +为了说明这一点,以下示例启动一个 `nginx` 容器,并使用容器的 `/usr/share/nginx/html` 目录下。这是 Nginx 存储其默认 HTML 内容的地方。 + +`--mount` 和 `-v` 示例具有相同的最终结果。 + +:::code-group +```bash [--mount] +docker run -d \ + --name=nginxtest \ + --mount source=nginx-vol,destination=/usr/share/nginx/html \ + nginx:latest +``` +```bash [-v] +docker run -d \ + --name=nginxtest \ + -v nginx-vol:/usr/share/nginx/html \ + nginx:latest +``` +::: + +运行这些示例中的任何一个后,运行以下命令以清理容器和卷。请注意,卷删除是一个单独的步骤。 +```bash +docker container stop nginxtest + +docker container rm nginxtest + +docker volume rm nginx-vol +``` + +## 使用只读卷 +对于某些开发应用程序,容器需要写入绑定挂载,以便将更改传播回 Docker 主机。 +在其他时候,容器只需要对数据进行读取访问。多个容器可以挂载同一个卷。 +对于某些容器,您可以同时将单个卷挂载为`read-write`卷,而将其他容器挂载为`read-only`卷。 + +以下示例更改了上一个示例。它将目录挂载为只读卷,方法是将 `ro` 添加到选项列表(默认为空)中,在容器中的挂载点之后。 +如果存在多个选项,您可以使用逗号分隔它们。 + +:::code-group +```bash [--mount] +docker run -d \ + --name=nginxtest \ + --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \ + nginx:latest +``` +```bash [-v] +docker run -d \ + --name=nginxtest \ + -v nginx-vol:/usr/share/nginx/html:ro \ + nginx:latest +``` +::: + +使用 `docker inspect nginxtest` 验证 Docker 是否正确创建了只读挂载。查找 `Mounts` 部分: + +``` json:line-numbers +"Mounts": [ + { + "Type": "volume", + "Name": "nginx-vol", + "Source": "/var/lib/docker/volumes/nginx-vol/_data", + "Destination": "/usr/share/nginx/html", + "Driver": "local", + "Mode": "", + "RW": false, + "Propagation": "" + } +], +``` + +停止并删除容器,然后删除卷。卷删除是一个单独的步骤。 +``` bash +docker container stop nginxtest + +docker container rm nginxtest + +docker volume rm nginx-vol +``` + +## 挂载 volume 子目录 +将卷挂载到容器时,您可以使用 `--mount` 标志的 `volume-subpath` 参数指定要使用的卷的子目录。 +在尝试将卷挂载到容器中之前,您指定的子目录必须存在于卷中;如果不存在,则挂载失败。 + +如果您只想与容器共享卷的特定部分,则指定 `volume-subpath` 非常有用。 +例如,假设您正在运行多个容器,并且您希望将每个容器的日志存储在共享卷中。 +您可以为共享卷中的每个容器创建一个子目录,并将该子目录挂载到容器中。 + +以下示例创建一个`logs` 卷并启动子目录 `app1` 和 `app2` 中的请求。 +然后,它会启动两个容器,并将`logs`卷的一个子目录挂载到每个容器。 +这个例子 假设容器中的进程将其日志写入 `/var/log/app1` 和 `/var/log/app2` 的 URL 中。 + +```bash +docker volume create logs + +docker run --rm \ + --mount src=logs,dst=/logs \ + alpine mkdir -p /logs/app1 /logs/app2 + +docker run -d \ + --name=app1 \ + --mount src=logs,dst=/var/log/app1/,volume-subpath=app1 \ + app1:latest + +docker run -d \ + --name=app2 \ + --mount src=logs,dst=/var/log/app2,volume-subpath=app2 \ + app2:latest +``` + +通过此设置,容器将其日志写入`logs`卷的单独子目录。容器无法访问其他容器的日志。 + +## 在计算机之间共享数据 +在构建容错应用程序时,您可能需要配置同一服务的多个副本才能访问相同的文件。 + +![共享数据示意图](/docs/storage/volumes-shared-storage.webp) + +在开发应用程序时,有几种方法可以实现此目的。一种是向应用程序添加逻辑,以将文件存储在 Amazon S3 等云对象存储系统上。 +另一种方法是使用支持将文件写入外部存储系统(如 NFS 或 Amazon S3)的驱动程序创建卷。 + +卷驱动程序允许您从应用程序逻辑中抽象出底层存储系统。 +例如,如果您的服务使用带有 NFS 驱动程序的卷,则可以更新服务以使用其他驱动程序。 +例如,将数据存储在云中,而不更改应用程序逻辑。 + +## 使用卷驱动程序 +当您使用 `docker volume create` 创建卷时,或者当您启动使用尚未创建的卷的容器时,您可以指定卷驱动程序。 +以下示例首先在创建独立卷时使用 vieux/sshfs 卷驱动程序,然后在启动创建新卷的容器时使用 `vieux/sshfs` 卷驱动程序。 + +:::info 注意 +如果您的卷驱动程序接受逗号分隔的列表作为选项,则必须从外部 CSV 解析器中转义该值。 +要转义 `volume-opt`,请用双引号 (` " `) 将其括起来,并用单引号 (` ' `) 将整个 mount 参数括起来。 + +例如,`local`驱动程序接受 ` o ` 参数中以逗号分隔列表的形式接受挂载选项。此示例显示了转义列表的正确方法。 +```bash +docker service create \ + --mount 'type=volume,src=,dst=,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:,"volume-opt=o=addr=,vers=4,soft,timeo=180,bg,tcp,rw"' + --name myservice \ + +``` +::: + +### 初始设置 +以下示例假设您有两个节点,第一个节点是 Docker 主机,可以使用 SSH 连接到第二个节点。 + +在 Docker 主机上,安装 `vieux/sshfs` 插件: +```bash +docker plugin install --grant-all-permissions vieux/sshfs +``` + +### 使用卷驱动程序创建卷 +此示例指定 SSH 密码,但如果两台主机配置了共享密钥,则可以排除该密码。 +每个卷驱动程序可能有零个或多个可配置选项,您可以使用 `-o` 标志指定每个选项。 +```bash +docker volume create --driver vieux/sshfs \ + -o sshcmd=test@node2:/home/test \ + -o password=testpassword \ + sshvolume +``` + +### 启动一个使用卷驱动程序创建卷的容器 +以下示例指定 SSH 密码。但是,如果两个主机都配置了共享密钥,则可以排除密码。每个卷驱动程序可以有零个或多个可配置选项。 + +:::warning 注意 +如果卷驱动程序要求您传递任何选项,则必须使用 `--mount` 标志来挂载卷,而不是 `-v`。 +::: + +```bash +docker run -d \ + --name sshfs-container \ + --mount type=volume,volume-driver=vieux/sshfs,src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \ + nginx:latest +``` + +### 创建用于创建 NFS 卷的服务 +以下示例显示了如何在创建服务时创建 NFS 卷。 +它使用 `10.0.0.10` 作为 NFS 服务器,使用 `/var/docker-nfs` 作为 NFS 服务器上的导出目录。 +请注意,指定的卷驱动程序是 `local` 的。 + +#### NFSv3 +```bash +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 +```bash +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 共享,而无需在主机上配置挂载点。 +```bash +docker volume create \ + --driver local \ + --opt type=cifs \ + --opt device=//uxxxxx.your-server.de/backup \ + --opt o=addr=uxxxxx.your-server.de,username=uxxxxxxx,password=*****,file_mode=0777,dir_mode=0777 \ + --name cif-volume +``` + +如果指定主机名而不是 IP,则需要 `addr` 选项。这允许 Docker 执行主机名查找。 + +### 块存储设备 +您可以将块存储设备(例如外部驱动器或驱动器分区)挂载到容器。 +以下示例说明如何创建文件并将其用作块存储设备,以及如何将块存储设备挂载为容器卷。 + +:::tip 重要 +以下过程只是一个示例。不建议将此处所示的解决方案作为一般做法。除非您对自己正在做的事情有信心,否则不要尝试这种方法。 +::: + +#### 挂载块设备的工作原理 +在后台,使用`local`存储驱动程序的 `--mount` 标志会调用 Linux `mount`系统调用,并原封不动地转发您传递给它的选项。 +Docker 不会在 Linux 内核支持的本机挂载功能之上实现任何其他功能。 + +如果您熟悉 [Linux `mount` 命令](https://man7.org/linux/man-pages/man8/mount.8.html), +您可以按以下方式将 `--mount` 选项视为转发给 `mount` 命令: + +```bash +mount -t -o +``` + +为了进一步解释这一点,请考虑以下 `mount` 命令示例。此命令将 `/dev/loop5` 设备挂载到系统上的路径 `/external-drive`。 +```bash +mount -t ext4 /dev/loop5 /external-drive +``` + +以下 `docker run` 命令从正在运行的容器的角度来看,可实现类似的结果。使用此 `--mount` 选项运行容器会以与执行 `mount` 命令。 + +```bash +docker run \ + --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4' +``` + +您不能直接在容器内部运行 `mount` 命令,因为容器无法访问 `/dev/loop5` 设备。这就是 `docker run` 命令使用 `--mount` 选项的原因。 + +##### 示例:在容器中挂载块存储设备 +以下步骤创建一个 `ext4` 文件系统并将其挂载到容器中。系统的文件系统支持取决于您使用的 Linux 内核版本。 + +1. 创建一个文件并为其分配一些空间: +```bash +fallocate -l 1G disk.raw +``` +2. 在 `disk.raw` 文件上构建文件系统: +```bash +mkfs.ext4 disk.raw +``` +3. 创建 loop 设备: +```bash +losetup -f --show disk.raw +/dev/loop5 +``` +:::warning 注意 +`losetup` 会创建一个临时循环设备,该设备在系统重启后被删除,或者使用 `losetup -d` 手动删除。 +::: +4. 运行将 loop 设备挂载为卷的容器: +```bash +docker run -it --rm \ + --mount='type=volume,dst=/external-drive,volume-driver=local,volume-opt=device=/dev/loop5,volume-opt=type=ext4' \ + ubuntu bash +``` +当容器启动时,路径 `/external-drive` 会挂载 `disk.raw` 作为块设备从主机文件系统中获取的文件。 +5. 完成后,从容器中卸载设备后,分离 loop 设备以从主机系统中删除设备: +```bash +losetup -d /dev/loop5 +``` + +## 备份、还原或迁移数据卷 +卷可用于备份、还原和迁移。使用 `--volumes-from` 标志创建挂载该卷的新容器。 + +### 备份卷 +例如,创建一个名为 `dbstore` 的新容器: +```bash +docker run -v /dbdata --name dbstore ubuntu /bin/bash +``` + +在下一个命令中: +- 启动新容器并从 `dbstore` 容器挂载卷 +- 将本地主机目录挂载为 `/backup` +- 传递一个命令,将 `dbdata` 卷的内容 tar 到 `/backup` 目录中的 `backup.tar` 文件。 +```bash +docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata +``` +当命令完成并且容器停止时,它会创建 `dbdata` 卷的备份。 + +### 从备份还原卷 +使用刚刚创建的备份,您可以将其还原到同一容器,或还原到您在其他位置创建的另一个容器。 + +例如,创建一个名为 `dbstore2` 的新容器: +```bash +docker run -v /dbdata --name dbstore2 ubuntu /bin/bash +``` + +然后,在新容器的数据卷中解压备份文件: +```bash +docker run --rm --volumes-from dbstore2 -v $(pwd):/backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1" +``` + +您可以使用这些技术,通过您喜欢的工具自动执行备份、迁移和还原测试。 + +## 删除卷 +删除容器后,Docker 数据卷仍然存在。有两种类型的卷需要考虑: + +- 命名卷具有来自容器外部的特定源,例如 `awesome:/bar`。 +- 匿名卷没有特定的来源。因此,在删除容器时,您可以指示 Docker Engine 守护程序将其删除。 + +### 删除匿名卷 +要自动删除匿名卷,请使用 `--rm` 选项。例如,此命令会创建一个匿名的 `/foo` 卷。 +删除容器时,Docker 引擎会删除 `/foo` 卷,但不会删除 `awesome` 卷。 +```bash +docker run --rm -v /foo -v awesome:/bar busybox top +``` + +:::warning 注意 +如果另一个容器将卷与 `--volumes-from` 时,卷定义将被复制,匿名卷也会在删除第一个容器后保留。 +::: + +### 删除所有卷 +```bash +docker volume prune +``` + +## 后续步骤 + +- 了解 [绑定挂载](/docs/storage/mount)。 +- 了解 [tmpfs 挂载](/docs/storage/tmpfs-bind)。 +- 了解 [存储驱动程序](/docs/storage/drivers/)。 +- 了解 [第三方卷驱动程序插件](https://docs.docker.com/engine/extend/legacy_plugins/)。 \ No newline at end of file diff --git a/docs/storage/volumes-shared-storage.webp b/docs/storage/volumes-shared-storage.webp new file mode 100644 index 0000000000000000000000000000000000000000..05e2395105a5f519ef0926c39e6ee18d23d6c133 GIT binary patch literal 22132 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