前言
翻译一下Docker 关于数据管理方面的部分知识。
生产环境中使用Docker的过程中,往往需要对数据进行持久化,或者需要在多个容器之间进行数据共享,这必然涉及容器的数据管理操作。
Docker有两个选项供容器在主机上存储文件,这样即使容器停止后,文件也会持续存在:volumes(卷) 和 bind mounts(绑定挂载)。
不同的数据挂载方式
无论哪种类型的挂载,数据在容器内看起来都是一样的。它在容器的文件系统中以目录或单个文件的形式显示。
要直观地了解volumes、bind mounts和tmpfs mounts之间的区别,可以考虑数据在Docker主机上的存放位置。
-
Volumes存储在主机文件系统的一部分,由Docker管理(Linux上为/var/lib/docker/volumes/)。非Docker进程不应修改文件系统的这一部分。Volumes是在Docker中保存数据的最佳方式。
-
Bind mounts可以存储在主机系统的任何地方。它们甚至可能是重要的系统文件或目录。Docker主机或Docker容器上的非Docker进程可以在任何时候修改它们。
-
tmpfs mounts只存储在主机系统的内存中,不会被写入主机系统的文件系统。
使用 Volumes
Volumes是保存由Docker容器产生和使用的数据的首选机制。Bind mounts依赖于主机的目录结构和操作系统,而Volumes则完全由Docker管理。Volumes比Bind mounts有几个优势。
-
Volumes比Bind mounts更容易备份或迁移。
-
你可以使用Docker CLI命令或Docker API来管理Volumes。
-
Volumes在Linux和Windows容器上都可以工作。
-
Volumes可以更安全地在多个容器之间共享。
-
Volumes的驱动可以让你在远程主机或云供应商上存储Volumes,对Volumes的内容进行加密,或添加其他功能。
-
新的Volumes可以由容器预先填充其内容。
-
Docker桌面上的Volumes比来自Mac和Windows主机的Bind mounts的性能高得多。
此外,Volumes通常是比在容器的可写层中持久化数据更好的选择,因为Volumes不会增加使用它的容器的大小,而且Volumes的内容存在于特定容器的生命周期之外。
创建和管理Volumes
创建一个Volume
$ docker volume create my-vol
列出Volumes
$ docker volume ls
查看Volume信息
$ docker volume inspect my-vol
删除Volume
$ docker volume rm my-vol
启动一个带Volume的容器
如果你用一个尚不存在的卷来启动一个容器,Docker会为你创建这个卷。下面的例子将卷myvol2挂载到容器的/app/中。
$ docker run -d \
--name devtest \
--mount source=myvol2,target=/app \
nginx:latest
# 方式一:使用 --mount
$ docker run -d \
--name devtest \
-v myvol2:/app \
nginx:latest
# 方式二:使用 -v
$ docker inspect devtest
# 查看容器信息
挂载点信息:
"Mounts": [
{
"Type": "volume",
"Name": "myvol2",
"Source": "/var/lib/docker/volumes/myvol2/_data",
"Destination": "/app",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
这表明挂载的是一个卷,它显示了正确的源和目标,并且挂载是读写的。
停止容器并删除卷。注意卷的移除是一个单独的步骤。
$ docker container stop devtest
$ docker container rm devtest
$ docker volume rm myvol2
用卷来启动一个服务容器
当你启动一个服务并定义一个卷时,每个服务容器使用它自己的本地卷。如果你使用本地卷驱动,所有的容器都不能共享这些数据,但有些卷驱动确实支持共享存储。Docker for AWS和Docker for Azure都支持使用Cloudstor插件的持久化存储。
下面的例子启动了一个有四个副本的nginx服务,每个副本使用一个名为myvol2的本地卷。
$ docker service create -d \
--replicas=4 \
--name devtest-service \
--mount source=myvol2,target=/app \
nginx:latest
$ docker service ps devtest-service
# 查看服务容器运行起来
$ docker service rm devtest-service
# 删除该服务,这将停止其所有任务。
# 移除服务不会移除该服务所创建的任何卷。卷的移除是一个单独的步骤。
服务容器语法不同点
docker service create 命令不支持
-v
或者--volume
标志,当挂载一个卷到一个服务类型的容器中时,你必须使用-mount
标志。
使用一个容器填充一个卷
如果你启动了一个创建新卷的容器,如上所述,并且该容器在要挂载的目录中拥有文件或目录(比如上面的/app/),那么该目录的内容就会被复制到该卷。然后容器挂载并使用该卷,其他使用该卷的容器也可以访问预先填充的内容。
为了说明这一点,本例启动了一个nginx容器,并将容器的/usr/share/nginx/html目录的内容填充到新的卷nginx-vol中,该目录是Nginx存储默认HTML内容的地方。
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html \
nginx:latest
# 方式一:使用 --mount
$ docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html \
nginx:latest
# 方式二:使用 -v
运行以下命令来清理容器和卷。注意卷的移除是一个单独的步骤。
$ docker container stop nginxtest
$ docker container rm nginxtest
$ docker volume rm nginx-vol
使用只读卷
对于一些开发应用,容器需要写进绑定挂载,以便将变化传播回Docker主机。在其他时候,容器只需要对数据进行读取访问。请记住,多个容器可以挂载同一个卷,它可以对其中一些容器进行读写挂载,而同时对其他容器进行只读挂载。
这个例子修改了上面的例子,但是把目录挂载为只读卷,方法是在容器内的挂载点后面的选项(默认为空)列表中加入ro。如果有多个选项,请用逗号将它们分开。
$ docker run -d \
--name=nginxtest \
--mount source=nginx-vol,destination=/usr/share/nginx/html,readonly \
nginx:latest
# 方式一:使用 --mount
$ docker run -d \
--name=nginxtest \
-v nginx-vol:/usr/share/nginx/html:ro \
nginx:latest
# 方式二:使用 -v
$ 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": "",
"RW": false,
"Propagation": ""
}
],
运行以下命令来清理容器和卷。注意卷的移除是一个单独的步骤。
$ docker container stop nginxtest
$ docker container rm nginxtest
$ docker volume rm nginx-vol
备份、恢复或迁移数据卷
卷对于备份、恢复和迁移很有用。使用 --volumes-from 标志来创建一个新的容器,挂载该卷。
备份一个卷
例如,创建一个名为dbstore的新容器。
$ docker run -v /dbdata --name dbstore ubuntu /bin/bash
然后在下一个命令中:
-
启动一个新的容器,并从dbstore容器中挂载该卷
-
挂载一个本地主机目录作为/backup
-
传递一条命令,将dbdata卷的内容剪切到我们的/backup目录下的backup.tar文件中。
$ docker run --rm --volumes-from dbstore -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
当命令完成,容器停止时,我们就留下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
Note:
如果另一个容器用 --volumes-from 绑定卷,卷的定义会被复制,匿名卷也会在第一个容器被移除后保持不变。
删除所有卷
要删除所有未使用的卷,释放空间:
$ docker volume prune
使用bind mounts
用绑定挂载启动一个容器
$ docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app \
nginx:latest
# 方式一:使用 --mount
$ docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app \
nginx:latest
# 方式二:使用 -v
$ docker inspect nginxtest
# 查看容器信息
挂载点信息:
"Mounts": [
{
"Type": "bind",
"Source": "/tmp/source/target",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
挂载到容器上一个非空的目录中
如果你绑定挂载到容器上的一个非空目录,该目录的现有内容会被绑定挂载所掩盖。这可能是有益的,例如当你想测试你的应用程序的一个新版本而不建立一个新的镜像。然而,它也可能是令人惊讶的,这种行为与docker卷的行为不同。
$ docker run -d \
-it \
--nme broken-container \
--mount type=bind,source=/tmp,target=/usr \
nginx:latest
# 方式一:使用 --mount
$ docker run -d \
-it \
--name broken-container \
-v /tmp:/usr \
nginx:latest
# 方式二:使用 -v
使用只读的绑定挂载
对于某些开发应用,容器需要写入绑定挂载,这样变化就会传播到Docker主机上。在其他时候,容器只需要读取权限。
$ docker run -d \
-it \
--name devtest \
--mount type=bind,source="$(pwd)"/target,target=/app,readonly \
nginx:latest
# 方式一:使用 --mount
$ docker run -d \
-it \
--name devtest \
-v "$(pwd)"/target:/app:ro \
nginx:latest
# 方式二:使用 -v
$ docker inspect nginxtest
# 查看容器信息
挂载点信息:
"Mounts": [
{
"Type": "bind",
"Source": "/tmp/source/target",
"Destination": "/app",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
],
总结
Docker 数据管理推荐使用volume,绑定挂载常用于应用开发环境。
Docker 数据管理非常重要。
参考链接: