5.1.1.1. 前言
volume 是保存 Docker 容器生成、使用数据的首选机制。与 bind mounts 相识,都依赖于主机的目录结构,但是 volume
(卷)
完全由 Docker 进行管理管理。使用 volume 由以下优点:
- 相对于
bind mounts
,volume
更容易备份、迁移和恢复数据。 - 你完全可以通过 Docker CLI 命令和 Docker API 进行管理 volumes。
- volume 同时适用于 Linux 和 Windows 平台。
- 在多容器之间分享数据 volumes 显得更加安全。
- 使用 volumes 可以实现在远程主机或云上存储以及加密卷的内容数据。
- 创建一个新 volumes 时可以通过容器预先填充数据内容。
此外,卷(volumes
)相比于容器的可写层持久化数据更具有优势,因为卷不会增加使用它的容器的大小。并且,卷独立与容器的生命周期之外。可以通过下面这张图
了解卷相比较 bind mount
和 tmpfs mount
之间数据存储位置的不同。
如果容器生成的是非持久化数据,可以考虑使用 tmpfs mount 以避免将数据永久存储在宿主机器中的任何位置,并且通过避免写入容器的可写层来提高 容器的性能。
卷(volumes
)使用 rprivate
绑定传播(propagation),并且卷不可配置绑定传播。
5.1.1.2. --volume 和 --mount
从 docker 诞生之初,-v
或 --volume
选项一直使用于独立容器,--mount
选项则是使用于 swarm
集群服务中。不过,从 Docker v17.06
开始,--mount
同样适用于独立容器。与 --volume
相比较,--mount
语法更清晰直观。它们之间最大的区别在于 --volume
语法是将所有选项组合在一个字段中,而 --mount
语法
则将他们分开。
[info] 小提示
新手应该尝试使用
--mount
语法,因为该语法使用起来比--volume
更加简单。
如果你需要为卷(volumes
)指定选项,那么只能使用 --mount
。下面就说下它们之间的具体区别已经语法的使用:
--volume
:语法由三个字段组成,字段之间用冒号(:
)分隔。而且字段必须按正确的顺序排列。- 对于命名卷(
volume
),第一个字段是卷的名称,并且该名称在给定主机上是唯一的。对于匿名卷,该字段可以省略。 - 第二个字段是文件或目录挂载到容器中的路径。
- 第三个字段是可选字段。用逗号分隔的选项列表。如:
ro
,后面会具体说明。
- 对于命名卷(
--mount
:语法由多个键值对组成,用键值对之间使用逗号(,
)分隔。每个键值由<key>=<value>
元组组成。与--volume
语法不同,--mount
对键值对的顺序不做要求。type
字段(即mount
的类型)可以是bind
、volume
或tmpfs
。本节主要讨论 volume,因此类型始终是volume
。source
字段。对于显示命名卷,值对应的是 volume 的名称。对于匿名卷,该字段可以省略。另外,source
也可以由src
代替。destination
字段其值是挂载到容器中的路径。另外,destination
也可以由dst
或target
代替。readonly
选项。如果指定,则挂载的容器以只读的方式安装到容器中。volume-opt
选项可以多次指定,它采用由选项名称及其值组成的键值对。
[success] 从外部CSV解析器中转义值
如果你的卷(
volume
)驱动程序接受以逗号(,
)分隔的列表作为选项,则必须从外部 CSV 解析器中转义该值。要转义volume-opt
选项,则需要使用 双引号("
)将其包裹起来,并要使用单引号('
)包裹住整个mount
参数。 例如,local
驱动程序接受将挂载选项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-NAME>
5.1.1.3. --volume 和 --mount 的行为差异
与绑定挂载(bind mounts
)相反,卷(volume
)的所有选项都使用 --mount
或 -v
。
不过,在服务(如:swarm
集群服务)中使用 volumes
时,只能使用 --mount
语法。
5.1.1.4. Docker volumes 命令列表
$ docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
5.1.1.5. 创建、管理 volumes
与绑定挂载(bind mounts
)不同,你可以创建和管理任何容器范围之外的卷(volume
)。
- 创建一个
volume
$ docker volume create my-vol
my-vol
- 列出机器中的
volume
$ docker volume ls
DRIVER VOLUME NAME
local my-vol
- 使用
inspect
命令查看my-vol
volume
信息
$ docker volume inspect my-vol
[
{
"CreatedAt": "2019-02-04T17:39:34+08:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
- 删除
volume
$ docker volume rm my-vol
5.1.1.6. 在容器中使用 volume
在启动容器时绑定某个卷并且该卷尚不存在,Docker 会按需创建。下面示例中展示将一个命名 volume my-vol2
挂载进容器的 /app
目录。
下面示例中使用 devtest
容器, -v
和 --mount
两个展示结果是相同的。注意,在同一容器中不能同时运行它们,只有删除其中一个才能运行另外一个。
在之前先查看当前机器上的 volumes:
$ docker volume ls
DRIVER VOLUME NAME
$ docker run -d \
--name devtest \
--mount type=volume,source=my-vol2,target=/app \
nginx:latest
console:
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
5e6ec7f28fb7: Pull complete
ab804f9bbcbe: Pull complete
052b395f16bc: Pull complete
Digest: sha256:56bcd35e8433343dbae0484ed5b740843dd8bff9479400990f251c13bbb94763
Status: Downloaded newer image for nginx:latest
4d774d44176420027864396ae6cf68a17ad6b326a550bbeb0beee4e38ba610f0
现在,再检查一下机器中的 volumes:
$ docker volume ls
DRIVER VOLUME NAME
local my-vol2
可以看到,my-vol2
volume 按需自动创建了。再使用 inspect
命令查看该 volume 信息:
$ docker volume inspect my-vol2
[
{
"CreatedAt": "2019-02-04T18:42:38+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/my-vol2/_data",
"Name": "my-vol2",
"Options": null,
"Scope": "local"
}
]
现在再使用 inspect
命令检查 devtest
容器的 Mounts
信息:
$ docker inspect devtest
...
"Mounts": [
{
"Type": "volume",
"Name": "my-vol2",
"Source": "/var/lib/docker/volumes/my-vol2/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
]
...
可以看到容器中挂载点的 source
同 my-vol2
volume 的 Mountpoint
。
以上信息展示了 mount 是一个卷,它显示正确的源和目标,并且 mount 是可读写的。
停止容器并移除卷。注意删除卷是一个单独的步骤。
$ docker container stop devtest
$ docker container rm devtest
$ docker volume rm myvol2
5.1.1.7. 在服务中使用 volume
当你运行服务并定义一个 volume,每个服务的任务(即容器)都是使用本地卷(local volume
)。如果使用本地卷驱动程序,则所有容器都不能共享数据。
不过,有些卷驱动程序是支持数据共享的。例如,Docker AWS 和 Docker Azure 都是支持使用 Cloudstor
插件的持久化存储。
下面示例展示启动一个 nginx
服务,该服务有四个实例,每个实例都使用 local
volume,volume 的名字是 my-vol2
。
$ docker service create -d \
--replicas=4 \
--name devtest-service \
--mount type=volume,source=my-vol2,target=/app \
nginx:latest
7m88h14ly1ocbta20suzrmar3
注意,在使用服务之前需要使用
docker swarm init
命令初始化swarm
。
服务成功启动后,可以使用 docker service ps devtest-service
命令验证服务是否启动成功:
$ docker service ps devtest-service
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
kwpvgxntba7i devtest-service.1 nginx:latest localhost.localdomain Running Running 22 seconds ago
on2ta0awxfdq devtest-service.2 nginx:latest localhost.localdomain Running Running 22 seconds ago
bvmjo3vku2cy devtest-service.3 nginx:latest localhost.localdomain Running Running 21 seconds ago
hy8ulezith0h devtest-service.4 nginx:latest localhost.localdomain Running Running 21 seconds ago
现在再查看下 volume 信息:
$ docker volume inspect my-vol2
[
{
"CreatedAt": "2019-02-04T22:59:13+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/my-vol2/_data",
"Name": "my-vol2",
"Options": null,
"Scope": "local"
}
]
最后再停止所有任务、移除服务:
$ docker service rm devtest-service
需要注意,移除服务并不会删除 volume,因为 volume 是在容器的生命周期之外,删除 volume 需要单独的步骤,如下所示:
$ docker volume rm <volume-name>
[danger] 注意
在服务中,
docker service create
命令不支持使用-v
或--volume
语法。想要在服务中挂载 volume,必须使用--mount
。
5.1.1.8. 使用容器填充 volume
像之前一样,如果你启动一个创建新卷(volume
)的容器,并且挂载容器中文件或目录(如之前的 /app
目录)。这个目录中的内容会拷贝到新卷中。该容
器挂载并使用的 volume,如果其他容器也使用该 volume,同样也可以访问预先填充的卷内容。
为了说明这点,可以启动一个 nginx
容器并创建一个新卷 nginx-vol
。将该容器中的目录 /usr/share/nginx/html
挂载到 volume 中,该目录是
Nginx 默认存储 HTML 内容的目录。
$ docker run -d \
--name=nginxtest \
--mount type=volume,source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
6abdcee3be79e012e98eb075346cb1fcd1028c85348d5696c0b3eb375b328d7e
$ docker volume inspect nginx-vol
[
{
"CreatedAt": "2019-02-05T09:35:19+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/nginx-vol/_data",
"Name": "nginx-vol",
"Options": null,
"Scope": "local"
}
]
列出 volume 中的内容:
$ ls /var/lib/docker/volumes/nginx-vol/_data
50x.html index.html
可以看到容器中的 Nginx 的 HTML 内容被拷贝到了卷中,实现了 volume 内容预先填充。
运行完成后,使用以下命令清楚内容和卷。注意:删除卷是一个单独的步骤。
$ docker container stop nginxtest
$ docker container rm nginxtest
$ docker volume rm nginx-vol
5.1.1.9. 使用只读 volume
对于一些部署应用,容器需要写入绑定挂载,以便将更改传播回 Docker 主机。不过,在其他时候,一些容器可能只需要对数据具有读权限即可。之前说过,多个容 器可以同时只挂载到一个 volume。这些容器可以是 读写 和 只读 volume。
下面示例中只是修改了之前的示例,但目录安装为只读卷。对于 -v
,需要在容器内的挂载点之后将 ro
添加到(默认为空)选项列表。如果存在多个选项,需
要使用逗号(,
)分隔。
$ docker run -d \
--name=nginxtest \
--mount type=volume,source=nginx-vol,destination=/usr/share/nginx/html,readonly \
nginx:latest
201d2add788f000ce7ff78aaa3b76b7dc5348c795fa96e05f076bcbe8a0e6387
使用 docker inspect nginxtest
进行验证挂载被正确创建。并且,容器对卷的权限为只读权限,可以查看 Mounts
栏:
$ docker inspect nginxtest
...
"Mounts": [
{
"Type": "volume",
"Name": "nginx-vol",
"Source": "/var/lib/docker/volumes/nginx-vol/_data",
"Destination": "/usr/share/nginx/html",
"Driver": "local",
"Mode": "z",
"RW": false,
"Propagation": ""
}
]
...
最后,停止并删除容器以及 volume:
$ docker container stop nginxtest
$ docker container rm nginxtest
$ docker volume rm nginx-vol
5.1.1.10. 多机器之间数据分享
当需要构建一个具有容错性的应用时,你可能需要配置一个具有多个实例的服务,并且这些实例之间能够访问相同的文件。
有以下几种方式可以实现如上图所示的应用部署。
- 为应用程序增加逻辑,以将文件存储在云对象存储系统上(如:
Amazon S3
)。 - 使用支持将文件写入诸如 NFS 或 Amazon S3 等外部存储系统的卷(
volumes
)驱动程序的卷。
卷驱动程序允许你从应用程序中抽象底层存储系统。例如,如果你的服务使用 NFS
驱动程序的卷,那你可以使用其他驱动程序更新服务。譬如在云存储数据,而
无需更改应用程序的逻辑。
5.1.1.11. 使用卷驱动程序
当你使用 docker volume create
命令创建卷(volumes
)或者当你运行一个容器并且绑定还未创建的卷时,你可以指定一个卷驱动程序。下面的示例中使
用的是 vieux/sshfs
卷驱动程序,开始创建一个独立卷,然后当启动容器时创建一个新卷。
初始设置
假设你有两个节点,第一个节点是 Docker 主机,可以使用 SSH 连接到第二个节点。
在 Docker 主机上,安装 vieux/sshfs
插件:
$ docker plugin install --grant-all-permissions vieux/sshfs
示例:
$ docker plugin install --grant-all-permissions vieux/sshfs
latest: Pulling from vieux/sshfs
52d435ada6a4: Download complete
Digest: sha256:1d3c3e42c12138da5ef7873b97f7f32cf99fb6edde75fa4f0bcf9ed277855811
Status: Downloaded newer image for vieux/sshfs:latest
Installed plugin vieux/sshfs
$ docker plugin ls
ID NAME DESCRIPTION ENABLED
98bfb9819e91 vieux/sshfs:latest sshFS plugin for Docker true
使用卷驱动程序创建卷
该示例需要指定 SSH 密码,不过如果两台主机之间有分享 keys 配置。则可以免密码登录。每个卷驱动程序都有 0 或多个参数配置,每个配置需要使用 -o
参数进行指定,命令如下:
$ docker volume create --driver vieux/sshfs \
-o sshcmd=test@node2:/home/test \
-o password=testpassword \
sshvolume
笔者使用的另一台主机是 192.168.1.14
,使用 root
用户进行登录:
$ docker volume create --driver vieux/sshfs \
-o [email protected]:/root \
-o password=MinGRn97 \
sshvolume
sshvolume
命令执行完成后看到创建了一个卷 sshvolume
。可以在机器上执行卷监测命令查看卷信息:
$ docker volume inspect sshvolume
[
{
"CreatedAt": "0001-01-01T00:00:00Z",
"Driver": "vieux/sshfs:latest",
"Labels": {},
"Mountpoint": "/mnt/volumes/1124f8042e7a222fb71202f4f1243f13",
"Name": "sshvolume",
"Options": {
"password": "MinGRn97",
"sshcmd": "[email protected]:/root"
},
"Scope": "local"
}
]
可以看到,在卷的 Options
中有你配置的 SSH 登录信息。
启动一个使用卷驱动程序创建卷的容器
同样的,你需要使用 SSH 登录到另一台主机,如果主机之间已经共享 keys
则不需要设置登录密码了。每个卷驱动程序都有 0 个或多个配置参数。**如果卷驱动程序要求你传递选项,则必须使用 --mount
而不是 -v
选项。
$ docker run -d \
--name sshfs-container \
--volume-driver vieux/sshfs \
--mount type=volume,src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \
nginx:latest
同样的,笔者使用 192.168.1.14
主机的 root
用户。所以命令如下:
$ docker run -d \
--name sshfs-container \
--volume-driver vieux/sshfs \
--mount type=volume,src=sshvolume,target=/app,[email protected]:/root,volume-opt=password=MinGRn97 \
nginx:latest
示例如下:
$ docker run -d \
--name sshfs-container \
--volume-driver vieux/sshfs \
--mount type=volume,src=sshvolume,target=/app,[email protected]:/root,volume-opt=password=MinGRn97 \
nginx:latest
5.1.1.12. 卷备份、恢复和迁移
卷对备份、还原和迁移很有用。使用 --volumes-from
标志创建一个安装该卷的新容器。
备份容器
- 启动一个新容器从
dbstore
容器装入卷 - 将本机主机目录挂载为
/backup
- 将包含
dndate
卷内容的命令传递到/backup
目录中的backup.tar
文件
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
当命令执行完成后容器将会停止,并且留下了 dbdata
卷的备份。
从备份中还原容器
就刚备份的 dbtate
而言,我们可以还原数据导到同一个容器或者恢复到另一个容器中。
例如,创一个新容器 dbstore2
:
$ docker run -v /dbdata --name dbstore2 ubuntu /bin/bash
然后解压缩新容器的数据卷中的备份文件:
$ docker run --rm --volumes-from dbstore2 -v $(pwd):backup ubuntu bash -c "cd /dbdata && tar xvf /backup/backup.tar --strip 1"
你可以使用上述技术使用首选工具自动执行备份,迁移和还原测试。
5.1.1.13. 删除卷
当 docker 容器删除时,volume 就会持久化保存,并不会删除。卷有如下两种类型:
- 命名卷 在容器外有一个特定的源表单,如
awesome:/bar
。 - 匿名卷 没有特定的源,所以当容器删除时,指示 Docker Engine 守护程序删除他们。
删除匿名卷
使用 --rm
参数会自动删除匿名卷。例如,有一个匿名卷 /foo
,当你的容器删除时,Docker Engine 会自动删除 /foo
而不是 awesome
卷:
$ docker run --rm -v /foo -v awesome:bar busybox top
删除所有卷
要删除所有未使用的卷并释放空间使用如下命令即可:
$ docker volume prune
5.1.1.14. 总结
使用 volume 可以有效的实现数据持久化。虽然 bind mount
也具有同样的功能,但是 bind mount
具有局限性和安全性问题。索引,volume 是推荐使
用的容器数据持久化的方案。
在容器中使用 volume 有两种方式:--volume
和 --mount
。相比较而言,后者语法更加清晰简单益于理解,另外在 swarm
服务中不支持使用 --volume
语法。所以,在实际应用中,还是以 --mount
语法为主。