docs.dockoro.cn/docs/storage/volume.md

600 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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=<volume-name>,dst=<mount-path>
docker run --volume <volume-name>:<mount-path>
```
通常,`--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` 标志由多个键值对组成,用逗号分隔,每个键值对由一个 `<key>=<value>` 元组组成。键的顺序并不重要。
```bash
docker run --mount type=volume[,src=<volume-name>],dst=<mount-path>[,<key>=<value>...]
```
`--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 [<volume-name>:]<mount-path>[: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=<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>
```
:::
### 初始设置
以下示例假设您有两个节点,第一个节点是 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 <mount.volume-opt.type> <mount.volume-opt.device> <mount.dst> -o <mount.volume-opts.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/)。