Docker 基础修炼 5-- 容器数据共享和持久化实战

用户头像
黑马腾云
关注
发布于: 2020 年 07 月 21 日
Docker基础修炼5--容器数据共享和持久化实战

前面文章讲解了Docker最基本的概念:容器、镜像、仓库相关知识,本文继续演示Docker容器中数据持久化相关的知识。



本文主要分析为什么需要将Docker容器中的数据持久化、数据持久化有什么作用以及演示如何通过数据卷和数据卷容器实现Docker容器数据持久化。



一、Docker数据共享和持久化



顾名思义,数据持久化就是把数据长期保存起来,当以后有需要的时候能够拿来使用。个人认为软件的本质就是输入数据、计算分析数据、输出数据,简单说软件就是处理数据,要么生产数据、要么分析数据。由此可见数据的重要性,因此我们需要将重要数据保存起来。



如果大家用过Mysql、SQL Server、Redis等数据库,则一定会知道他们的数据都会以文件的形式存储在计算机磁盘中,并且管理人员会定期对数据进行备份以防止各种原因导致的数据丢失。



同样的道理,Docker容器中既然运行的程序,那就一定会产生数据,而这些数据,我们需要将其长期保存起来,这个数据保存的过程就是数据持久化的过程。



Docker容器中数据持久化是通过卷实现的。那么什么是卷呢?简单一句话可以理解为卷就是插在电脑上的一个移动硬盘,它类似于redis里边的rdb和aof文件。



1.1 容器数据为什么要持久化



前文介绍过Docker设计的理念,它将应用程序与运行该程序的环境打包形成容器运行。运行可以伴随着容器,但我们对数据的要求是希望能持久化,特别是有些重要的数据,我们希望能够长期保存起来。



另外我们有时也需要在容器之间共享数据。 docker容器产生的数据,如果不重新commit成新镜像,使之成为镜像的一部分保存下来,那么当容器删除后,数据自然就没有了。



虽然我们可以通过docker cp命令将容器内的数据拷贝到宿主机,但如果容器分布集群中不同的节点上这显然是办不到的,因此需要通过下文介绍的数据卷和数据卷容器来实现数据的共享和传递。



docker cp 命令可以在宿主机和docker容器之间相互拷贝数据,无论容器是停止状态还是运行状态。具体使用可以通过帮助命令进行查看。



1.2 Docker数据持久化的方法



通常使用数据卷和数据卷容器来实现docker中数据的共享和传递。数据卷相当于是移动硬盘,而数据卷容器相当于是继承。



添加数据卷有两种方式:一种是在创建容器时指定,通过docker run命令创建容器的时候通过-v参数指定数据卷或在docker compose文件中通过volume指定指定;另外一种则是在创建镜像时指定,通过DockerFile文件使用volume指令指定数据卷。



说明:如果你是初次使用docker,对于docker compose文件、dockerfile等这些概念可能不太明白,这里先有个大概的印象即可,等继续后文的学习后返回来再看一定会更加明白。



二、数据卷有什么作用



既然docker容器数据持久化主要是通过数据卷来实现,因此我们先了解下数据卷的特点和作用。



2.1 数据卷作用



(1)容器的持久化



(2)容器间继承和数据共享 卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但它不属于联合文件系统,因此能绕过UnionFS提供一些用于持续存储和共享数据的特性



卷的设计目的就是为了数据的持久化,完全独立与容器的生存周期,因此docker不会在容器删除时删除其挂载的数据卷



2.2 数据卷特点



(1)容器之间共享或重用数据



(2)卷中的更改可以直接生效



(3)卷中的更改不会包含在镜像的更新中



(4)数据卷的生命周期一直持续到没有容器使用他为止



以上这些特点光看以上的文章总结,难以加深印象,一定要自己多敲命令去试验、去归纳,如果不自己亲自验证很快就忘记了。接下来将演示具体的这些特性以及如何将docker容器内的数据持久化到宿主机。



三、数据持久化实战



3.1 数据卷



数据卷可以通过-v命令添加,也可以在dockerfile中指定。



3.1.1 命令方式添加卷



  • 案例1:创建容器时添加数据卷,实现数据共享和持久化



本例以官方提供的centos镜像为例,演示数据卷的使用



(1)创建带数据卷的容器



命令格式:



docker run -it -v /宿主机绝对路径目录:/容器内目录 镜像名



[root@docker ~]# docker run -it --name case1 -v /case1:/mycontainer/case1 centos
[root@bd821370117a /]# hostname
bd821370117a
[root@bd821370117a /]# ls -d mycontainer/case1/
mycontainer/case1/
[root@bd821370117a /]# exit
exit
[root@docker ~]# ls -d /case1/
/case1/
[root@docker ~]#



