文章目录
- Docker:存储
-
- [Docker 的两类存储资源](#Docker 的两类存储资源)
-
- [1. storage driver](#1. storage driver)
- [2. data volume](#2. data volume)
- [Bind Mount](#Bind Mount)
- [Docker Managed Volume](#Docker Managed Volume)
- 如何共享数据?
-
- [容器与 host 共享数据](#容器与 host 共享数据)
- 容器之间共享数据
- [Data-packed Volume Container](#Data-packed Volume Container)
- [Volume 生命周期管理](#Volume 生命周期管理)
Docker:存储
Docker 的两类存储资源
想象一下,你的容器就像一个临时小房间,里面可以放东西,但一旦房间拆除(容器删除),里面的东西就都没了。为此 Docker 为容器提供了两种存放数据的资源:
- 由 storage driver 管理的镜像层和容器层。
- Data Volume。
1. storage driver
在镜像那一章,我们学习了docker镜像的分层结构

容器的最上层是可写的容器层,以及若干只读的镜像层组成,容器的数据存放在这些层中,这样的分层结构最大的特性是 Copy-on-Write(COW):
- 新数据会直接存放在最上面的容器层
- 修改现有数据会从镜像层将数据复制到容器层,修改后的数据直接保存在容器层,镜像层保持不变
- 如果多个层中有命名相同的文件,用户只能看到最上层的文件
可见docker storage driver的优点:
- 分层结构使镜像和容器的创建更加迅速
- 镜像复用节省空间
- 写时不完全复制节省资源
常见的 Storage Driver:overlay2、aufs、devicemapper
对于使用哪一种,这是一个难题,因为没有哪一种driver能适应多有场景,而driver本身也在快速发展和迭代,当然官方给了一个简单的方法,使用linux发行版中默认的storage driver即可
就比如


CentOS Stream 8 用的overlay2,底层文件系统是xfs,各层数据存放在 /var/lib/docker
2. data volume
Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统中
docker有以下特点:
- 持久化:容器删除,数据还在
- 高性能:直接访问宿主机磁盘
- 可共享:多个容器可挂载同一卷
- 可迁移:备份恢复方便
我们具体选择上述的哪一种方式存放数据呢?以下有几个场景
- Database 软件 vs Database 数据
- Web 应用 vs 应用产生的日志
- 数据分析软件 vs input/output 数据
- Apache Server vs 静态 HTML 文件
相信大家会做出这样的选择:
- 前者放在数据层中。因为这部分内容是无状态的,应该作为镜像的一部分。
- 后者放在 Data Volume 中。这是需要持久化的数据,并且应该与镜像分开存放。
那么我们怎么设置volume的容量呢?
因为 volume 实际上是 docker host 文件系统的一部分,所以 volume 的容量取决于文件系统当前未使用的空间,目前还没有方法设置 volume 的容量。
在具体使用上,docker 提供两种类型的volume:bind mount 和 docker managed volume
Bind Mount
bind mount 是将 host 上已存在的目录或文件 mount 到容器。
例如 docker host 上有目录 home/myapp:挂载到 容器的 /app
bash
# 基本语法
docker run -v /宿主机/目录:/容器内目录 镜像名
# 实际例子:把本地的 /home/myapp 挂载到容器的 /app
docker run -d -v /home/myapp:/app nginx
# 常用写法(使用当前目录)
docker run -d -v $(pwd)/html:/usr/share/nginx/html nginx
使用场景
- 开发时共享代码:代码在本地改,容器里立即生效
- 配置文件管理:把配置文件放在宿主机,多个容器共用
- 日志收集:容器日志直接写到宿主机目录
- 方便迁移:比如将 mysql 容器的数据放在 bind mount 里,这样 host 可以方便地备份和迁移数据
当然也有不足的地方:
bind mount 需要指定 host 文件系统的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他 host,而该 host 没有要 mount 的数据或者数据不在相同的路径时,操作会失败。
注意事项
如果宿主机目录不存在,Docker 会自动创建吗?
答案:不会!必须先创建好目录
Docker Managed Volume
docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了
bash
# 方式1:运行容器时创建
docker run -d -v /容器内路径 镜像名
# 例子:Docker 会自动在宿主机创建目录
docker run -d -v /data mysql
# 方式2:先创建数据卷,再使用
docker volume create my_volume # 创建
docker run -d -v my_volume:/data mysql # 使用
# 查看所有数据卷
docker volume ls
# 查看数据卷详细信息
docker volume inspect my_volume
拿httpd容器举个例子
bash
[root@docker ~]# docker run -d -v /usr/local/apache2/htdocs httpd
e191a2a9bafef8eff1730453ef53ed2fb81757f6dcd6d1099ca3f1f9125ede4a
[root@docker ~]# docker inspect e191a2a9ba
"Mounts": [
{
"Type": "volume",
"Name": "db84f285dcc41c890221967cc2ec70a5f7e63a1da117b1613ec230adbb0beabd",
"Source": "/var/lib/docker/volumes/db84f285dcc41c890221967cc2ec70a5f7e63a1da117b1613ec230adbb0beabd/_data",
"Destination": "/usr/local/apache2/htdocs",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
docker inspect 的输出很多,我们感兴趣的是 Mounts 这部分,这里会显示容器当前使用的所有 data volume,包括 bind mount 和 docker managed volume。
可以看见在linux上docker 把数据卷存放到 /var/lib/docker/volumes/ 的目录下面,其中有一个很长名字的目录下的 /_data 就是mount 源
但要明确一点:此时的 /usr/local/apache2/htdocs 已经不再是由 storage driver 管理的层数据了,它已经是一个 data volume。我们可以像 bind mount 一样对数据进行操作,例如更新数据
现在,
简单回顾一下 docker managed volume 的创建过程:
- 容器启动时,简单的告诉 docker "我需要一个 volume 存放数据,帮我 mount 到目录 /abc"。
- docker 在 /var/lib/docker/volumes 中生成一个随机目录作为 mount 源。
- 如果 /abc 已经存在,则将数据复制到 mount 源,
- 将 volume mount 到 /abc
使用场景
- 不像要操心宿主机的路径
- 让docker 完全管理存储
- 需要备份和迁移数据卷
如何共享数据?
数据共享是 volume 的关键特性,本节我们详细讨论通过 volume 如何在容器与 host 之间,容器与容器之间共享数据
容器与 host 共享数据
对于 bind mount 是非常明确的:直接将要共享的目录 mount 到容器
而docker managed volume 就要复杂一些,由于 volume 位于 host 中的目录,是在容器启动时才生成的,所以需要将共享数据拷贝到 volume 中
bash
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
8b0774de494bfae8c70ad4e655903b8e2b2fb11db3356dc1da1e66600f9b1b81
[root@docker ~]# curl 127.0.0.1:80
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>It works! Apache httpd</title>
</head>
<body>
<p>It works!</p>
</body>
</html>
# 将docker host 的文件拷贝到容器中
[root@docker ~]# docker cp ~/index.html 8b0774de49:/usr/local/apache2/htdocs
Successfully copied 2.05kB to 8b0774de49:/usr/local/apache2/htdocs
[root@docker ~]# curl 127.0.0.1:80
hahahahaha
docker cp 可以在容器和 host 之间拷贝数据,当然我们也可以直接通过 Linux 的 cp 命令复制到 /var/lib/docker/volumes/xxx。
容器之间共享数据
方法一:
将共享数据放在 bind mount 中,然后将其 mount 到多个容器
还是以httpd为例,这次我们创建由三个httpd容器组成的集群,让他们使用相同的html文件
将容器的$HOME/htdocs mount 到三个容器
bash
[root@docker ~]# docker run --name web1 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
65612fdd1fd5357b618b2be27536ecf3dd43c29c638dd6c7c9734f36507e4137
[root@docker ~]# docker run --name web2 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
e8af72a7cf1ca173a4ccc3c03162de23727d3a15d73f0501e2b01364ff84f69c
[root@docker ~]# docker run --name web3 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
60fcc8745bb8f491c60d2c75f7f6ed85c096f7b0e4c4d5a67bcdbc1019803e04
查看当前主页面
bash
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
60fcc8745bb8 httpd "httpd-foreground" About a minute ago Up About a minute 0.0.0.0:32771->80/tcp, :::32771->80/tcp web3
e8af72a7cf1c httpd "httpd-foreground" About a minute ago Up About a minute 0.0.0.0:32770->80/tcp, :::32770->80/tcp web2
65612fdd1fd5 httpd "httpd-foreground" About a minute ago Up About a minute 0.0.0.0:32769->80/tcp, :::32769->80/tcp web1
[root@docker ~]# curl 127.0.0.1:32769
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Index of /</title>
</head>
<body>
<h1>Index of /</h1>
<ul></ul>
</body></html>
[root@docker ~]# curl 127.0.0.1:32770
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Index of /</title>
</head>
<body>
<h1>Index of /</h1>
<ul></ul>
</body></html>
[root@docker ~]# curl 127.0.0.1:32771
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Index of /</title>
</head>
<body>
<h1>Index of /</h1>
<ul></ul>
</body></html>
修改volume中主页文件,并查看确定所有容器使用了新的主页
bash
[root@docker ~]# vim htdocs/index.html
updated index page!
[root@docker ~]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
60fcc8745bb8 httpd "httpd-foreground" 2 minutes ago Up 2 minutes 0.0.0.0:32771->80/tcp, :::32771->80/tcp web3
e8af72a7cf1c httpd "httpd-foreground" 2 minutes ago Up 2 minutes 0.0.0.0:32770->80/tcp, :::32770->80/tcp web2
65612fdd1fd5 httpd "httpd-foreground" 2 minutes ago Up 2 minutes 0.0.0.0:32769->80/tcp, :::32769->80/tcp web1
[root@docker ~]# curl 127.0.0.1:32769
updated index page!
[root@docker ~]# curl 127.0.0.1:32770
updated index page!
[root@docker ~]# curl 127.0.0.1:32771
updated index page!
方法二:
volume container 共享数据
volume container 是专门为其他容器提供 volume 的容器。它提供的卷可以是 bind mount,也可以是 docker managed volume。
下面我们创建一个volume container:
bash
[root@docker ~]# docker create --name vc_data \
> -v ~/htdocs/:/usr/local/apache2/htdocs \
> -v /other/userful/tools \
> busybox
014faa96a1cf5eb930e85e98ca765c0c8579703fa3d46b9fb9c88734b2491dce
解释:
- 这里执行docker create 是因为volume container的作用只是提供数据,它本身不需要处于运行状态
- 容器mount 了两个volume:
- 第一个-v 是bind mount ,存放web server静态文件
- 第二个-v 是docker managed volume ,存放一些实用工具(当然现在是空的,这里只是做个示例)
通过docker inspect 可以查看这两个volume
bash
[root@docker ~]# docker inspect vc_data
......
"Mounts": [
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2",
"Source": "/var/lib/docker/volumes/d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2/_data",
"Destination": "/other/userful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
其他容器可以通过 --volumes-from 使用 vc_data 这个 volume container:
bash
[root@docker ~]# docker run --name web1 -d -p 80 --volumes-from vc_data httpd
71cfab54eb669e4ed1d1ef65c4c45a99adcb134ff9344394c20a65994e09e338
[root@docker ~]# docker run --name web2 -d -p 80 --volumes-from vc_data httpd
7a49e071194135dad80adb66775d9c11041e52e85f5176815a8c5c193b2809f2
[root@docker ~]# docker run --name web3 -d -p 80 --volumes-from vc_data httpd
2b970fc89a4134e561c1b1ca88db718fc2d1cda63b78aced3e44495cf95cfb2f
三个 httpd 容器都使用了 vc_data,看看它们现在都有哪些 volume,以 web1 为例:
bash
[root@docker ~]# docker inspect web1
"Mounts": [
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
},
{
"Type": "volume",
"Name": "d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2",
"Source": "/var/lib/docker/volumes/d4a26dd5e2fa266a999ecc612e94b23f5e224e40315d5394e3ebcdbd0a4155e2/_data",
"Destination": "/other/userful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
可以看见,web1容器使用的就是vc_data 的volume,而且连挂载点都是一样的
现在验证一下数据共享的效果:
bash
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2b970fc89a41 httpd "httpd-foreground" 2 minutes ago Up 2 minutes 0.0.0.0:32770->80/tcp, :::32770->80/tcp web3
7a49e0711941 httpd "httpd-foreground" 2 minutes ago Up 2 minutes 0.0.0.0:32769->80/tcp, :::32769->80/tcp web2
71cfab54eb66 httpd "httpd-foreground" 2 minutes ago Up 2 minutes 0.0.0.0:32768->80/tcp, :::32768->80/tcp web1
[root@docker ~]# echo "This content is from a volume container!" > ~/htdocs/index.html
[root@docker ~]# curl 127.0.0.1:32768
This content is from a volume container!
[root@docker ~]# curl 127.0.0.1:32769
This content is from a volume container!
[root@docker ~]# curl 127.0.0.1:32770
This content is from a volume container!
可见,三个容器已经成功共享了 volume container 中的 volume
volume container 的特点:
- 与 bind mount 相比,不必为每一个容器指定 host path,容器只需与 volume container 关联,实现了容器与 host 的解耦
- 使用 volume container 的容器其 mount point 是一致的,有利于配置的规范和标准化,但也带来一定的局限,使用时需要综合考虑
Data-packed Volume Container
创建时就自带初始数据的数据卷容器
volume container 的数据归根到底还是在 host 里,有没有办法将数据完全放到 volume container 中,同时又能与其他容器共享呢?
通常我们称这种容器为 data-packed volume container。其原理是将数据打包到镜像中,然后通过 docker managed volume 共享
怎么创建?
bash
[root@docker ~]# cd dockerfile/
[root@docker dockerfile]# mkdir htdocs
[root@docker dockerfile]# vim Dockerfile
[root@docker dockerfile]# cat Dockerfile
FROM busybox:latest
ADD htdocs /usr/local/apache2/htdocs
VOLUME /usr/local/apache2/htdocs
ADD 将静态文件添加到容器目录 /usr/local/apache2/htdocs。
VOLUME 的作用与 -v 等效,用来创建 docker managed volume,mount point 为 /usr/local/apache2/htdocs,因为这个目录就是 ADD 添加的目录,所以会将已有数据拷贝到 volume 中。
修改文本内容
bash
[root@docker dockerfile]# echo "This content is from a data packed volume container!" > htdocs/index.html
build 新镜像 datapacked:
bash
[root@docker dockerfile]# docker build -t datapacked .

用新镜像创建 data-packed volume container:
bash
[root@docker ~]# docker create --name vc_data datapacked
7406f82b781dc19b4625d81934e5a29e81810349b8fad9891f92c00b5209b801
因为在 Dockerfile 中已经使用了 VOLUME 指令,这里就不需要指定 volume 的 mount point 了。启动 httpd 容器并使用 data-packed volume container:
bash
[root@docker ~]# docker run -d -p 80:80 --volumes-from vc_data httpd
4897f83f470490e76493bae7f9343c09ebdf16abd9dc8bd45119a422b181801d
[root@docker ~]# curl 127.0.0.1:80
This content is from a data packed volume container!
容器能够正确读取 volume 中的数据。data-packed volume container 是自包含的,不依赖 host 提供数据,具有很强的移植性,非常适合 只使用 静态数据的场景,比如应用的配置信息、web server 的静态文件等。
Volume 生命周期管理
创建数据卷
bash
docker volume create [卷名]
docker run -v 卷名:/路径 # 会自动创建
查看数据卷
bash
docker volume ls # 列表
docker volume inspect 卷名 # 详细信息
docker inspect 容器名 # 查看容器的数据卷信息
清理无用数据卷
bash
# 删除所有未被使用的数据卷(谨慎!)
docker volume prune
# 删除指定数据卷
docker volume rm 卷名
备份和恢复
因为 volume 实际上是 host 文件系统中的目录和文件,所以 volume 的备份实际上是对文件系统的备份。将volume 文件打包备份即可
volume 的恢复也很简单,如果数据损坏了,直接用之前备份的数据拷贝到 指定目录下就可以了
数据卷迁移
如果我们想使用更新版本的容器,这就涉及到数据迁移,方法是:
-
docker stop当前容器。 -
启动新版本容器并 mount 原有 volume。
例如
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:latest
当然,在启用新容器前要确保新版本的默认数据路径是否发生变化。
总结对比表
| 特性 | Bind Mount | Docker Managed Volume | Volume Container |
|---|---|---|---|
| 宿主机路径 | 需要指定 | Docker 自动分配 | Docker 自动分配 |
| 数据持久化 | ✅ | ✅ | ✅ |
| 数据共享 | ✅ | ✅ | ✅ |
| 备份难度 | 容易(直接复制) | 中等 | 容易 |
| 性能 | 直接IO | 略低 | 略低 |
| 适合场景 | 开发、配置 | 生产数据 | 多容器共享 |
实用命令小抄:
bash
# 查看数据卷占用空间
docker system df -v
# 清理所有无用资源
docker system prune -a --volumes
# 复制数据卷数据到宿主机
docker run --rm -v 源卷:/源 -v $(pwd):/备份 busybox cp -r /源 /备份