docker 的数据卷挂载volume(持久化)及其意义(三种方式--volume,自定义volume,volume继承)

docker中有许多新的概念,其实也就因为这些新的概念使得学习docker有很多难度,比如说,docker的三要素 仓库,镜像,容器中的仓库是老概念,比较好理解,而镜像和容器对新手来说就很难理解了。

在镜像启动为容器这个阶段,docker需要定义镜像如何使用(比如,必须的环境变量,配置文件的剥离,共享,以方便的定义如何启动镜像)和定义启动为容器后的网络通信形式(比如,是bridge还是nat还是host形式与外界通信,当然,不指定的情况下就默认使用最常使用的bridge模式了,但,桥接模式的IP地址会发生动态的变化,而这作为一个服务来说通常是不可接受的),docker的网络配置是由使用目的来决定的,比较复杂,这不在本文讨论范围,现在要讨论的是容器与宿主机共享数据的问题(也就是通过volume来共享)。

数据共享使得各种集群的部署成为可能,同样的,docker通过镜像所启动的每一个实例(一般将这个实例叫做容器)可以看做一个单独的个体,因为它具有完整的单独的操作系统的大部分特质,这些容器通过它自己所搭建的网络进行通信,互相传递消息,从而使得集群的搭建成为可能。

那么,现在问题来了,如果我有一个镜像,该镜像是一个MySQL5.7的镜像,将它启动为一个容器后,发现这个镜像里的MySQL使用的字符集是utf8,现在我想将数据库使用的字符集更改为utf8mb4,怎么办?

如果是在物理机安装的MySQL,遇到上述问题,我们通常的做法是在MySQL的主配置文件 my.cnf 文件中指定使用哪个字符集并重启MySQL服务使之生效即可,对于docker容器,当然也可以使用这种方法,进入容器-->找到配置文件-->修改文件并保存-->重启容器内的服务(很多容器并没有安装vi,vim这些文本编辑器,还需要自己手动安装~!~~!!!),可是如果一会又有了别的需求,比如,容器运行的MySQL服务缓慢,需要排查什么原因,这个时候怎么办呢?  还是重复上面的步骤  进入容器-->找到日志文件-->排查原因-->找到原因后修复问题?毫无疑问,这样是低效的方法。正是由于这些原因,docker提出了volume这个概念。

如果,能将容器内的所需要经常修改或者监控的文件放在宿主机并与容器内所在的原文件同步,那么这些痛点是不是就不是痛点了呢(也就是相互映射)?答案是肯定的,docker的volume就是互相映射宿主机和容器的文件的(比如将容器内的文件 A映射到了宿主机的家目录下,也就是家目录也有一个A文件,在容器内修改A文件,在宿主机家目录下的A文件也同时改变,同理,宿主机的A改变了,容器的A文件也改变 了,这样的情况,我们就称之为映射)。实现volume的前提是宿主机和容器可以互相的双向通信哦。

以上是docker  volume的意义,下面讲述 volume的实现方式


volume的实现分为两种基本模式:volume 模式,也就是默认的模式和bind模式,也就是绑定模式。和一种简洁模式,tmpfs模式。

本文示例使用的镜像为hub.c.163.com/library/mysql:latest,新手请在安装完docker后执行  docker pull hub.c.163.com/library/mysql:latest  命令

[root@centos7 ~]# docker images
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
postgres                      latest              f51c55ac75ed        2 weeks ago         314MB
adminer                       latest              476c78d02a95        4 weeks ago         89.9MB
hub.c.163.com/library/mysql   latest              9e64176cd8a2        3 years ago         407MB

一,=====================Volume模式================================

[root@centos7 ~]# docker run -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v /var/lib/mysql --name mysql_test hub.c.163.com/library/mysql:latest
6e04a2504b4d523cae3ea36787806da8dc8417242dec81f411ad216353045924

