volume

为了持久化由容器产生和使用的数据,优先选择卷来达到这个目的。bind mounts依赖主机操作系统的目录结构,而卷完全由容器进行管理。与bind mounts相比,卷有以下几点优势:

  • 备份或迁移更加方便。

  • 可以通过Docker CLI或Docker API对卷进行管理。

  • 卷可以同时用于Linux容器和Windows容器。

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

  • 卷驱动可以让我们在远端主机或云服务商存储卷,来加密卷中的内容或添加其他的功能。

  • 新的卷中的内容可以被容器预先处理。

  • 在Mac或Windows主机上,Docker Desktop的卷比起bind mounts拥有更高的性能。

对于持久化容器可写层的数据,卷通常是更好的一种方案。它不会增加容器的大小,并且卷中的内容与容器的生命周期是分开的。

如果容器生成是非持久化的数据,我们可以考虑使用tmfs mount来避免将数据永久性地存储在某个地方,并且通过避免对容器的写入层进行写入而提升容器的性能。

卷使用rprivate绑定传播(bind propagation),并且对于卷,绑定传播(bind propagation)无法配置。

选择-v还是--mount?

通常来说,--mount更加的明确和详细。最大的不同是-v语法将所有的选项组合在一起放入一个字段,而--mount语法则是分开的。

如果我们需要指定卷驱动选项,那么必须使用--mount。

-v--volume: 由三个字段组成,通过:分隔开。字段必须按照正确的顺序组合。

  • 如果是指定一个命名卷,第一个字段是卷的名称,并且这个名称在主机上是独一无二的。如果是匿 名卷,则第一个字段可以省略。

  • 第二个字段指定容器上被挂载的文件或路径。

  • 第三个字段是可选的,是一个逗号分隔的选项列表,例如ro

--mount:由多个键值对组成,通过逗号进行分隔,每一个都是一个键值对组合。--mount语法比-v--volume更详细,但是键的顺序没有那么重要,并且它所对应的值更容易被理解。

  • 挂载的类型(type)可以是bindvolume或者tmpfs

  • 挂载的源(source)。对于命名卷,它是卷的名称。对于匿名卷,这个字段可以被省略。可以通过sourcesrc两种形式来指定源。

  • 挂载的目的地(destination)使用容器中被挂载的文件或目录的路径作为值。可以通过destinationdsttarget三种形式来指定目的地。

  • readonly选项如果存在,它会将bind mount以只读的形式挂载到容器中。

  • volume-opt选项可以多次使用,使用选项名和值的键值对形式。

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

bind mount相反,卷即可以使用-v也可以使用--mount。当使用带有服务的卷时,只支持--mount

创建和管理卷

bind mount不同,卷可以在容器外部被创建和管理。

$ docker volume create my-vol

列出卷:

$ docker volume ls

local               my-vol

检查一个卷:

$ docker volume inspect my-vol
[
    {
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
        "Name": "my-vol",
        "Options": {},
        "Scope": "local"
    }
]

移除一个卷:

$ docker volume rm my-vol

启动一个带有卷的容器

我们在启动一个带有卷的容器时,如果卷还不存在,Docker会为我们创建一个卷。下面的实例将myvol2挂载到了容器中的/app/目录。

使用--mount的形式:

$ docker run -d \
  --name devtest \
  --mount source=myvol2,target=/app \
  nginx:latest

使用-v的形式:

$ docker run -d \
  --name devtest \
  -v myvol2:/app \
  nginx:latest

使用docker inspect devtest来检查一个卷是否挂载正确。查看Mounts部分:

"Mounts": [
    {
        "Type": "volume",
        "Name": "myvol2",
        "Source": "/var/lib/docker/volumes/myvol2/_data",
        "Destination": "/app",
        "Driver": "local",
        "Mode": "",
        "RW": true,
        "Propagation": ""
    }
],

上面的信息显示挂载的是一个卷,显示了正确的源和目的地,并且挂载是read-write的。

停止一个容器,然后移除卷。这里需要注意移除一个卷的正确步骤!

$ docker container stop devtest

$ docker container rm devtest

$ docker volume rm myvol2

在docker-compose中使用卷

带有卷的docker-compose服务看起来应该是下面这样的:

version: "3.8"
services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:

在我们第一次调用docker-compose up时,卷会被创建。后续的调用会重复使用相同的卷。

一个卷也可以直接在docker-compose外部通过docker volume create创建,然后在docker-compose.yml中引用:

version: "3.8"
services:
  frontend:
    image: node:lts
    volumes:
      - myapp:/home/node/app
volumes:
  myapp:
    external: true

启动一个带有卷的服务

当我们启动一个服务和定义一个卷时,每一个服务容器使用自己的本地卷。如果使用本地(local)卷驱动, 容器之前无法共享存储,但是有一些的卷驱动支持共享存储。

下面的示例启动一个带有四个副本的nginx服务,每一个服务都使用名字叫myvol2的本地卷。

$ docker service create -d \
  --replicas=4 \
  --name devtest-service \
  --mount source=myvol2,target=/app \
  nginx:latest

使用docker service ps ps devtest-service来检查正在运行的服务:

$ 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

移除服务:

$ docker service rm devtest-service

针对服务语法的不同

docker service create命令不支持-v--volume。当挂载一个卷到服务的容器时,必须使用--mount

使用容器填充一个卷

当启动一个使用新创建的卷的容器时,被挂载的目录里面包含了容器的文件和一些目录(例如上面所说的/app/),被挂载目录的内容会被拷贝到卷中。容器会进一步挂载和使用卷,其他使用该卷的容器也可以访问这些预填充的内容。

为了解释上面所说的,下面的示例启动了一个nginx容器,并且使用容器中/usr/share/nginx/html目录中的内容填充了新的nginx-vol卷。该目录默认保存nginx的默认HTML内容。

使用--mount:

$ docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html \
  nginx:latest

使用-v:

$ docker run -d \
  --name=nginxtest \
  -v nginx-vol:/usr/share/nginx/html \
  nginx:latest

运行完上面的示例以后,依然按照正确的步骤删除卷。

$ docker container stop nginxtest

$ docker container rm nginxtest

$ docker volume rm nginx-vol

使用一个只读卷

对于一些应用开发,容器需要对bind mount进行写入,以便写入以后产生的更改可以传播到容器主机。在其他时候,容器只需要访问这些数据。在同一时间,多个容器可以挂载到相同的卷,对于一些容器可以使用read-write形式挂载而其他的容器使用read-only形式挂载。

下面的示例通过在前面使用过的命令选项列表中添加ro,来使卷作为只读卷挂载到容器中的指定路径。

$ docker run -d \
  --name=nginxtest \
  --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
  nginx:latest

通过docker inspect nginxtest | jq '.[] | .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": ""
    }

停止和移除容器,然后移除卷。卷删除必须按照单独有序的步骤执行。

$ docker container stop nginxtest

$ docker container rm nginxtest

$ docker volume rm nginx-vol

在主机之间共享数据

当构建零容错的应用时,我们可能需要为服务配置多个副本来访问相同的文件。

当开发应用时,我们有多种方式来实现数据共享。一个是在我们的应用中添加代码逻辑,使文件存储在像AWS S3这样的存储系统中。另外一种方式是通过一个支持将文件写入到外部存储系统(例如NFS或AWS S3)的驱动来创建一个卷。

使用一个卷驱动

当我们使用docker volume create或当我们启动一个使用还没有创建的卷的容器时,我们可以指定一个卷驱动。下面的示例使用vieus/sshfs卷驱动。

初始化设置

下面示例假设我们有两个节点,第一个节点可以使用ssh连接到第二个节点。

在docker主机上安装vieux/sshfs插件:

$ docker plugin install --grant-all-permissions vieux/sshfs

使用卷驱动创建一个卷

下面示例指定了一个SSH密码,但是如果两个主机共享配置的key,我们可以省略密码。每个卷驱动可以有零个或多个可配置的选项,每一个选项使用-o指定。·

$ docker volume create --driver vieux/sshfs \
  -o sshcmd=test@node2:/home/test \
  -o password=testpassword \
  sshvolume

启动一个使用通过卷驱动创建的卷的容器

下面示例指定了一个SSH密码,但是如果两个主机共享配置的key,我们可以省略密码。每个卷驱动可以有零个或多个可配置的选项。如果卷驱动需要我们传递选项,我们必须使用--mount来挂载卷而不是-v

$ docker run -d \
  --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

创建一个创建NFS卷的服务

下面示例展示了创建一个服务时如何创建一个NFS卷。示例使用10.0.0.10作为NFS服务器地址,而/var/docker-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

备份,恢复或迁移数据卷

卷对于备份,恢复和迁移非常有用。使用--volumes-from来创建一个挂载到卷上面的新容器。

备份一个容器

例如,创建一个dbstore的容器:

$ docker run -v /dbdata --name dbstore ubuntu /bin/bash

下面的命令中,我们:

  • 启动一个新的容器,然后挂载到dbstore容器的卷上面。

  • 挂载一个本地主机目录/backup。

  • 传递一个命令到容器中将dbdata卷中的内容打包成backup.tar文件并保存在/dbdata中。

$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata

从备份中恢复容器

通过上面备份的数据,我们可以在相同的容器中或者你在其他地方创建的容器中恢复它。

例如,创建一个名为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"

移除卷

一个Docker数据卷在容器被删除以后依然存在。有两种类型的卷需要了解:

  • 命名卷有一个来自容器外部的指定的源,例如awesome:/bar。

  • 匿名卷没有指定的源,因此当容器被删除时,需要通过命令让Docker引擎去移除它们。

移除匿名卷

如果需要自动删除匿名卷,可以使用-rm选项。例如,下面的命令创建了一个匿名/foo卷,当容器被删除时,Docker引擎会移除/foo卷,但是不会移除awesome卷。

$ docker run --rm -v /foo -v awesome:/bar busybox top

移除所有卷

移除所有卷来释放空间,我们可以使用下面的命令:

$ docker volume prune

Last updated