volume的主要作用是数据持久化及数据共享。当删除容器的时候,容器内产生的数据也会删除,但是生产的数据可能不想被删除,比如数据库容器产生的数据,这时可以通过volume实现将数据保存在本地机器而不保存在容器里。
实现volume的2种方式
- 无指定本地目录,这时默认本地目录在
/var/lib/docker/volumes
下创建。这种方式可以通过dockerfile/docker-compose.yaml/docker run -v volume-name
实现 - 指定本地目录,本地目录就是指定的本地目录。这种方式只可以通过
docker run -v absolute-local-dir:container-dir
或者docker run -v volume-name:container-dir
实现数据共享
如果要授权一个容器访问另一个容器的Volume,我们可以使用-volumes-from参数来执行docker run。$ docker run -it -h NEWCONTAINER --volumes-from container-test debian /bin/bash
root@NEWCONTAINER:/# ls /data
test-file
root@NEWCONTAINER:/#
数据容器
常见的使用场景是使用纯数据容器来持久化数据库、配置文件或者数据文件等。官方的文档上有详细的解释。例如:$ docker run --name dbdata postgres echo "Data-only container for postgres"
该命令将会创建一个已经包含在Dockerfile里定义过Volume的postgres镜像,运行echo命令然后退出。当我们运行docker ps命令时,echo可以帮助我们识别某镜像的用途。我们可以用-volumes-from命令来识别其它容器的Volume:$ docker run -d --volumes-from dbdata --name db1 postgres
使用数据容器的两个注意点:
- 不要运行数据容器,这纯粹是在浪费资源。
- 不要为了数据容器而使用“最小的镜像”,如busybox或scratch,只使用数据库镜像本身就可以了。你已经拥有该镜像,所以并不需要占用额外的空间。
备份
如果你在用数据容器,那做备份是相当容易的:$ docker run --rm --volumes-from dbdata -v $(pwd):/backup debian tar cvf /backup/backup.tar /var/lib/postgresql/data
该示例应该会将Volume里所有的东西压缩为一个tar包(官方的postgres Dockerfile在/var/lib/postgresql/data目录下定义了一个Volume)权限与许可
通常你需要设置Volume的权限或者为Volume初始化一些默认数据或者配置文件。要注意的关键点是,在Dockerfile的VOLUME指令后的任何东西都不能改变该Volume,比如:
FROM debian:wheezy
RUN useradd foo
VOLUME /data
RUN touch /data/x
RUN chown -R foo:foo /data
该Docker file不能按预期那样运行,我们本来希望touch命令在镜像的文件系统上运行,但是实际上它是在一个临时容器的Volume上运行。如下所示:
FROM debian:wheezy
RUN useradd foo
RUN mkdir /data && touch /data/x
RUN chown -R foo:foo /data
VOLUME /data
Docker可以将镜像中Volume下的文件挂载到Volume下,并设置正确的权限。如果你指定Volume的主机目录将不会出现这种情况。 如果你没有通过RUN指令设置权限,那么你就需要在容器启动时使用CMD或ENTRYPOINT指令来执行(译者注:CMD指令用于指定一个容器启动时要运行的命令,与RUN类似,只是RUN是镜像在构建时要运行的命令)。
删除volumes
这个功能可能会更加重要,如果你已经使用docker rm来删除你的容器,那可能有很多的孤立的Volume仍在占用着空间。 Volume只有在下列情况下才能被删除:
该容器是用docker rm -v命令来删除的(-v是必不可少的)。
docker run中使用了—rm参数
即使用以上两种命令,也只能删除没有容器连接的Volume。连接到用户指定主机目录的Volume永远不会被docker删除。 除非你已经很小心的,总是像这样来运行容器,否则你将会在/var/lib/docker/volumes目录下得到一些僵尸文件和目录,并且还不容易说出它们到底代表什么。
docker-compose.yaml volume 语法
volumes:
# Just specify a path and let the Engine create a volume
- /var/lib/mysql
# Specify an absolute path mapping
- /opt/data:/var/lib/mysql
# Path on the host, relative to the Compose file
- ./cache:/tmp/cache
# User-relative path
- ~/configs:/etc/configs/:ro
# Named volume
- datavolume:/var/lib/mysql
挂载嵌套目录
Mixing named volumes and bind mounting in Docker?
使用 volume driver
当使用 docker volume create 创建卷或启动尚未创建卷的容器的时候,可以指定卷驱动程序。
下面这个例子,首先创建独立卷时使用 volume driver,然后在启动创建新卷的容器时使用 volume driver。
初始设置
这个例子假定你有 2 个节点,第一个是 docker 主机,可以使用 SSH 连接到第二个节点。
在 docker 主机上安装 vieux/sshfx 插件:
$ docker plugin install --grant-all-permissions vieux/sshfs
使用 volume driver 创建卷
下面指定了一个 SSH 密码,但如果 2 台主机共享密钥已配置,则可以省略密码。每个 volume driver 可以有多个配置选项,使用 -o 标志指定。
$ docker volume create --driver vieux/sshfs \
-o sshcmd=test@node2:/home/test \
-o password=testpassword \
sshvolume
创建容器时使用 volume driver
这里需要注意的是,如果需要在命令中使用选项,则必须使用 —mount,而不是 -v。
$ docker run -d \
-it \
--name sshfs-container \
--volume-driver vieux/sshfs \
--mount src=sshvolume,target=/app,volume-opt=sshcmd=test@node2:/home/test,volume-opt=password=testpassword \
nginx:latest
bind mount 方式
通过 bind mount 方式,你可以将你主机上的任何文件或目录(绝对路径)挂载到容器中。
挂载的文件或目录可以被任何进程修改,因此有时候容器中修改了该文件或目录将会影响其他进程。
如果挂载主机的文件或目录不存在将会自动创建。
使用该方式不能通过 docker volume 管理,推荐使用 volume 方式。
相关用例
bind mounts,一般情况在如下方式使用:
- 从主机共享配置文件到容器。默认情况,docker 会绑定类似 /etc/resolv.conf 的文件用于 DNS 的解析。
- 主机与容器共享源代码或构建工具。如,你可以将 Maven target/ 挂载到容器中,并且每次主机上构建 Maven 项目时,容器都可以访问重建的构件。
- 主机的文件或目录结构与容器所需的一致时。
如果将空文件或目录挂载到容器,容器中的该目录又有文件,那么,这些文件将会被复制到主机上的目录中。如果将非空的文件或目录挂载到容器,容器中的该目录也有文件,那么,容器中的文件将会被隐藏。
使用方式
-v/-mount 标志
最初,-v 和 -volume 用于独立的容器,—mount 用于 swarm server。但 docker 17.06 之后,也可以使用 —mount。两者的区别在于,-v 将所有选项组合在一个字段中,—mount 则将它们分开。
新用户应使用 —mount 语法,老用户推荐使用 —mount。
-v 或 —volume:由(:)分隔的字段组成。这些字段是有顺序的。
第一个字段,主机上的文件或目录。
第二个字段,容器中的文件或目录。
第三个字段,可选,且用逗号分隔,如:ro,consistent,delegated,cached,z 和 Z。
—mount:由多个键值对组成,由逗号分隔,每一个由
- type,可以是 bind,volume,tmpfs。
- source,主机上的文件或目录的路径。可能用 src,source 指定。
- destination,容器中的文件或目录的路径。可能用 destination,dst,target 指定。
- readonly,如果存在,将更改 Propagation,可以是一个 rprivate。
- consistency,如果存在,可以是 consistent,delegated 或 cached,只在 Mac 版有效。
- —mount 标志不支持 z 或 Z 修改 selinux。
-v 和 –mount 的差异
使用 -v 和 —volume 绑定主机不存在的文件或目录,将会自动创建。始终创建的是一个目录。
使用 —mount 绑定主机上不存在的文件或目录,则不会自动创建,会产生一个错误。
使用 bind mount 启动容器
主机上的目录 source/target,容器的目录 /app/。$(pwd) 将使用当前目录:
# 只读方式:--mount type=bind,source="$(pwd)"/target,target=/app,readonly
$ docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app \
nginx:latest
# 只读方式:-v "$(pwd)"/target:/app:ro
$ docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app \
nginx:latest
用 docker inspect devtest 可以查看相关信息,查看 Mounts 部分:
"Mounts": [
{
"Type": "bind",
"Source": "/tmp/source/target",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
这些信息表明了这是一个 bind 挂载,源路径和目的路径,并且是可读写的,且 Propagation 设置为 rprivate。
停止容器:
$ docker container stop devtest
$ docker container rm devtest
配置 Propagation
Propagation 的在 bind mount 和 volume 中默认为 rprivate。它只能在 bind mount 配置,并且只能在 Linux 主机上配置。这是一个高级选项,许多用户不需要配置它。
Propagation 是指在给定的挂载卷或命名卷中创建的挂载是否可以传播到该挂载的副本。考虑一个挂载点 /mnt,它被挂载在 /tmp。传播设置控制是否挂载 /tmp/a 也可用 /mnt/a.每个 Propagation 设置都有一个递归对应点。在递归的情况下,考虑 /tml/a 被挂载为 /foo。传播设置控制是否 /mnt/a 或 /tmp/a 将存在。
Propagation 设置 描述
- shared 原始安装的子安装会暴露给副本安装,并且副本安装的子安装也会传播到原始安装。
- slave 类似于共享的安装,但仅在一个方向上。如果原始安装显示一个子安装,副本安装可以看到它。但是,如果副本安装公开了子安装,则原始安装无法看到它。
- private 这座山是私人的。其中的子安装不会暴露给副本安装,并且副安装的子安装不会暴露给原始安装。
- rshared 与共享相同,但是传播也扩展到嵌套在任何原始或副本安装点内的挂载点。
- rslave 与从属设备相同,但传播也延伸到嵌套在任何原始或副本安装点内的挂载点。
- rprivate 默认。与私有相同,这意味着在原始或副本安装点内的任何位置都不会有安装点向任一方向传播。
在可以在安装点上设置绑定传播之前,主机文件系统需要已经支持绑定传播。有关绑定传播的更多信息,请参阅 共享子树 的 Linux内核文档。
以下示例将 target/ 目录装载到容器中两次,第二个装入设置 ro 选项和 rslave 绑定传播选项。
在 —mount 和 -v 实例有同样的结果。
$ 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
$ docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app \
-v "$(pwd)"/target:/app2:ro,rslave \
nginx:latest