第6章 存储
摘要:本章深入探讨 Docker 存储机制,涵盖两类核心存储资源:由 storage driver 管理的镜像层/容器层(分层结构、Copy-on-Write 特性)以及 Data Volume。重点解析 Data Volume 的两种类型------bind mount(直接挂载宿主机目录/文件)与 docker managed volume(由 Docker 自动管理),并详细对比其特性、使用场景与差异。进一步介绍如何通过 volume 实现容器与宿主机、容器与容器之间的数据共享,包括使用 volume container 及 data-packed volume container 等高级方案。最后,系统讲解 volume 生命周期的管理操作,如备份、恢复、迁移与销毁,并通过实战案例演示如何利用 bind mount 持久化 MySQL 数据,确保容器删除后数据不丢失。本章内容为容器数据持久化与共享提供了全面指导。
续上篇博客 从零拆解 Docker 网络:bridge/host/none、自定义网络与 DNS 通信实战
038 Docker的两类存储资源
我们从本章开始讨论 Docker 存储。 Docker 为容器提供了两种存放数据的资源:
- 由 storage driver 管理的镜像层和容器层。
- Data Volume。
我们会详细讨论它们的原理和特性。
storage driver
在前面镜像章节我们学习到 Docker 镜像的分层结构,简单回顾一下。

容器由最上面一个可写的容器层,以及若干只读的镜像层组成,容器的数据就存放在这些层中。这样的 分层结构最大的特性是 Copy-on-Write:
- 新数据会直接存放在最上面的容器层。
- 修改现有数据会先从镜像层将数据复制到容器层,修改后的数据直接保存在容器层中,镜像层保持 不变。
- 如果多个层中有命名相同的文件,用户只能看到最上面那层中的文件。
分层结构使镜像和容器的创建、共享以及分发变得非常高效,而这些都要归功于 Docker storage driver。正是 storage driver 实现了多层数据的堆叠并为用户提供一个单一的合并之后的统一视图。
Docker 支持多种 storage driver,有 AUFS、Device Mapper、Btrfs、OverlayFS、VFS 和 ZFS。它们都 能实现分层的架构,同时又有各自的特性。对于 Docker 用户来说,具体选择使用哪个 storage driver 是 一个难题,因为:
- 没有哪个 driver 能够适应所有的场景。
- driver 本身在快速发展和迭代。
不过 Docker 官方给出了一个简单的答案:
优先使用 Linux 发行版默认的 storage driver。
Docker 安装时会根据当前系统的配置选择默认的 driver。默认 driver 具有最好的稳定性,因为默认 driver 在发行版上经过了严格的测试。
运行 docker info查看CentOS的默认 driver:
bash
[root@docker ~]# docker info
Client: Docker Engine - Community
Version: 26.1.3
Context: default
Debug Mode: false
Plugins:
buildx: Docker Buildx (Docker Inc.)
Version: v0.14.0
Path: /usr/libexec/docker/cli-plugins/docker-buildx
compose: Docker Compose (Docker Inc.)
Version: v2.27.0
Path: /usr/libexec/docker/cli-plugins/docker-compose
Server:
Containers: 2
Running: 0
Paused: 0
Stopped: 2
Images: 16
Server Version: 26.1.3
Storage Driver: overlay2
Backing Filesystem: xfs
Supports d_type: true
Using metacopy: false
Native Overlay Diff: true
userxattr: false
Logging Driver: json-file
Cgroup Driver: cgroupfs
Cgroup Version: 1
Plugins:
Volume: local
Network: bridge host ipvlan macvlan null overlay
Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog
Swarm: inactive
Runtimes: io.containerd.runc.v2 runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 8b3b7ca2e5ce38e8f31a34f35b2b68ceb8470d89
runc version: v1.1.12-0-g51d5e94
init version: de40ad0
Security Options:
seccomp
Profile: builtin
Kernel Version: 4.18.0-553.6.1.el8.x86_64
Operating System: CentOS Stream 8
OSType: linux
Architecture: x86_64
CPUs: 4
Total Memory: 15.36GiB
Name: docker
ID: fa9330b1-aebb-4889-a2d6-bc7181d443b2
Docker Root Dir: /var/lib/docker
Debug Mode: false
Experimental: false
Insecure Registries:
127.0.0.0/8
CentOS Stream 8 用的overlay2,底层文件系统是xfs,各层数据存放在 /var/lib/docker。
对于某些容器,直接将数据放在由 storage driver 维护的层中是很好的选择,比如那些无状态的应用。 无状态意味着容器没有需要持久化的数据,随时可以从镜像直接创建。
比如 busybox,它是一个工具箱,我们启动 busybox 是为了执行诸如 wget,ping 之类的命令,不需要 保存数据供以后使用,使用完直接退出,容器删除时存放在容器层中的工作数据也一起被删除,这没问 题,下次再启动新容器即可。
但对于另一类应用这种方式就不合适了,它们有持久化数据的需求,容器启动时需要加载已有的数据, 容器销毁时希望保留产生的新数据,也就是说,这类容器是有状态的。
这就要用到 Docker 的另一种存储机制:Data Volume,下一节我们讨论。
039 Data Volume之bind mount
storage driver 和 data volume 是容器存放数据的两种方式,上一节我们学习了 storage driver,本节开 始讨论 Data Volume。
Data Volume 本质上是 Docker Host 文件系统中的目录或文件,能够直接被 mount 到容器的文件系统 中。Data Volume 有以下特点:
- Data Volume 是目录或文件,而非没有格式化的磁盘(块设备)。
- 容器可以读写 volume 中的数据。
- volume 数据可以被永久的保存,即使使用它的容器已经销毁。
好,现在我们有数据层(镜像层和容器层)和 volume 都可以用来存放数据,具体使用的时候要怎样选 择呢?考虑下面几个场景:
- Database 软件 vs Database 数据
- Web 应用 vs 应用产生的日志
- 数据分析软件 vs input/output 数据
- Apache Server vs 静态 HTML 文件
相信大家会做出这样的选择:
- 前者放在数据层中。因为这部分内容是无状态的,应该作为镜像的一部分。
- 后者放在 Data Volume 中。这是需要持久化的数据,并且应该与镜像分开存放。
还有个大家可能会关心的问题:如何设置 voluem 的容量?
因为 volume 实际上是 docker host 文件系统的一部分,所以 volume 的容量取决于文件系统当前未使 用的空间,目前还没有方法设置 volume 的容量。
在具体的使用上,docker 提供了两种类型的 volume:bind mount 和 docker managed volume。

