1、Docker 数据管理简介
默认情况下,在容器内创建的所有文件都存储在可写容器层上。这意味着:
- 当该容器不再存在时,数据将不会持久保存,并且如果另一个进程需要它,则可能很难从容器中取出数据;
- 容器的可写层与运行容器的主机紧密耦合,不能轻易地将数据移动到其他地方;
- 写入容器的可写层需要 存储驱动程序来管理文件系统。存储驱动程序使用Linux内核提供联合文件系统,与使用直接写入主机文件系统的数据卷相比,这种额外的抽象降低了性能 。
Docker为容器提供了两种方案来将文件存储在主机中,以便即使容器停止后文件也可以持久存储:volumes 和 bind mounts。如果您在Linux上运行Docker,则还可以使用tmpfs挂载。如果您在Windows上运行Docker,则还可以使用named pipe。
2、Docker 数据持久化之 Data Volume
2.1、Volume 简介
Volume 存储在由 Docker 管理的主机文件系统的一部分中(在Linux上为/var/lib/docker/volumes/
)。非 Docker 进程不应修改文件系统的这一部分,Volume方式是在Docker中持久保存数据的最佳方法(官方文档说的,不过实际情况中你可以按需选择)。
Volumes 由 Docker 创建和管理,您可以使用 docker volume create
命令显式创建卷,Docker 也可以在容器或服务创建期间创建 Volumes:
$ sudo docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes
Run 'docker volume COMMAND --help' for more information on a command.
创建 Volume 时,它存储在 Docker 主机上的目录中,将 Volume 装入容器时,此目录就是装入容器的目录。这类似于 bind mount 的工作方式,只是 Volume 由 Docker 管理并且与主机的核心功能隔离。
多个容器可以同时使用同一个 Volume ,当没有正在运行的容器使用 volume 时,或者删除某个容器后,对应的 volume 不会自动删除,过后启动新的容器时照样可以使用此 volume ,当然,你也可以手动删除没被容器使用的 volumes ,命令: docker volume prune
。
挂载 volume 时,默认是随机名称,你也可以在启动容器时指定 volume 名称。
Volume 还支持将数据存储在远程主机或云提供商上。
2.2、简单实验
接下来,我们一次来实际操作体验一下 docker 的volume 挂载模式的实现。
2.2.1、从 docker hub 官网 pull 一个 MySQL 镜像
pull 一个MySQL镜像,这一步没什么说的
# pull mysql镜像
$ sudo docker pull mysql:5.7
2.2.2、启动一个 MySQL 容器
接下来我们要启动一个MySQL容器,进一步感受下 volume 挂载方式
# 启动一个MySQL容器
$ sudo docker run --name test1_mysql -e MYSQL_ROOT_PASSWORD=password -d mysql:5.7
-e MYSQL_ROOT_PASSWORD
为此镜像环境变量(环境变量不用解释了吧?每个镜像都有自己的环境变量),具体参数及意义见此链接
-d
后台运行
--name
指定容器名称
2.2.3、查看启动 MySQL 容器默认创建的 volume
通过 docker volume ls
查看本机 volumes 列表,因为我自始至终只启动了一个容器,所以,看到的这一个 volume 就是我启动的这个 MySQL 容器对应的 volume 了,你可以通过不同的方式查看到这个 volume 。
# 查看当前 volume 列表
$ sudo docker volume ls
DRIVER VOLUME NAME
local 325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d
# 你也可以通过 docker inspect 命令查看当前这个 volume 的详情
$ sudo docker inspect 325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d
[
{
"CreatedAt": "2019-12-25T16:10:31+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d/_data",
"Name": "325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d",
"Options": null,
"Scope": "local"
}
]
# 当然,此时你也可以通过查看 /var/lib/docker/volumes/ 目录来查看生成的 volume
$ sudo ll /var/lib/docker/volumes/
总用量 28
drwxr-xr-x 3 root root 4096 12月 25 16:10 325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d
-rw------- 1 root root 32768 12月 25 16:10 metadata.db
2.3.4、启动两个 MySQL 容器,查看生成的 volume
# 启动第二个 MySQL 容器
$ sudo docker run --name test2_mysql -d -e MYSQL_ROOT_PASSWORD=password mysql:5.7
67358cf2b8feecd2274700e833de1a6565a207017cc42fd39e92b56b2537d7ab
# 查看 volumes
$ sudo docker volume ls
DRIVER VOLUME NAME
local 325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d
local 06517fdc343cb927b11d4e1d97967066118ce92132435f5214ee97afae967769
# 查看 volumes
$ sudo ll /var/lib/docker/volumes/
总用量 32
drwxr-xr-x 3 root root 4096 12月 25 16:45 06517fdc343cb927b11d4e1d97967066118ce92132435f5214ee97afae967769
drwxr-xr-x 3 root root 4096 12月 25 16:10 325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d
-rw------- 1 root root 32768 12月 25 16:45 metadata.db
2.3.4、自定义 volume 名称
以上例子可以看到有个问题,启动容器默认生成的 volume 名字是个很长的字符串,其实我们可以在启动容器时自定义一个 volume 名字。
首先通过 docker inspect
命令查看 MySQL 镜像的 volumes 信息,由以下结果可以看出,MySQL 默认会将 volumes 挂载在 /var/lib/mysql
下,也就是说,MySQL 镜像创建容器后,数据默认存储在容器的 /var/lib/mysql
中。
docker inspect
命令可以查看镜像、容器、网络、存储等等信息
# 查看MySQL镜像的volume信息
$ sudo docker inspect mysql:5.7|nl|grep Volumes -A 3
42 "Volumes": {
43 "/var/lib/mysql": {}
44 },
45 "WorkingDir": "",
--
79 "Volumes": {
80 "/var/lib/mysql": {}
81 },
82 "WorkingDir": "",
通过 -v
参数指定 volume 名称启动一个 MySQL 容器,格式为 -v 自定义volume名称:容器内数据目录
,注意,如果本地有这个名称的 volume 就引用这个 volume ,没有则会新建自定义名称的volume。
# 通过 -v 参数指定 volume 名称
$ sudo docker run --name test3_mysql -v test3_mysql:/var/lib/mysql -d -e MYSQL_ROOT_PASSWORD=password mysql:5.7
a86213ba65d05296e0bbd22e8bfaff7c6e07ce9cb0818b734959323f728d5d4f
# 查看 volumes 信息
$ sudo docker volume ls
DRIVER VOLUME NAME
local 325c3adc386ba4f649f429abe820d56212057a7dc475611a9639bcb66f41df7d
local 06517fdc343cb927b11d4e1d97967066118ce92132435f5214ee97afae967769
local test3_mysql
你将看到自定义的 test3_mysql 这个 volume 。
2.3.5、引用已有 volume
前文提到,删除或停止某个容器后,对应的 volume 不会自动删除;而且不同的容器 可以同用一个相同的 volume 。
进入第三个 MySQL 容器,创建测试数据库
# 进入容器
$ sudo docker exec -it test3_mysql /bin/bash
# 进入 MySQL 终端
root@a86213ba65d0:/# 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.28 MySQL Community Server (GPL)
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
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> create database docker;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| docker |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> exit
Bye
root@a86213ba65d0:/#
创建第四个 MySQL 容器,引用第三个 MySQL 容器的 volume
$ docker run --name test4_mysql -v test3_mysql -d -e MYSQL_ROOT_PASSWORD=password mysql:5.7
进入第四个 MySQL 容器,通过查看数据库信息,确认第四个 MySQL 容器是否引用了第三个 MySQL 容器的 volume 。
$ sudo docker exec -it test4_mysql /bin/bash
root@8b34ea93e59a:/# mysql -uroot -p
Enter password:
ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
root@8b34ea93e59a:/# exit
此时你会发现无法连接到数据库,提示 “Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)”
,我是这样认为的, docker 不仅将数据存储在这个 volume 中,而且 mysqld.sock 也存储在此 volume 中,所以,当你在使用别的容器进行连接这个数据库文件时,就报出这样的错误了。如果仅仅存储数据问题,应该是不会出现这个错误的。(这部分完全自己想的,仅做参考,如有可靠答案,望告知)。
你可以将第三个容器停止后再次进行以上操作,如果你的第四个容器已经停止运行,你可以使用 docker logs
命令来查看容器退出的具体原因,你讲看到如下错误:Check that you do not already have another mysqld process using the same InnoDB data or log files.
,还有其他错误信息,总而言之就是因为资源不可用或者说被占用,导致容器终止运行。
# 停止第三个容器
$ sudo docker stop test3_mysql
test3_mysql
# 启动第四个容器
$ sudo docker start test4_mysql
test4_mysql
# 进入第四个容器
$ sudo docker exec -it test4_mysql /bin/bash
root@8b34ea93e59a:/# 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.28 MySQL Community Server (GPL)
Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.
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 databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| docker |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql>
以上操作可以看出,第四个容器和第三个容器使用的是同一个 volume ,你也可以使用 docker volume ls
来查看 volume ,会看到并没有生成新的 volume ,一共还是三个 volume。
以上便是基于 volume 的数据存储方式。
3、Docker 数据持久化之 bind mounts
3.1、bind mounts 简介
bind mounts 的方式实际上可以理解为将本地目录映射给容器内数据存储的目录,本地目录内的内容发生改变,容器内对应的内容也发生变化,同样的,在容器内操作数据目录,本地的目录文件也会发生相同的变化。
3.2、bind mounts 实验
接下来,通过实际操作体验一下 bind mount 的方式。
3.2.1、运行一个 Nginx 容器
运行一个Nginx容器,将本地 /root/testfile
挂载到容器的 /root/testfile
下,如果本地及容器没有这个目录,则会自动创建。
# 运行一个 Nginx 容器
$ sudo docker run --name test1_nginx -d -P -v /root/testfile:/root/testfile nginx
6ef8b9785f8495865f43454fd1b0a261c4a813703019ec0de7bbfc948a9e7caa
# 查看本地的目录
$ sudo ls
testfile
3.2.2、修改文件测试 bind mount 挂载方式
# 进入容器创建文件并写入测试数据
$ sudo docker exec -it test1_nginx /bin/bash
root@6ef8b9785f84:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@6ef8b9785f84:/# ls /root
testfile
root@6ef8b9785f84:/# cd /root/testfile/
root@6ef8b9785f84:~/testfile# echo "test docker date" >> hello.txt
root@6ef8b9785f84:~/testfile# cat hello.txt
test docker date
# 另开个shell,在本地查看测试文件
$ sudo ls /root/testfile/
hello.txt
$ sudo cat /root/testfile/hello.txt
test docker date
# 修改测试文件
$ sudo echo "test ok" >> hello.txt
$ sudo cat /root/testfile/hello.txt
test docker date
test ok
# 返回容器终端查看测试文件
$ sudo docker exec -it test1_nginx /bin/bash
root@6ef8b9785f84:/# cat /root/testfile/hello.txt
test docker date
test ok
3.3.3、共享数据目录
你可以在运行一个容器,然后通过以上方法测试数据的变化情况,会得出结论:不同的容器,可以挂载相同的数据目录。
# 运行一个新的nginx容器
$ sudo docker run --name test2_nginx -d -P -v /root/testfile:/root/testfile nginx
# 进入容器创建文件并写入测试数据
$ sudo docker exec -it test2_nginx /bin/bash
...操作内容省略...
以上操作可以看出,通过 bind mount 方式,可以实现容器与本地数据文件目录的映射,数据是同步的。
4、Docker 数据管理之tmpfs
tmpfs
挂载仅存储在主机系统的内存中,并且永远不会写入主机系统的文件系统中。
tmpfs
挂载不会持久化在磁盘上,无论是在Docker主机上还是在容器内。容器在其生存期内可以使用它来存储非持久状态或敏感信息。
绑定 volume 和 bind mount 都可以使用-v
或 --volume
标志安装到容器中,但是两者的语法略有不同。对于tmpfs
,使用--tmpfs
,此方式不太常用,就不说了,感兴趣可以去官网或者找资料学习。
5、总结
5.1、volume 的使用场景
volume是将数据持久保存在Docker容器和服务中的首选方法。volume的一些使用场景:
- 在多个运行中的容器之间共享数据。如果未显式创建volume,则在首次运行容器中时创建volume,当该容器停止或卸下时,该 volume 仍然存在,多个容器可以同时使用相同的volume(可读写或只读);
- 支持将容器的数据存储在远程主机或云上;
- 当您需要将数据从一个Docker主机备份、还原或迁移到另一个Docker主机时,volume 是一个更好的选择,您可以停止使用该卷的容器,然后备份该卷的目录(例如
/var/lib/docker/volumes/<volume-name>
)。
5.2、bind mounts 的使用场景
通常,应尽可能使用 volume,bind mounts 适用于以下场景:
- 将配置文件从主机共享到容器,默认情况下,这就是Docker通过
/etc/resolv.conf
从主机安装到每个容器的方式为容器提供DNS解析的方式 。 - 在Docker主机上的开发环境和容器之间共享源代码或构建工件。例如,您可以将Maven
target/
目录 mount 到容器中,这样每次在 Docker 主机上构建Maven项目时,容器都可以访问重建的工件。 - 当确保Docker主机的文件或目录结构与容器所需的一致时。
5.3、tmpfs挂载的好用例
当您不希望数据在主机上或容器内持久存在时,当您的应用程序需要写入大量非持久状态数据时,mpfs挂载最适合使用;这是出于安全原因或为了保护容器的性能。
参考链接:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。