指定宿主机数据卷目录为/case1,容器内数据卷目录/mycontainer/case1。这两个目录事先可以不存在,命令执行后自动创建。



(2)查看数据卷是否挂载成功



可以通过以下方式查看数据卷是否挂载成功:



  • 直接查看目录是否创建



从上面的ls命令可以看到容器内自动创建了mycontainer/case1/目录,同时在宿主机也可以看到在根目录下自动创建了/case1目录。



这充分说明:宿主机和容器内的目录都可以不存在,会自动创建。



  • 通过docker inspect命令查看



除了直接查看对应的目录,还可以通过 docker inspect命令查看容器相关信息



[root@docker ~]# docker inspect case1
...省略部分输出
"Mounts": [
{
"Type": "bind",
"Source": "/case1",
"Destination": "/mycontainer/case1",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
...省略部分输出



可以看到宿主机与容器内目录的关联关系,他们就像是同一个移动硬盘,之后往任意一个目录存放数据,另外一个目录都可以立即查看到。



同时我们看到RW为tue,表示对应目录拥有读写权限,可以在目录内进行文件的读写操作。



(3) 容器与宿主机之间数据共享



[root@docker ~]# cd /case1/
[root@docker case1]# echo 'create by host'>host.txt
[root@docker case1]# ls
host.txt
[root@docker case1]# docker exec -it case1 /bin/bash
[root@bd821370117a /]# ls /mycontainer/case1/
host.txt
[root@bd821370117a /]# exit
exit
[root@docker case1]#



宿主机创建文件host.txt,然后去容器对应的数据卷目录查看,就可以看到刚才宿主机创建的文件,无需重启就生效。



接下来在容器内修改数据,观察宿主机是否会立即生效。



[root@docker case1]# docker exec -it case1 /bin/bash
[root@bd821370117a /]# echo 'create by container'>/mycontainer/case1/container.txt
[root@bd821370117a /]# ls /mycontainer/case1/container.txt
/mycontainer/case1/container.txt
[root@bd821370117a /]# exit
exit
[root@docker case1]# ls /case1/
container.txt host.txt
[root@docker case1]#



在容器内创建container.txt文件后退回到宿主机,同样看到了刚才在容器内创建的文件。



这说明通过卷的映射,只要宿主机或容器卷目录的数据发送变化,都会立即同步,相互影响。



通过这个功能,我们就可以把容器内程序产生的数据,直接映射到宿主机上,即使以后容器删除了,数据依然存储在宿主机上,在一定程度上保证了数据不丢失。



(4)容器停止退出后,主机修改后的数据是否同步



我们在来思考一个问题,但容器停止后,宿主机对应的卷目录内数据发生变化,当容器再次重启时是否会自动更新这些变化呢?



[root@docker case1]# docker stop case1
case1
[root@docker case1]# echo 'add a file from host'>host1.txt
[root@docker case1]# ls /case1/
container.txt host1.txt host.txt
[root@docker case1]# docker start case1
case1
[root@docker case1]# docker exec -it case1 /bin/bash
[root@bd821370117a /]# ls /mycontainer/case1/
container.txt host.txt host1.txt
[root@bd821370117a /]# exit
exit
[root@docker case1]#



通过上边的实验可以很清楚的看到,修改主机卷内容后再次启动容器,可以看到宿主机修改的数据自动同步到了容器中。



通过这个功能,当我们想向容器内发送数据时(比如更新后的程序包),就可以通过这种方式实现。



  • 案例2:创建容器时添加数据卷并指定读写权限控制



本例以官方提供的centos镜像为例,演示带读写权限的数据卷的使用



(1)创建带数据卷的容器



权限通过ro指定,即readonly的意思。



命令格式:



docker run -it -v /宿主机绝对路径目录:/容器内目录:ro 镜像名



[root@docker case1]# docker run -it --name case2 -v /case2:/mycontainer/case2:ro centos
[root@bb5dd47f2174 /]# ls /mycontainer/case2/
[root@bb5dd47f2174 /]# exit
exit
[root@docker /]# ls /case2/
[root@docker /]#



可以看到数据卷创建成功,接下来验证下数据读写。



(2)验证数据读写



[root@docker /]# echo 'create by host'>/case2/chost.txt
[root@docker /]# ls /case2/
chost.txt
[root@docker /]# docker start case2
case2
[root@docker /]# docker exec -it case2 /bin/bash
[root@bb5dd47f2174 /]# ls /mycontainer/case2/
chost.txt
[root@bb5dd47f2174 /]# echo 'create by container'>/mycontainer/case2/container.txt
bash: /mycontainer/case2/container.txt: Read-only file system
[root@bb5dd47f2174 /]# exit
exit



通过此实验可以很清楚的看到,主机可以创建文件,但是容器不能增加文件。



这就相当于是单向的,类似于移动硬盘开启写保护,容器内只能查不能修改和删除。



再次通过docker inspect看,可以看到Mode为read only,RW为false。



[root@docker /]# docker inspect case2
...省略部分内容
"Mounts": [
{
"Type": "bind",
"Source": "/case2",
"Destination": "/mycontainer/case2",
"Mode": "ro",
"RW": false,
"Propagation": "rprivate"
}
]
...省略部分内容



3.1.2 通过dockerfile方式添加卷



  • dockerfile是什么?



镜像模板的源代码描述文件。类似于java程序的.java文件,右面的文章会专门对dockerfile进行深入的讲解,此处先有个印象即可。



  • 案例3:通过dockerfile创建数据卷实现数据共享



(1)编写dockerfile文件



创建目录并进入,创建dockerfile文件mycentos



[root@docker /]# mkdir /dockerfile
[root@docker /]# cd dockerfile/
[root@docker /]# vi mycentos

文件内容如下:



FROM centos
VOLUME ["/case3"]
CMD echo "finished build my image"
CMD /bin/bash

说明:



在dockerfile中指定卷通过volume指令,但是它不能实现-v参数一样指定宿主机目录。原因是从移植性和共享重用考虑,由于宿主机目录是依赖于特定宿主机信息,并不能保证所有宿主机都存在这样的特定目录,所以官方这样规定的。dockefile可以翻译为docker run等价指令,类似于:docker run --name xxx -it -v /floder:/case3 centos。



(2)build生成镜像 语法格式:docker build -f 文件名 -t 镜像名 .



[root@docker dockerfile]# docker build -f mycentos -t mycentos:1.0 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 470671670cac
Step 2/4 : VOLUME ["/case3"]
---> Running in e620a5150cab
Removing intermediate container e620a5150cab
---> 34654b4850ad
Step 3/4 : CMD echo "finished build my image"
---> Running in 3af8dd707f96
Removing intermediate container 3af8dd707f96
---> c898493c201a
Step 4/4 : CMD /bin/bash
---> Running in afa1cf378a59
Removing intermediate container afa1cf378a59
---> 3c8f64b26b81
Successfully built 3c8f64b26b81
Successfully tagged mycentos:1.0
[root@docker dockerfile]# docker images mycentos
REPOSITORY TAG IMAGE ID CREATED SIZE
mycentos 1.0 3c8f64b26b81 About a minute ago 237MB
[root@docker dockerfile]#

镜像名称为mycentos,版本号为1.0。这样就成功创建了一个自己的镜像。接下来就基于这个自定义镜像运行容器。



(3)run容器



[root@docker dockerfile]# docker run -it --name case3 mycentos:1.0
[root@47b7fb0b15fc /]# ls /case3/
[root@47b7fb0b15fc /]# cd case3/
[root@47b7fb0b15fc case3]# echo 'create by container'>container.txt
[root@47b7fb0b15fc case3]# ls
container.txt
[root@47b7fb0b15fc case3]# exit
exit
[root@docker ~]#

创建容器,并看见自动创建了在dockerfile中指定的case3卷路径。我们在该卷下创建文件,然后返回宿主机观察是否能看到此文件。



(4)查看数据卷



前面提到通过dockerfile指定卷,只能指定容器内部的路径,那对应的宿主机是哪个路径呢?我们仍然可以通过docker inspect命令来查看。



[root@docker dockerfile]# docker inspect case3
...省略部分输出
"Mounts": [
{
"Type": "volume",
"Name": "f1a17ec4a0b4f27a87bb9681bc2382685ce704bce44b880836221ef211ef7cc9",
"Source": "/var/lib/docker/volumes/f1a17ec4a0b4f27a87bb9681bc2382685ce704bce44b880836221ef211ef7cc9/_data",
"Destination": "/case3",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
]
...省略部分输出

从Source中可以看到宿主机的路径为:/var/lib/docker/volumes/f1a17ec4a0b4f27a87bb9681bc2382685ce704bce44b880836221ef211ef7cc9/_data ,这个路径是自动生成的。



[root@docker dockerfile]# cd /var/lib/docker/volumes/f1a17ec4a0b4f27a87bb9681bc2382685ce704bce44b880836221ef211ef7cc9/_data
[root@docker _data]# ls
container.txt
[root@docker _data]#

可以看到在容器内创建的文件,在宿主机也可以查看 ,同样在宿主机创建文件在容器内也可以看见。



这就通过dockerfile的形式为容器实现与宿主机数据传递与共享。



3.2 数据卷容器



3.2.1 数据卷容器是什么



容器内挂载数据卷,其他容器通过挂载这个父容器实现数据共享,挂载数据卷的容器就称为数据卷容器。



通过数据卷容器可以实现容器间的数据传递依赖。容器间传递共享通过在创建容器时使用--volumes-from参数指定。



3.2.2 案例实战



  • 案例4:通过数据卷容器实现数据共享



我们采用通过以上dockerfile创建的自定义镜像mycentos来演示数据卷容器的使用,基于此镜像创建3个容器c1、c2、c3,其中c2和c3均继承于c1,然后观察他们卷的关系。



(1)先创建一个容器c1,并在卷中新加内容



[root@docker /]# docker run -it --name c1 mycentos:1.0
[root@7e5549908ab6 /]# ls /case3/
[root@7e5549908ab6 /]# echo 'created by c1'>/case3/c1.txt
[root@7e5549908ab6 /]# ls /case3/
c1.txt
[root@7e5549908ab6 /]# exit
exit
[root@docker /]#

在卷中添加c1.txt,方便在其他容器中观察是否会共享成功。



(2)创建容器c2继承于父容器c1



接下来创建c2容器继承c1,观察能否看到c1容器中创建的c1.txt



[root@docker /]# docker run -it --name c2 --volumes-from c1 mycentos:1.0
[root@ecd6267595c9 /]# ls /case3/
c1.txt
[root@ecd6267595c9 /]# echo 'create by c2'>/case3/c2.txt
[root@ecd6267595c9 /]# exit
exit
[root@docker /]#

可以看到在c2中可以看到从c1继承来的文件c1.txt,说明是可以传递依赖的。同时此容器再添加一个文件c2.txt,然后在继续后边观察,验证不直接继承c1的容器是否也能看到此文件。



(3)再创建容器c3也继承父容器c1



[root@docker /]# docker run -it --name c3 --volumes-from c1 mycentos:1.0
[root@139d6940d269 /]# ls /case3/
c1.txt c2.txt
[root@139d6940d269 /]# exit
exit
[root@docker /]#

可以看到在c3容器中可以看到前2个容器的创建的文件,也就是说c2和c3中创建的数据,由于与c1有卷的关联,在他们中任意一个容器中创建的文件都可以在其他的容器中共享。



(4)各个容器内观察卷内数据



[root@docker /]# docker exec -it c1 /bin/bash
[root@7e5549908ab6 /]# ls /case3/
c1.txt c2.txt
[root@7e5549908ab6 /]# exit
exit
[root@docker /]# docker start c2
c2
[root@docker /]# docker exec -it c2 /bin/bash
[root@ecd6267595c9 /]# ls /case3/
c1.txt c2.txt
[root@7e5549908ab6 /]# exit
exit

观察父容器c1,看到了不同子容器c2、c3创建的文件c2.txt、c3.txt,这充分说明达到了共享,传递的目的。观察所有容器数据卷内容都有,他们都相同,相当于是有了多份副本一样。



需要说明的是,正如上边示例,通过exit命令退出容器后,容器就停止了,然而即便是容器停止了,卷仍然可以保存数据,当下次启动容器时,数据依然能看到。



  • 案例5:删除数据卷容器观察子容器中数据卷的影响



(1)删除父容器c1,观察子容器中数据情况



[root@docker /]# docker rm -f c1
c1
[root@docker /]# docker exec -it c2 /bin/bash
[root@ecd6267595c9 /]# ls /case3/
c1.txt c2.txt
[root@ecd6267595c9 /]# exit
exit
[root@docker /]# docker exec -it c3 ls /case3/
c1.txt c2.txt
[root@docker /]#

可以看到即使c1容器删除后,子容器c2、c3数据依然存在。



(2)在c2中创建文件,观察c3是否同步



[root@docker /]# docker exec -it c2 /bin/bash
[root@ecd6267595c9 /]# echo 'another file'>/case3/myfile.txt
[root@ecd6267595c9 /]# ls /case3/
c1.txt c2.txt myfile.txt
[root@ecd6267595c9 /]# exit
exit
[root@docker /]# docker exec -it c3 ls /case3/
c1.txt c2.txt myfile.txt
[root@docker /]#

在c2中新建文件myfile.txt,再观察c3容器,仍然看到了myfile.txt文件,这说明容器之间的数据共享没受损坏。



如果你继续删除c2再去观察c3,文件依然存在,再次就不继续演示了。



最后的结论:数据卷的生命周期一直持续到没有容器使用它为止。



本篇演示了容器持久化的相关方式,下一篇文件将演示容器之间通信。





自学帮



发布于: 2020 年 07 月 21 日 阅读数: 93
用户头像

黑马腾云

关注

自学帮号主,致力于帮助初学者提升IT技能。 2020.06.22 加入

程序猿,持续创业者。

评论

发布
暂无评论
Docker基础修炼5--容器数据共享和持久化实战