**bind mount **
bind mount 是将 host 上已存在的目录或文件 mount 到容器。
例如 docker host 上有目录 $HOME/htdocs:
bash
[root@docker ~]# pwd
/root
[root@docker ~]# mkdir htdocs
[root@docker ~]# cd htdocs/
[root@docker htdocs]# touch index.html
[root@docker htdocs]# vim index.html
<html><body><h1>This is a file in host file system !</h1></body></html>
[root@docker htdocs]# cd ..
[root@docker ~]# cat htdocs/index.html
<html><body><h1>This is a file in host file system !</h1></body></html>
[root@docker ~]#
通过 -v 将其 mount 到 httpd 容器:
bash
[root@docker ~]# docker run -d -p 80:80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
883f8a22b2be796928d16ad131f6cba922492e325fdadf4275ca9f59eae5ed12
-v 的格式为 <host path>:<container path>。/usr/local/apache2/htdocs 就是 apache server 存 放静态文件的地方。由于 /usr/local/apache2/htdocs 已经存在,原有数据会被隐藏起来,取而代之的是 host $HOME/htdocs/ 中的数据,这与 linux mount 命令的行为是一致的。
bash
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>This is a file in host file system !</h1></body</html>
curl 显示当前主页确实是 $HOME/htdocs/index.html 中的内容。更新一下,看是否能生效:
bash
[root@docker ~]# echo "updated index page!" > ~/htdocs/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
updated index page!
host 中的修改确实生效了,bind mount 可以让 host 与容器共享数据。这在管理上是非常方便的。
下面我们将容器销毁,看看对 bind mount 有什么影响:
bash
[root@docker ~]# docker stop 883f8a22b2be
883f8a22b2be
[root@docker ~]# docker rm 883f8a22b2be
883f8a22b2be
[root@docker ~]# cat ~/htdocs/index.html
updated index page!
[root@docker ~]#
可见,即使容器没有了,bind mount 也还在。这也合理,bind mount 是 host 文件系统中的数据,只 是借给容器用用,哪能随便就删了啊。
另外,bind mount 时还可以指定数据的读写权限,默认是可读可写,可指定为只读:
bash
[root@docker ~]# docker run -d -p 80:80 -v ~/htdocs:/usr/local/apache2/htdocs:ro
httpd
bf1f526ddde7a7babb29adc091eef039628ba2eb288e6db33c9ccd946cc5d5f1
[root@docker ~]# docker exec -it bf1f526ddde7 bash
root@bf1f526ddde7:/usr/local/apache2# echo "do some changes" > htdocs/index.html
bash: htdocs/index.html: Read-only file system
ro 设置了只读权限,在容器中是无法对 bind mount 数据进行修改的。只有 host 有权修改数据,提高 了安全性。
除了 bind mount 目录,还可以单独指定一个文件:
bash
# 删除上一个容器不然80端口冲突
[root@docker ~]# docker rm - f bf1f526ddde7
#运行新容器用单独文件绑定
[root@docker ~]# docker run -d -p 80:80 -v
~/htdocs/index.html:/usr/local/apache2/htdocs/new_index.html httpd
643e8820e98a17b977a984903ddfa6f37dcae3d705d45b624d0fce5c94fecb0c
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80/new_index.html
updated index page!
[root@docker ~]#
使用 bind mount 单个文件的场景是:只需要向容器添加文件,不希望覆盖整个目录。在上面的例子 中,我们将 html 文件加到 apache 中,同时也保留了容器原有的数据。
使用单一文件有一点要注意:host 中的源文件必须要存在,不然会当作一个新目录 bind mount 给容器。
mount point 有很多应用场景,比如我们可以将源代码目录 mount 到容器中,在 host 中修改代码就能 看到应用的实时效果。再比如将 mysql 容器的数据放在 bind mount 里,这样 host 可以方便地备份和迁移数据。
bind mount 的使用直观高效,易于理解,但它也有不足的地方:bind mount 需要指定 host 文件系统 的特定路径,这就限制了容器的可移植性,当需要将容器迁移到其他 host,而该 host 没有要 mount 的 数据或者数据不在相同的路径时,操作会失败。
移植性更好的方式是 docker managed volume,下一节我们讨论。
040 Data Volume之docker managed volume
docker managed volume 与 bind mount 在使用上的最大区别是不需要指定 mount 源,指明 mount point 就行了。还是以 httpd 容器为例:
bash
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
c75f36c4bf32e39f2d078d41956be85522c0630cf91498123dcd83ba159ce873
我们通过 -v 告诉 docker 需要一个 data volume,并将其 mount 到 /usr/local/apache2/htdocs。那么 这个 data volume 具体在哪儿呢?
这个答案可以在容器的配置信息中找到,执行 docker inspect 命令:
bash
[root@docker ~]# docker inspect c75f36c4bf32 #docker inspect后面跟的是容器ID
...
"Mounts": [
{
"Type": "volume",
"Name":
"1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16",
"Source":
"/var/lib/docker/volumes/1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16/_data",
"Destination": "/usr/local/apache2/htdocs",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
...
docker inspect 的输出很多,我们感兴趣的是 Mounts这部分,这里会显示容器当前使用的所有 data volume,包括 bind mount 和 docker managed volume。
Source 就是该 volume 在 host 上的目录。
原来,每当容器申请 mount docker manged volume 时,docker 都会在 /var/lib/docker/volumes下生成一个目录(例子中是 "/var/lib/docker/volumes/1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e56 9d16/_data ),这个目录就是 mount 源。
下面继续研究这个 volume,看看里面有些什么东西:
bash
[root@docker ~]# ls -l /var/lib/docker/volumes/1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16/_data
total 4
-rw-r--r-- 1 501 ftp 26 Sep 17 21:08 index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
volume 的内容跟容器原有 /usr/local/apache2/htdocs 完全一样,这是怎么回事呢?
这是因为:如果 mount point 指向的是已有目录,原有数据会被复制到 volume 中。
但要明确一点:此时的 /usr/local/apache2/htdocs 已经不再是由 storage driver 管理的层数据了,它已 经是一个 data volume。我们可以像 bind mount 一样对数据进行操作,例如更新数据:
bash
[root@docker ~]# echo "update volume from host !" > /var/lib/docker/volumes/1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16/_data/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
update volume from host !
简单回顾一下 docker managed volume 的创建过程:
- 容器启动时,简单的告诉 docker "我需要一个 volume 存放数据,帮我 mount 到目录 /abc"。
- docker 在 /var/lib/docker/volumes 中生成一个随机目录作为 mount 源。
- 如果 /abc 已经存在,则将数据复制到 mount 源,
- 将 volume mount 到 /abc
除了通过 docker inspect 查看 volume,我们也可以用docker volume命令:
bash
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
loca 1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16
569d16
[root@docker ~]#
[root@docker ~]# docker volume inspect 1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16
[
{
"CreatedAt": "2024-09-17T21:04:33+08:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16/_da
"Name":
"1cc915096121dea197bbbb93fc90e6491d530e0c9e2ace1de07e2a635e569d16",
"Options": null,
"Scope": "local"
}
]
[root@docker ~]#
目前, docker volume 只能查看 docker managed volume,还看不到 bind mount;同时也无法知道 volume 对应的容器,这些信息还得靠 docker inspect。
我们已经学习了两种 data volume 的原理和基本使用方法,下面做个对比:
- 相同点:两者都是 host 文件系统中的某个路径。
- 不同点:
| bind mount | docker managed volume | |
|---|---|---|
| volume 位置 | 可任意指定 | /var/lib/docker/volumes/... |
| 对已有mount point 影响 | 隐藏并替换为 volume | 原有数据复制到 volume |
| 是否支持单个文件 | 支持 | 不支持,只能是目录 |
| 权限控制 | 可设置为只读,默认为读写权限 | 无控制,均为读写权限 |
| 移植性 | 移植性弱,与 host path 绑定 | 移植性强,无需指定 host 目录 |
下节讨论如何通过 data volume 实现容器与 host,容器与容器共享数据。
041 如何共享数据
数据共享是 volume 的关键特性,本节我们详细讨论通过 volume 如何在容器与 host 之间,容器与容器 之间共享数据。
容器与 host 共享数据
我们有两种类型的 data volume,它们均可实现在容器与 host 之间共享数据,但方式有所区别。
对于 bind mount 是非常明确的:直接将要共享的目录 mount 到容器。具体请参考前面 httpd 的例子,不再赘述。
docker managed volume 就要麻烦点。由于 volume 位于 host 中的目录,是在容器启动时才生成,所以需要将共享数据拷贝到 volume 中。请看下面的例子:
bash
[root@docker ~]# docker run -d -p 80:80 -v /usr/local/apache2/htdocs httpd
889b9d5309bd12499f8e4ee78a491d9ba7acef5660be00f30cda6569c9974c6f
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
<html><body><h1>It works!</h1></body></html>
[root@docker ~]#
[root@docker ~]# docker cp ~/htdocs/index.html 889b9d5309bd:/usr/local/apache2/htdocs #将host os的/root/htdocs/index.html拷贝到容器中的/usr/local/apache2/htdocs目录下
Successfully copied 2.05kB to 889b9d5309bd:/usr/local/apache2/htdocs
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:80
updated index page!
[root@docker ~]#
docker cp 可以在容器和 host 之间拷贝数据,当然我们也可以直接通过 Linux 的 cp 命令复制到/var/lib/docker/volumes/xxx。
思考:容器中的文件拷贝到host os如何操作
容器之间共享数据
第一种方法是将共享数据放在 bind mount 中,然后将其 mount 到多个容器。还是以 httpd 为例,不过 这次的场景复杂些,我们要创建由三个 httpd 容器组成的 web server 集群,它们使用相同的 html 文 件,操作如下:
- 将 $HOME/htdocs mount 到三个 httpd 容器。
bash
[root@docker ~]# docker run --name web1 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
994e75ef90a80365d7ea7733c727c3b1d24e5423d59a74185304462b7137d774
[root@docker ~]#
[root@docker ~]# docker run --name web2 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
28ff7c72d18b30123ff7e383db211890a0007e48817cdc73e7f50e9f2233eee6
[root@docker ~]#
[root@docker ~]# docker run --name web3 -d -p 80 -v ~/htdocs:/usr/local/apache2/htdocs httpd
160d823d985bad0ce7c27a0d301956f3c7ab4e9ca34c5be2601b3374fa30f6f1
[root@docker ~]#
- 查看当前主页内容。
bash
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
160d823d985b httpd "httpd-foreground" 13 seconds ago Up 12 seconds
0.0.0.0:32770->80/tcp, :::32770->80/tcp web3
28ff7c72d18b httpd "httpd-foreground" 19 seconds ago Up 18 seconds
0.0.0.0:32769->80/tcp, :::32769->80/tcp web2
994e75ef90a8 httpd "httpd-foreground" 29 seconds ago Up 27 seconds
0.0.0.0:32768->80/tcp, :::32768->80/tcp web1
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:32770 #这个32770端口是host os映射到web3的duan'kou
updated index page!
[root@docker ~]# curl 127.0.0.1:32769
updated index page!
[root@docker ~]# curl 127.0.0.1:32768
updated index page!
[root@docker ~]#
- 修改 volume 中的主页文件,再次查看并确认所有容器都使用了新的主页。
bash
[root@docker ~]# echo "This is a new index page for web cluster" >
~/htdocs/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:32770
This is a new index page for web cluster
[root@docker ~]# curl 127.0.0.1:32769
This is a new index page for web cluster
[root@docker ~]# curl 127.0.0.1:32768
This is a new index page for web cluster
[root@docker ~]#
另一种在容器之间共享数据的方式是使用 volume container,下节讨论。
042 用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
bb4a33bf34aa7817ea752db83079aff15a5dfd77c23363f2e8dd3096d1cb910f
我们将容器命名为 vc_data(vc 是 volume container 的缩写)。注意这里执行的是 docker create 命令,这是因为 volume container 的作用只是提供数据,它本身不需要处于运行状态。容器 mount 了 两个 volume:
- bind mount,存放 web server 的静态文件。
- docker managed volume,存放一些实用工具(当然现在是空的,这里只是做个示例)。
通过 docker inspect可以查看到这两个 volume。
bash
[root@docker ~]# docker inspect vc_data
...
"Mounts": [
{
"Type": "volume",
"Name":
"4500300655eb8e32a5d88174395a61ea394e17266fbde60f224d815a7a9557e0",
"Source":
"/var/lib/docker/volumes/4500300655eb8e32a5d88174395a61ea394e17266fbde60f224d815a7a9557e0/_data",
"Destination": "/other/userful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "bind",
"Source": "/root/htdocs",
"Destination": "/usr/local/apache2/htdocs",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
...
其他容器可以通过 --volumes-from 使用 vc_data 这个 volume container:
bash
[root@docker ~]# docker run --name web1 -d -p 80 --volumes-from vc_data httpd
362f039b562e4bf0020c38839a69bfd62733f27fda5481eb5b58178f41318ffe
[root@docker ~]#
[root@docker ~]# docker run --name web2 -d -p 80 --volumes-from vc_data httpd
217c1a243bb9a1f32ccae9ae2792e3720263d36402bbac58f43cfb068bc21e66
[root@docker ~]#
[root@docker ~]# docker run --name web3 -d -p 80 --volumes-from vc_data httpd
ce00b5fa458fc1af01e82ce8b43e8c072c858a669c1298add8053cbc7ac9f49b
[root@docker ~]#
三个 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":
"4500300655eb8e32a5d88174395a61ea394e17266fbde60f224d815a7a9557e0",
"Source":
"/var/lib/docker/volumes/4500300655eb8e32a5d88174395a61ea394e17266fbde60f224d815a7a9557e0/_data",
"Destination": "/other/userful/tools",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
...
web1 容器使用的就是 vc_data 的 volume,而且连 mount point 都是一样的。验证一下数据共享的效 果:
bash
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ce00b5fa458f httpd "httpd-foreground" 2 seconds ago Up 2 seconds
0.0.0.0:32770->80/tcp, :::32773->80/tcp web3
217c1a243bb9 httpd "httpd-foreground" 2 seconds ago Up 2 seconds
0.0.0.0:32769->80/tcp, :::32772->80/tcp web2
362f039b562e httpd "httpd-foreground" 2 seconds ago Up 2 seconds
0.0.0.0:32768->80/tcp, :::32771->80/tcp web1
[root@docker ~]#
[root@docker ~]# echo "This content is from a volume container!" >
~/htdocs/index.html
[root@docker ~]#
[root@docker ~]# curl 127.0.0.1:32773
This content is from a volume container!
[root@docker ~]# curl 127.0.0.1:32772
This content is from a volume container!
[root@docker ~]# curl 127.0.0.1:32771
This content is from a volume container!
可见,三个容器已经成功共享了 volume container 中的 volume。
下面我们讨论一下 volume container 的特点:
- 与 bind mount 相比,不必为每一个容器指定 host path,所有 path 都在 volume container 中定 义好了,容器只需与 volume container 关联,实现了容器与 host 的解耦。
- 使用 volume container 的容器其 mount point 是一致的,有利于配置的规范和标准化,但也带来 一定的局限,使用时需要综合考虑。
另一种在容器之间共享数据的方式是 data-packed volume container,下一节讨论。
043 data-packed volume container
在上一节的例子中 volume container 的数据归根到底还是在 host 里,有没有办法将数据完全放到 volume container 中,同时又能与其他容器共享呢?
当然可以,通常我们称这种容器为 data-packed volume container。其原理是将数据打包到镜像中,然 后通过 docker managed volume 共享。
我们用下面的 Dockfile 构建镜像:
bash
[root@docker ~]# vim 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 ~]# echo "This content is from a data packed volume container!" >
htdocs/index.html
build 新镜像 datapacked:
bash
[root@docker ~]# docker build -t datapacked .

用新镜像创建 data-packed volume container:
baSH
[root@docker ~]# docker create --name vc_data datapacked
55dc035deeb31907830ea45f7af5e209c7e48078d59732798c529e7445d2a196
因为在 Dockerfile 中已经使用了 VOLUME 指令,这里就不需要指定 volume 的 mount point 了。启动 httpd 容器并使用 data-packed volume container:
bash
[root@docker ~]# docker run -d -p 80:80 --volumes-from vc_data httpd
98db78fc661441ec7ce0a2b76d62675fccf3389b7768cc9744b852645323c33b
[root@docker ~]#
[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 的静态文件等。
容器数据共享就讨论到这里,下一节我们学习如何对 data volume 的生命周期进行管理。
044 volume生命周期管理
Data Volume 中存放的是重要的应用数据,如何管理 volume 对应用至关重要。前面我们主要关注的是 volume 的创建、共享和使用,本节将讨论如何备份、恢复、迁移和销毁 volume。
备份
因为 volume 实际上是 host 文件系统中的目录和文件,所以 volume 的备份实际上是对文件系统的备份。
还记得前面我们是如何搭建本地 Registry 的吗?
bash
[root@docker ~]# docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry
registry:2
Unable to find image 'registry:2' locally
2: Pulling from library/registry
1cc3d825d8b2: Pull complete
85ab09421e5a: Pull complete
40960af72c1c: Pull complete
e7bb1dbb377e: Pull complete
a538cc9b1ae3: Pull complete
Digest: sha256:ac0192b549007e22998eb74e8d8488dcfe70f1489520c3b144a6047ac5efbe90
Status: Downloaded newer image for registry:2
a8214201171f28a13803c647ef84acdc982465c230cb5c6a034bd05574948408
[root@docker ~]#
所有的本地镜像都存在 host 的 /myregistry 目录中,我们要做的就是定期备份这个目录。
恢复
volume 的恢复也很简单,如果数据损坏了,直接用之前备份的数据拷贝到 /myregistry 就可以了。
迁移
如果我们想使用更新版本的 Registry,这就涉及到数据迁移,方法是:
-
docker stop当前 Registry 容器。 -
启动新版本容器并 mount 原有 volume。
docker run -d -p 5000:5000 -v /myregistry:/var/lib/registry registry:latest当然,在启用新容器前要确保新版本的默认数据路径是否发生变化。
销毁
可以删除不再需要的 volume,但一定要确保知道自己正在做什么,volume 删除后数据是找不回来的。
docker 不会销毁 bind mount,删除数据的工作只能由 host 负责。对于 docker managed volume,在 执行 docker rm 删除容器时可以带上 -v 参数,docker 会将容器使用到的 volume 一并删除,但前提是没有其他容器 mount 该 volume,目的是保护数据,非常合理。
如果删除容器时没有带 -v 呢?这样就会产生孤儿 volume,好在 docker 提供了 volume 子命令可以对 docker managed volume 进行维护。请看下面的例子:
bash
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
[root@docker ~]#
[root@docker ~]# docker run --name bbox -v /test/data busybox
[root@docker ~]#
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 8645b8e2edb91820799e6e8807d8b3476f1c5cec40beb14240b0e5143050e5e1
容器 bbox 使用的 docker managed volume 可以通过 docker volume ls 查看到。
删除 bbox:
bash
[root@docker ~]# docker rm bbox
bbox
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
local 8645b8e2edb91820799e6e8807d8b3476f1c5cec40beb14240b0e5143050e5e1
[root@docker ~]#
因为没有使用 -v,volume 遗留了下来。对于这样的孤儿 volume,可以用 docker volume rm 删除:
bash
[root@docker ~]# docker volume rm 8645b8e2edb91820799e6e8807d8b3476f1c5cec40beb14240b0e5143050e5e1
8645b8e2edb91820799e6e8807d8b3476f1c5cec40beb14240b0e5143050e5e1
[root@docker ~]#
[root@docker ~]# docker volume ls
DRIVER VOLUME NAME
[root@docker ~]#
如果想批量删除孤儿 volume,可以执行:
docker volume rm $(docker volume ls -q)
docker volume prune
小结
本章我们学习了以下内容:
- docker 为容器提供了两种存储资源:数据层和 Data Volume。
- 数据层包括镜像层和容器层,由 storage driver 管理。
- Data Volume 有两种类型:bind mount 和 docker managed volume。
- bind mount 可实现容器与 host 之间,容器与容器之间共享数据。
- volume container 是一种具有更好移植性的容器间数据共享方案,特别是 data-packed volume container。
- 最后我们学习了如何备份、恢复、迁移和销毁 Data Volume。
实战:安装mysql
docker hub上查找mysql镜像
bash
[root@docker ~]# docker search mysql
从华为云加速器拉取mysql镜像到本地标签为5.7
bash
[root@docker ~]# docker pull mysql:5.7
5.7: Pulling from library/mysql
20e4dcae4c69: Pull complete
1c56c3d4ce74: Pull complete
e9f03a1c24ce: Pull complete
68c3898c2015: Pull complete
6b95a940e7b6: Pull complete
90986bb8de6e: Pull complete
ae71319cb779: Pull complete
ffc89e9dfd88: Pull complete
43d05e938198: Pull complete
064b2d298fba: Pull complete
df9a4d85569b: Pull complete
Digest: sha256:4bc6bc963e6d8443453676cae56536f4b8156d78bae03c0145cbe47c2aad73bb
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
使用mysql镜像
简单版
bash
[root@docker ~]# docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7
cf4065b04c9d68bb02ef8ccb74b8b02f357f588680328556afcf008dd69c7138
[root@docker ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
cf4065b04c9d mysql:5.7 "docker-entrypoint.s..." 38 seconds ago up 36 seconds
0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp elated_gauss
[root@docker ~]# docker exec -it cf4065b04c9d bash
bash-4.2# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
建库建表插入数据
bash
mysql> CREATE DATABASE db01; #创建数据库叫db01
Query OK, 1 row affected (0.00 sec)
mysql> USE db01; #使用db01;
Database changed
mysql> CREATE TABLE tablea(id int,name varchar(20));
Query OK, 0 rows affected (0.04 sec)
mysql> INSERT INTO tablea VALUES(1,'gqd');
Query OK, 1 row affected (0.14 sec)
mysql> SELECT * FROM tablea;
+------+------+
| id | name |
+------+------+
| 1 | gqd |
+------+------+
1 row in set (0.00 sec)
mysql>
外部windows连接运行在docker上的mysql容器实例服务
安装 navicat170_premium_cs_x64.exe ,一直下一步直到安装完成
双击桌面图标打开





插入中文试试


报错了

为什么报错?
docker上默认字符集不支持中文
bash
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | latin1 |
| character_set_connection | latin1 |
| character_set_database | latin1 |
| character_set_filesystem | binary |
| character_set_results | latin1 |
| character_set_server | latin1 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)
删除容器后,里面的mysql数据在不在?
bash
# 删除mysql容器
[root@docker ~]# docker rm -f cf4065b04c9d
容器实例删除,你还有什么?删库到跑路?
再用同样的方式创建一个mysql,数据还在么?
bash
[root@docker ~]# docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
1e1a206582f2c7cf79a3c764b2c441b49d752146d87b09681f6e84dba1232361

实战版
新建mysql实例
bash
[root@docker ~]# docker run -d -p 3306:3306 --privileged=true -v /gqd/mysql/log:/var/log/mysql -v /gqd/mysql/data:/var/lib/mysql -v /gqd/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7
ff28918675ccf77947aba0b9602d40edfee1a4e9ff3122288d4c84d7c80e3f4
新建my.cnf,通过容器卷同步给mysql容器实例
bash
[root@docker ~]# cd /gqd/mysql/conf
[root@docker conf]# ls
[root@docker conf]# vim my.cnf #实现mysql支持中文
[client]
default_character_set=utf8
[mysqld]
collation_server = utf8_general_ci
character_set_server = utf8
[root@docker conf]#
重新启动mysql容器实例再重新进入并查看字符编码
bash
[root@docker conf]# docker restart mysql
mysql
[root@docker conf]# docker exec -it mysql bash
bash-4.2# mysql -uroot -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.44 MySQL Community Server (GPL)
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> SHOW VARIABLES LIKE 'character%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.01 sec)
mysql> CREATE DATABASE db01; #创建数据库叫db01
Query OK, 1 row affected (0.00 sec)
mysql> USE db01; #使用db01;
Database changed
mysql> CREATE TABLE tablea(id int,name varchar(20));
Query OK, 0 rows affected (0.04 sec)
mysql> INSERT INTO tablea VALUES(1,'gqd');
Query OK, 1 row affected (0.14 sec)
mysql> SELECT * FROM tablea;
+------+------+
| id | name |
+------+------+
| 1 | gqd |
+------+------+
1 row in set (0.00 sec)
mysql>
在新建库新建表再插入中文测试
不报错了!

假如当前容器实例删除,再重新来一次,之前创建的db01数据还有吗??赶紧动起来尝试一下 吧!!!
再次删库跑路!!!
bash
[root@docker ~]# docker rm -f ff28918675cc
重新创建容器,看看数据还在不在
bash
[root@docker ~]# docker run -d -p 3306:3306 --privileged=true -v /gqd/mysql/log:/var/log/mysql -v /gqd/mysql/data:/var/lib/mysql -v /gqd/mysql/conf:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 --name mysql mysql:5.7
507cd722fc40b75f5eb0202c09eb9276a7046b64797864eb5bba15144e01f661
发现数据还在