启动了一个新容器,后台模式,volume模式,mysqlroot密码为123456,端口映射3306,容器名称为mysql_test

[root@centos7 ~]# docker ps -a
CONTAINER ID        IMAGE                                COMMAND                  CREATED             STATUS              PORTS                    NAMES
6e04a2504b4d        hub.c.163.com/library/mysql:latest   "docker-entrypoint.s…"   4 minutes ago       Up 4 minutes        0.0.0.0:3306->3306/tcp   mysql_test
[root@centos7 ~]# docker inspect 6e04a
#输出做了省略,截取的需要关心的那一部分
"Mounts": [
            {
                "Type": "volume",
                "Name": "27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a",
                "Source": "/var/lib/docker/volumes/27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a/_data",
                "Destination": "/var/lib/mysql",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }

请关注一下  "Type": "volume", "Source": "/var/lib/docker/volumes/27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a/_data",
                "Destination": "/var/lib/mysql", 这三个字段,volume模式只需要在-v后 加要挂载的容器内的文件绝对路径,source的值指的是宿主机的挂载点路径,destination的值指向的是容器内部的mysql 数据库存放目录。

[root@centos7 ~]# cd -
/var/lib/docker/volumes/27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a/_data
[root@centos7 _data]# ll
total 188484
-rw-r----- 1 polkitd ssh_keys       56 Dec  4 23:13 auto.cnf
-rw------- 1 polkitd ssh_keys     1675 Dec  4 23:13 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys     1074 Dec  4 23:13 ca.pem
-rw-r--r-- 1 polkitd ssh_keys     1078 Dec  4 23:13 client-cert.pem
-rw------- 1 polkitd ssh_keys     1679 Dec  4 23:13 client-key.pem
-rw-r----- 1 polkitd ssh_keys     1321 Dec  4 23:13 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 Dec  4 23:13 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 Dec  4 23:13 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 Dec  4 23:13 ib_logfile1
-rw-r----- 1 polkitd ssh_keys 12582912 Dec  4 23:13 ibtmp1
drwxr-x--- 2 polkitd ssh_keys     4096 Dec  4 23:13 mysql
drwxr-x--- 2 polkitd ssh_keys     8192 Dec  4 23:13 performance_schema
-rw------- 1 polkitd ssh_keys     1675 Dec  4 23:13 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys      451 Dec  4 23:13 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys     1078 Dec  4 23:13 server-cert.pem
-rw------- 1 polkitd ssh_keys     1679 Dec  4 23:13 server-key.pem
drwxr-x--- 2 polkitd ssh_keys     8192 Dec  4 23:13 sys

可以看到,27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a是一个65位的字符串,这个是由docker自动生成的,如果要在宿主机管理这些文件是十分不方便的,并且这样的一串也不是见名知意,如果后期挂载更多的目录,管理就会混乱了。那么,有什么好的办法解决这样的问题呢?

当然有了,自定义宿主机挂载点的目录的名称即可。

[root@centos7 mysql.conf.d]# docker volume list
DRIVER              VOLUME NAME
local               15d6e03f521c068b63287d63a55c2abe38d57ebfa081577e9012002c13943c32
local               27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a
[root@centos7 mysql.conf.d]# docker volume create mysql
mysql
[root@centos7 mysql.conf.d]# docker volume list
DRIVER              VOLUME NAME
local               15d6e03f521c068b63287d63a55c2abe38d57ebfa081577e9012002c13943c32
local               27fe35c28d6b073bbc8638d6172ea53fcf6de9c5918f400ea5483b7ef3afa30a
local               mysql
#建立了一个自定义的volume ,名字叫mysql,下面就使用它,看看会有什么变化
[root@centos7 _data]# docker rm -f $(docker ps -qa)
50fbc82dbfa8
6e04a2504b4d
[root@centos7 _data]# docker run -itd -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -v mysql:/var/lib/mysql --name mysql_test hub.c.163.com/library/mysql:latest
85c4316cd729bac756dd92d7fb6d172cbddb59654f3a8aa1255f25b6e4502632
[root@centos7 _data]# pwd
/var/lib/docker/volumes/mysql/_data
[root@centos7 _data]# ls
auto.cnf    ca.pem           client-key.pem  ibdata1      ib_logfile1  mysql               private_key.pem  server-cert.pem  sys
ca-key.pem  client-cert.pem  ib_buffer_pool  ib_logfile0  ibtmp1       performance_schema  public_key.pem   server-key.pem
[root@centos7 _data]# docker inspect 85c43
#输出省略部分内容,截取需要的部分
"Mounts": [
            {
                "Type": "volume",
                "Name": "mysql",
                "Source": "/var/lib/docker/volumes/mysql/_data",#宿主机的目录
                "Destination": "/var/lib/mysql",#容器的目录
                "Driver": "local",#驱动是本地驱动
                "Mode": "z",#相比第一个默认,这有一个值哦
                "RW": true,   #注意了,这个地方表示可读写,启动的时候可以指定是rw还是ro只读
                "Propagation": ""
            }

 

可以看到,启动容器的时候 -v后指定了使用自定义的volume    mysql(自定义的名字就是mysql),与容器的目录之间用:分隔开就可以啦。/var/lib/docker/volumes/mysql/_data这样的形式就很好接受了,见到这个就知道是mysql所使用的volume,并且映射的内容也都有了(也就是容器的数据库所有文件)。

一般推荐使用自定义的volume名称,这样不会造成管理上的混乱。(-v 容器内需要映射的目录,绝对路径形式书写  或者 -v  自定义的volume:容器内需要映射的目录,绝对路径形式书写)

也可以这样写:

[root@centos7 _data]# docker run -itd -p 3310:3306  --mount src=mysql,dst=/var/lib/mysql  --name mysql1 -e MYSQL_ROOT_PAAWORD=123456  hub.c.163.com/library/mysql:latest
16150e02d1186e671443d00546a8230b466393301078c5028af3529ce9565917
#原来的旧容器没有删除,因此改了端口和容器名称。-v 改为了--mount, 路径用src和dst指定了

 

二,bind mounts 模式,

删除所有volume的命令为:docker volume rm -f $(docker volume ls -q)

删除所有容器的命令为:docker rm -f $(docker ps -qa)  ,先执行这两个命令恢复原始环境,防止其它的干扰。

bind模式就简单了,

[root@centos7 data]# docker run -itd -p 3306:3306 --name mysql -v /opt/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD hub.c.163.com/library/mysql
1c3e7a9160d8116f9af5aceb8a52746e29967d49d5ec64adb25266ff107c469d
[root@centos7 data]# docker inspect 1c2e7
#部分输出省略
Mounts": [
            {
                "Type": "bind",
                "Source": "/opt/mysql/data",
                "Destination": "/var/lib/mysql",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }

"Source": "/opt/mysql/data" 这个目录不用事先创建,会自动帮你在宿主机创建好的哦。可以看到type变为了bind。-v 后面 的:左边是宿主机的目录,右边是容器的目录

也可以这样写:指定挂载模式为bind

[root@centos7 data]# docker run -itd -p 3306:3306 --name mysql --mount type=bind,src=/opt/mysql/data,dst=/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456  hub.c.163.com/library/mysql
9f87a7076d15c0f12e4ba080c12f82154f89ef7b99fa51e4d99eecf6c6dbe52f

三,volume的继承

继承的先决条件是父容器是运行状态的,前述命令docker run -itd -p 3306:3306 --name mysql --mount type=bind,src=/opt/mysql/data,dst=/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456  hub.c.163.com/library/mysql 生成的容器作为父容器,现在在启动一个容器,作为子容器继承它的volume。

[root@centos7 mnt]# docker run -itd --name mysql1 -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 --volumes-from mysql hub.c.163.com/library/mysql:latest
a19de7c250dc11f98fe7e2266dd28e0b21e519a819b1af48c996ec3388f67c47
#-volumes-from mysql  的MySQL指的是父容器的名称


#查看这个新启动的子容器挂载详情
[root@centos7 mnt]# docker inspect a19de
#省略部分输出
 "Mounts": [
            {
                "Type": "bind",
                "Source": "/opt/mysql/data",
                "Destination": "/var/lib/mysql",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],

以上仅仅是举例,由于MySQL并不允许两个MySQL或者多个MySQL共用一套数据库文件,因此,子容器并不会启动成功。需要说明的是,父容器如果挂载了多个volume,那么继承的时候,子容器是全部继承的哦,在实际应用中可以启动一个无关紧要的父容器仅仅作为内容分发共享的作用,

四,临时挂载volume

[root@centos7 mnt]# docker rm -f $(docker ps -aq)
53e828d698ea
4f59a8aafa0b
[root@centos7 mnt]# docker run -itd -p 3306:3306 --name mysql --mount type=tmpfs,dst=/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456  hub.c.163.com/library/mysql
a803ce977d9d6fe3d620edcafc2ae5c3a05f90125a458178f5aa724285470767
[root@centos7 mnt]# docker inspect a803c
#省略部分输出
  "Mounts": [
            {
                "Type": "tmpfs",
                "Source": "",
                "Destination": "/var/lib/mysql",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],

#指定挂载方式为tmpfs即可

tmpfs mounts,顾名思义,是一种非持久化的数据存储。它仅仅将数据保存在宿主机的内存中,一旦容器停止运行,tmpfs mounts会被移除,从而造成数据丢失。因此,使用该模式需要保证宿主机的内存够大,并且没有数据持久化的需求时使用。

总结(这是精华):

新建volume的命令为:

docker volume create 你想要的自定义名称,比如建立一个volume叫v2的

[root@centos7 mnt]# docker volume create v2
v2

删除所有volume的命令为:docker volume rm -f $(docker volume ls -q)

删除单个的volume:

[root@centos7 mnt]# docker volume ls
DRIVER              VOLUME NAME
local               e864e693a065106ab5db0308eaad83f55a144ab07af4e5177706e3ab2dfd88fe
local               v2
[root@centos7 mnt]# docker volume rm e864e693a065106ab5db0308eaad83f55a144ab07af4e5177706e3ab2dfd88fe
e864e693a065106ab5db0308eaad83f55a144ab07af4e5177706e3ab2dfd88fe
[root@centos7 mnt]# docker volume ls
DRIVER              VOLUME NAME
local               v2

 删除所有容器的命令为:docker rm -f $(docker ps -qa) ,和上面的命令是不是特别像??????注意一点,正在运行的容器中的volume是不允许删除的,直到容器停止运行。

使用volume的命令为:

 docker run -itd -p 3306:3306 --name mysql --mount type=bind,src=/opt/mysql/data,dst=/var/lib/mysql  -e MYSQL_ROOT_PASSWORD=123456  hub.c.163.com/library/mysql

--mount type=【bind,tmpfs,或者不写默认volume】,宿主机的绝对路径,容器的绝对路径。

bind模式下,如果容器的路径目录内是有文件的,将会被宿主机的文件覆盖隐藏,这个一定要注意。本例是因为容器的目录 /var/lib/mysql/  是在启动后经过初始化才生成那些数据库文件,也就是说镜像内是没有那些文件的。启动转换成容器后,如果有挂载bind模式volume,容器内的原文件将被宿主机的挂载目录内文件覆盖,如果宿主机目录为空,那么容器目录也为空,绝对的强一致。

基于以上原因,常使用的volume模式为默认模式。默认模式的挂载点在 /var/lib/docker/volumes/  下。bind模式是高度自定义的,但会产生覆盖隐藏的问题的模式。

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