1、Docker 数据管理简介

        默认情况下,在容器内创建的所有文件都存储在可写容器层上。这意味着:

  • 当该容器不再存在时,数据将不会持久保存,并且如果另一个进程需要它,则可能很难从容器中取出数据;
  • 容器的可写层与运行容器的主机紧密耦合,不能轻易地将数据移动到其他地方;
  • 写入容器的可写层需要 存储驱动程序来管理文件系统。存储驱动程序使用Linux内核提供联合文件系统,与使用直接写入主机文件系统的数据卷相比,这种额外的抽象降低了性能 。

        Docker为容器提供了两种方案来将文件存储在主机中,以便即使容器停止后文件也可以持久存储:volumesbind 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挂载最适合使用;这是出于安全原因或为了保护容器的性能。


参考链接:

https://docs.docker.com/storage/

https://blog.csdn.net/hujyhfwfh2/article/details/83926465


Liu_wt
35 声望3 粉丝

业余选手,多多指教!