前几篇Blog是对Docker的一个入门和初识,本篇Blog开始就详细学习下一个新的理论基础概念:Volume,也就是容器数据卷,听起来名字高大上,实际上就是一个宿主机的目录而已,为什么需要容器数据卷呢,可以类比Redis来理解,Redis运行在内存中,一旦停了数据都丢了怎么办?只能通过持久化的手段,Redis的持久化方案是RDB+AOF,感兴趣的可以看看我这篇Blog【Redis核心知识 二】Redis持久化策略,容器虽然没有那么脆弱,停了数据还是在的,但是容器删起来也很容易啊,删了数据就没有了,问题来了,如果容器运行着一个mysql服务,不小心一删,那不直接删库跑路了么,所以容器也需要持久化,把数据持久化到宿主机磁盘,实现方式就是通过容器数据卷机制。
数据卷基本概念
Docker中的数据可以存储在类似于虚拟机磁盘的介质中,在Docker中称为数据卷(Data Volume)。数据卷可以用来存储Docker应用的数据,也可以用来在Docker容器间进行数据共享。数据卷呈现给Docker容器的形式就是一个目录,支持多个容器间共享,修改也不会影响镜像。使用Docker的数据卷,类似在系统中使用mount 挂载一个文件系统
容器数据卷的特性
Docker Volume数据卷说白了就是从容器到宿主机直接创建一个文件目录映射,宿主机创建的容器卷可以挂载到任何一个容器上,它有以下特点:
- 绕过UFS系统,以达到本地磁盘IO的性能,比如运行一个容器,在容器中对数据卷修改内容,会直接改变宿主机上的数据卷中的内容,所以是本地磁盘IO的性能,而不是先在容器中写一份,最后还要将容器中的修改的内容拷贝出来进行同步
- 绕过UFS系统,有些文件例如容器运行产生的数据文件不需要再通过
docker commit
打包进镜像文件 - 数据卷可以在容器间共享和重用数据
- 数据卷可以在宿主和容器间共享数据
- 数据卷数据改变是直接修改且实时同步的
- 数据卷是持续性的,直到没有容器使用它们。即便是初始的数据卷容器或中间层的数据卷容器删除了,只要还有其他的容器使用数据卷,那么里面的数据都不会丢失
可以理解这么理解,宿主机的数据卷目录和容器里映射的目录在宿主机磁盘上是一个地址,容器里的目录类似快捷方式。
数据卷基本命令
数据卷的常用命令如下,还是老方法,通过--help
去获取volume可进行的操作:
[root@192 _data]# 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. [root@192 _data]#
1 创建数据卷
在宿主机创建一个数据卷:
[root@192 /]# docker volume create tml-volume tml-volume [root@192 /]#
2 查看所有数据卷
可以通过如下命令查看现在所有存在的数据卷:
[root@192 /]# docker volume ls DRIVER VOLUME NAME local 0cd478e2d6a6c8cecbeaf2e07cc411323960ece5ad6b938ccd8ce3845108fe70 local 4a572909e8b5e931db5c7906fff0d92f042f73ff73ddb34be7cee8c3c2fda1f6 local 521f65180d63919ff0edad9eb2fdb05e97870f2a5d1782d0c5bbe680b86cb774 local 819f5ee5d9fa1eea5721b5e94d64897709c81e64c0c86b63cc8340d3b93115db local 852e2d79d67e1869d8e8805c9fcb93a27bdefdf72a4e5d94b7d8cce3c8f2497e local d2f0f70687937d2346909cb5a3582cd17a414e69d7b39a14d2d0d792b70d9b75 local tml-volume
3 查看指定数据卷的信息
通过如下命令查看指定数据卷的元信息:
[root@192 /]# docker volume inspect tml-volume [ { "CreatedAt": "2022-02-26T21:08:43+08:00", "Driver": "local", "Labels": {}, "Mountpoint": "/var/lib/docker/volumes/tml-volume/_data", "Name": "tml-volume", "Options": {}, "Scope": "local" } ]
4 删除指定的数据卷
可以使用如下命令删除指定数据卷:
[root@192 /]# docker volume rm tml-volume tml-volume [root@192 /]# docker volume ls DRIVER VOLUME NAME local 0cd478e2d6a6c8cecbeaf2e07cc411323960ece5ad6b938ccd8ce3845108fe70 local 4a572909e8b5e931db5c7906fff0d92f042f73ff73ddb34be7cee8c3c2fda1f6 local 521f65180d63919ff0edad9eb2fdb05e97870f2a5d1782d0c5bbe680b86cb774 local 819f5ee5d9fa1eea5721b5e94d64897709c81e64c0c86b63cc8340d3b93115db local 852e2d79d67e1869d8e8805c9fcb93a27bdefdf72a4e5d94b7d8cce3c8f2497e local d2f0f70687937d2346909cb5a3582cd17a414e69d7b39a14d2d0d792b70d9b75 [root@192 /]#
5 删除所有未被使用的数据卷
相当于做系统的垃圾清理,如果没有容器使用相关卷,那么使用这个命令减少磁盘占用:
[root@192 /]# docker volume prune WARNING! This will remove all local volumes not used by at least one container. Are you sure you want to continue? [y/N] y Deleted Volumes: d2f0f70687937d2346909cb5a3582cd17a414e69d7b39a14d2d0d792b70d9b75 0cd478e2d6a6c8cecbeaf2e07cc411323960ece5ad6b938ccd8ce3845108fe70 521f65180d63919ff0edad9eb2fdb05e97870f2a5d1782d0c5bbe680b86cb774 Total reclaimed space: 205.1MB [root@192 /]# docker volume ls DRIVER VOLUME NAME local 4a572909e8b5e931db5c7906fff0d92f042f73ff73ddb34be7cee8c3c2fda1f6 local 819f5ee5d9fa1eea5721b5e94d64897709c81e64c0c86b63cc8340d3b93115db local 852e2d79d67e1869d8e8805c9fcb93a27bdefdf72a4e5d94b7d8cce3c8f2497e [root@192 /]#
容器数据卷挂载方式
数据卷有三种挂载方式:指定路径挂载、匿名挂载以及具名挂载。
1 容器数据卷指定路径挂载
指定路径挂载指的是指定容器内的数据路径和宿主机本机路径的映射关系,举个例子,我们为centos下的文件目录有宿主机进行映射:
运行容器时交互式运行,并指定容器卷挂载
容器内数据变化
[root@192 ~]# cd /home [root@192 home]# ls test.java tml-1 [root@192 home]# docker run -it -v /home/volume_centos:/home centos /bin/bash [root@ed2f3e1f3787 /]# cd /home [root@ed2f3e1f3787 home]# ls [root@ed2f3e1f3787 home]# touch test-tml.java [root@ed2f3e1f3787 home]# ls test-tml.java
宿主机数据变化
[root@192 ~]# cd /home [root@192 home]# ls test.java tml-1 volume_centos [root@192 home]# cd volume_centos/ [root@192 volume_centos]# ls [root@192 volume_centos]# ls test-tml.java
当然这种挂载映射是双向的,我们在本地主机的操作:新增一个文件test-add.java
[root@192 volume_centos]# touch test-add.java [root@192 volume_centos]# cd .. [root@192 home]# ls test.java tml-1 volume_centos [root@192 home]# touch tml-not-add.java [root@192 home]#
在容器内也能在映射目录下找到对应新增的目录
[root@192 home]# docker start ed2f3e1f3787 ed2f3e1f3787 [root@192 home]# docker attach ed2f3e1f3787 [root@ed2f3e1f3787 /]# ls bin etc lib lost+found mnt proc run srv tmp var dev home lib64 media opt root sbin sys usr [root@ed2f3e1f3787 /]# cd /home [root@ed2f3e1f3787 home]# ls test-add.java test-tml.java [root@ed2f3e1f3787 home]# ls test-add.java test-tml.java [root@ed2f3e1f3787 home]#
但是非映射目录的数据变更不会互相影响,例如:主机tml-not-add.java
文件的添加容器内目录就没有做相应改变。挂载完数据卷后我们可以通过查看容器元数据来查看容器卷相关信息:
[root@ed2f3e1f3787 home]# exit exit [root@192 home]# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ed2f3e1f3787 centos "/bin/bash" 5 minutes ago Exited (130) 6 seconds ago thirsty_spence 78664208a245 portainer/portainer "/portainer" 7 days ago Up 6 days 0.0.0.0:8088->9000/tcp, :::8088->9000/tcp thirsty_gauss b7842800b1cb mysql "docker-entrypoint.s…" 7 days ago Exited (255) 6 days ago 33060/tcp, 0.0.0.0:3366->3306/tcp, :::3366->3306/tcp mysql-tml 3b66fa35905f elasticsearch:7.6.2 "/usr/local/bin/dock…" 7 days ago Exited (255) 6 days ago 0.0.0.0:3396->9200/tcp, :::3396->9200/tcp, 0.0.0.0:3397->9300/tcp, :::3397->9300/tcp elasticsearch-tml-7.6.2 bd6877cd46ce elasticsearch "/docker-entrypoint.…" 7 days ago Exited (255) 6 days ago 0.0.0.0:3392->9200/tcp, :::3392->9200/tcp, 0.0.0.0:3393->9300/tcp, :::3393->9300/tcp elasticsearch-tml 2f59536a92da tomcat "catalina.sh run" 7 days ago Up 6 days 0.0.0.0:3335->8080/tcp, :::3335->8080/tcp tomcat-tml b8ae778bad8c nginx "/docker-entrypoint.…" 7 days ago Exited (255) 6 days ago 0.0.0.0:3334->80/tcp, :::3334->80/tcp nginx-tml e36cf9dc0c2d centos "/bin/bash" 3 weeks ago Exited (0) 3 weeks ago sharp_turing 4ed9be7f96c8 centos "/bin/bash" 3 weeks ago Exited (255) 6 days ago inspiring_rhodes a5b62e74ac6b centos "/bin/bash" 3 weeks ago Exited (130) 3 weeks ago keen_mccarthy de82be8ae261 centos "/bin/bash" 3 weeks ago Exited (127) 3 weeks ago silly_tu a066e195a535 hello-world "/hello" 3 weeks ago Exited (0) 3 weeks ago sharp_wescoff 220887d9f138 centos "/bin/bash" 3 weeks ago Exited (0) 3 weeks ago kind_noyce cc886973b2cb centos "/bin/sh -c 'while t…" 3 weeks ago Exited (255) 6 days ago suspicious_borg 1dadd4b80c2c centos "/bin/bash" 3 weeks ago Exited (0) 3 weeks ago intelligent_hopper efa634e8f92c tomcat "catalina.sh run" 3 weeks ago Exited (130) 3 weeks ago unruffled_pascal 565553077e93 centos "/bin/bash" 3 weeks ago Exited (0) 3 weeks ago busy_hoover 22728b1cc9da centos "/bin/bash" 3 weeks ago Exited (0) 3 weeks ago loving_mahavira [root@192 home]# docker inspect ed2f3e1f3787 [ { ............... "Mounts": [ { "Type": "bind", "Source": "/home/volume_centos", "Destination": "/home", "Mode": "", "RW": true, "Propagation": "rprivate" } ] } ............... ]
其中Source为宿主机目录,Destination为容器内目录,模式为RW,也就是读写模式,这种读写模式或只读模式约束的是容器对数据卷的操作,主机在对应目录可以随意更改,就好像你在随意改数据库,而限制了程序对数据库的读写模式一样。我们可以再看一个数据库Mysql的例子,使用如下命令创建一个数据卷:
docker run -d -p 6603:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql-5.7-tml mysql:5.7
容器内数据变化
[root@192 home]# docker run -d -p 6603:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql-5.7-tml mysql:5.7 Unable to find image 'mysql:5.7' locally 5.7: Pulling from library/mysql 72a69066d2fe: Already exists 93619dbc5b36: Already exists 99da31dd6142: Already exists 626033c43d70: Already exists 37d5d7efb64e: Already exists ac563158d721: Already exists d2ba16033dad: Already exists 0ceb82207cd7: Pull complete 37f2405cae96: Pull complete e2482e017e53: Pull complete 70deed891d42: Pull complete Digest: sha256:f2ad209efe9c67104167fc609cca6973c8422939491c9345270175a300419f94 Status: Downloaded newer image for mysql:5.7 33fe5e65f7697538045046c122622cbcdb6981492a50f46d9033e4a049c280e2
宿主机数据变化
[root@192 /]# cd home [root@192 home]# ls mysql test.java tml-1 tml-not-add.java volume_centos [root@192 home]# cd mysql [root@192 mysql]# ls conf data [root@192 mysql]# cd data [root@192 data]# ls auto.cnf client-cert.pem ibdata1 ibtmp1 private_key.pem server-key.pem ca-key.pem client-key.pem ib_logfile0 mysql public_key.pem sys ca.pem ib_buffer_pool ib_logfile1 performance_schema server-cert.pem [root@192 data]#
当我们通过navicat连接数据库写数据时:
可以看到,navicat创建的数据库docker_database:
在数据卷上也写了一份:
[root@192 data]# ls auto.cnf client-cert.pem ib_buffer_pool ib_logfile1 performance_schema server-cert.pem ca-key.pem client-key.pem ibdata1 ibtmp1 private_key.pem server-key.pem ca.pem docker_database ib_logfile0 mysql public_key.pem sys [root@192 data]#
2 容器数据卷匿名挂载
匿名挂载就是在指定数据卷的时候,不指定容器路径对应的主机路径,这样对应映射的主机路径就是默认的路径/var/lib/docker/volumes/
中自动生成一个随机命名的文件夹
[root@192 /]# docker volume ls DRIVER VOLUME NAME local 4a572909e8b5e931db5c7906fff0d92f042f73ff73ddb34be7cee8c3c2fda1f6 local 819f5ee5d9fa1eea5721b5e94d64897709c81e64c0c86b63cc8340d3b93115db local 852e2d79d67e1869d8e8805c9fcb93a27bdefdf72a4e5d94b7d8cce3c8f2497e [root@192 /]# clear [root@192 /]# docker run -d -P --name nginx-tml-volume -v /etc/nginx nginx a872a0802014abe94b72615bc09788b6741f762867e5c112d3338c2f142b7787 [root@192 /]# docker volume ls DRIVER VOLUME NAME local 4a572909e8b5e931db5c7906fff0d92f042f73ff73ddb34be7cee8c3c2fda1f6 local 819f5ee5d9fa1eea5721b5e94d64897709c81e64c0c86b63cc8340d3b93115db local 852e2d79d67e1869d8e8805c9fcb93a27bdefdf72a4e5d94b7d8cce3c8f2497e local c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11 [root@192 /]#
可以看出来c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11
就是新运行的nginx容器阐释的数据卷,查看元数据并进入目录,可以清晰的看到nginx的配置文件:
[root@192 /]# docker volume inspect c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11 [ { "CreatedAt": "2022-02-26T21:34:38+08:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11/_data", "Name": "c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11", "Options": null, "Scope": "local" } ] [root@192 /]# cd /var/lib/docker/volumes/c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11/_data [root@192 _data]# ls conf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params [root@192 _data]#
3 容器数据卷具名挂载
具名挂载,就是指定文件夹名称,区别于指定路径挂载,这里的指定文件夹名称是在Docker指定的默认数据卷路径下的。同样通过docker volume ls
命令可以查看当前数据卷的目录情况
[root@192 _data]# docker run -d -P --name nginx-tml-juming -v tml-juming-mount-nginx:/etc/nginx nginx 0b24ae53e7fc60fd2ba6ee4d989903b3fb1a25cdd597d9d95a988674fd884fbb [root@192 _data]# docker volume ls DRIVER VOLUME NAME local 4a572909e8b5e931db5c7906fff0d92f042f73ff73ddb34be7cee8c3c2fda1f6 local 819f5ee5d9fa1eea5721b5e94d64897709c81e64c0c86b63cc8340d3b93115db local 852e2d79d67e1869d8e8805c9fcb93a27bdefdf72a4e5d94b7d8cce3c8f2497e local c648402f4573092f76dfd2b89ca16b774bdada37290937d361bf9e6e3b321d11 local tml-juming-mount-nginx [root@192 _data]# docker volume inspect tml-juming-mount-nginx [ { "CreatedAt": "2022-02-26T21:38:06+08:00", "Driver": "local", "Labels": null, "Mountpoint": "/var/lib/docker/volumes/tml-juming-mount-nginx/_data", "Name": "tml-juming-mount-nginx", "Options": null, "Scope": "local" } ] [root@192 _data]# cd /var/lib/docker/volumes/tml-juming-mount-nginx/_data [root@192 _data]# ls conf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params [root@192 _data]#
当然在挂载时我们可以指定读写和只读模式,默认是读写模式,我们来看看设置只读模式效果:
[root@192 _data]# docker run -d -P --name nginx-ro -v juming-nginx:/etc/nginx:ro nginx b4c122ea9c7eb33aeaec56a9784d8e66f410245f06911699335a679f6d0b7265 [root@192 /]# docker inspect b4c122ea9c7eb33aeaec56a9784d8e66f410245f06911699335a679f6d0b7265
打印出来的容器元数据如下,可以看到RW属性值为false。
"Mounts": [ { "Type": "volume", "Name": "juming-nginx", "Source": "/var/lib/docker/volumes/juming-nginx/_data", "Destination": "/etc/nginx", "Driver": "local", "Mode": "ro", "RW": false, "Propagation": "" } ],
容器数据卷依赖挂载
不光是容器与宿主机之间,容器与容器之间也可以相互依赖进行数据卷挂载,实质上这个示例只是容器间数据同步。首先我们新开一个容器,在里面新增两个目录:
container-tml-01容器,创建一个容器数据卷
[root@192 ~]# docker run -it -v /var/volume1 -v /var/volume2 --name container-tml-01 centos:latest /bin/bash [root@df3a751e89b2 /]# cd /var [root@df3a751e89b2 var]# ls adm crash empty games kerberos local log nis preserve spool volume1 yp cache db ftp gopher lib lock mail opt run tmp volume2 [root@df3a751e89b2 var]# ls adm crash empty games kerberos local log nis preserve spool volume1 yp cache db ftp gopher lib lock mail opt run tmp volume2 [root@df3a751e89b2 var]# cd volume1 [root@df3a751e89b2 volume1]# ls tml.java [root@df3a751e89b2 volume1]# touch tml2.java [root@df3a751e89b2 volume1]# ls tml.java tml2.java [root@df3a751e89b2 volume1]#
container-tml-02容器,挂载到container-tml-01下
[root@192 ~]# docker run -it --name container-tml-02 --volumes-from container-tml-01 centos:latest /bin/bash [root@763ce484f7f4 /]# ls bin etc lib lost+found mnt proc run srv tmp var dev home lib64 media opt root sbin sys usr [root@763ce484f7f4 /]# cd var [root@763ce484f7f4 var]# ls adm crash empty games kerberos local log nis preserve spool volume1 yp cache db ftp gopher lib lock mail opt run tmp volume2 [root@763ce484f7f4 var]# cd volume1 [root@763ce484f7f4 volume1]# touch tml.java [root@763ce484f7f4 volume1]# ls tml.java [root@763ce484f7f4 volume1]# ls tml.java tml2.java [root@763ce484f7f4 volume1]#
从以上两个命令窗口变化可以看出,容器1和2在挂载目录处已经实现了数据互通。
总结一下
容器数据卷还是一个比较重要的机制,类似于redis的持久化机制。因为容器感觉太轻量级了,似乎很轻松就能被干掉,所以容器也需要在宿主机的一个目录上持久化自己的重要数据,防止容器被删除时数据也被误删,尤其是容器如果是基于一个Mysql镜像构建的,那就太危险了!当然其实除了数据保护容器数据卷机制还有一些别的功能,例如将多个容器配置映射到宿主机同一目录,通过配置一次性修改达到多个容器同时生效。它的数据在容器间互通,容器宿主机互通的特性感觉在之后会有很多场景可以使用到,学到的时候拭目以待。