注:本文全部操作在centos8环境下进行!
一.容器数据卷概念
容器技术使用rootfs机制与Namespace,构建出与宿主机隔离开的文件系统。在用户使用Docker容器的时候,会产生一系列的数据文件。这些数据文件在Docker容器关闭时就会消失,但是其中部分数据是用户希望能够保存的。Docker将应用与运行环境打包成容器进行运行,用户希望在运行过程中产生的部分数据是可以持久化的,并且容器之间能够实现数据互通。这正是容器数据卷(Docker Volume)要解决的问题。
Docker中的数据可以存储在类似于虚拟机磁盘的介质中,这种介质在Docker中称为数据卷。数据卷以目录的形式呈现给Docker,不仅可用来存储Docker应用的数据,还可以支持多个容器间数据共享,并且修改数据卷文件也不会影响镜像。在Docker中使用数据卷,就是在系统中挂载一个文件系统。
Docker数据卷所使用的挂载技术,就是Linux的绑定挂载。其主要作用是,允许用户将一个目录或文件(并非整个设备),挂载到一个指定的目录上。用户在该挂载点上进行的任何操作都只发生在被挂载的目录或文件上,而原挂载点的内容则会被隐藏起来且不受任何影响。容器利用数据卷与宿主机进行数据共享,从而实现容器间的数据共享与交换。
Docker数据卷默认存储在宿主机的/var/lib/Docker/volumes/目录下,也可以指定挂载到任意位置。这种挂载仅存储在宿主机的内存中,永远不会写入宿主机的文件系统。如图所示。
数据卷的特点如下:
(1)容器启动时初始化,如果容器使用的镜像包含数据卷,这些数据也会复制到数据卷中。
(2)容器对数据卷的修改是直接生效的。
(3)数据卷的变化不会影响镜像的更新,数据卷是独立于联合文件系统,镜像是基于联合文件系统,镜像与数据卷不会相互影响。
(4)数据卷是宿主机中的一个目录,与容器生命周期隔离。
二.数据卷挂载
1.在命令行挂载数据卷
在docker create或docker run命令中,使用-v为容器增加一个数据卷。
[root@Docker ~]# docker run -it --name volume -v /web/app docker.io/centos
[root@120b3e3fe6b2 /]# ls web/
[root@120b3e3fe6b2 /]# exit
以上示例使用docker run命令运行了一个容器,并通过-v参数为容器添加了一个数据卷。
下面通过命令查看挂载信息。
[root@Docker ~]# docker inspect volume
从以上示例中可以看到,宿主机已经在/var/lib/docker/volumes/下自动生成了挂载目录。
下面通过命令行手动指定宿主机挂载目录。
[root@Docker ~]# docker run -it -d --name test -v /webapp:/app docker.io/nginx
以上示例在后台运行了一个被命名为test的Nginx容器,并为它挂载数据卷。
下面查看容器test的状态及挂载数据信息。
[root@Docker ~]# docker ps -a
[root@Docker ~]# docker inspect test
上述示例中,Mount信息包含了上面创建的容器的详细挂载信息,Source指定了本机路径,Destination指定了容器内部的路径。
下面通过示例观察数据卷共享机制,会在宿主机与容器端之间多次切换,建议开启两个终端。
[root@Docker ~]# docker run -it --name test -v /web/webapp:/app docker.io/nginx /bin/bash
以上示例创建了一个名为test的Nginx容器,并将容器内的/app目录挂载至宿主机的/web/webapp路径下。
下面分别查看宿主机与容器的根目录下的文件。
容器:
root@e270eef21ad9:/# ls /
根目录下多了一个app目录。
宿主机:
[root@Docker ~]# ls /
[root@Docker ~]# cd /web/webapp/
[root@Docker webapp]# ls
从以上示例中可以看到,宿主机的根目录下新建了一个web目录,而该目录下没有任何文件。
下面在宿主机的/web目录下创建文件,并返回容器观察目录内容。
在宿主机目录上创建两个文件a.txt和b.txt
[root@Docker web]# cd /web/webapp/
[root@Docker webapp]# touch a.txt b.txt
[root@Docker webapp]# ls
返回容器查看,在挂载目录下有了在宿主机中创造的文件a.txt和b.txt
root@e270eef21ad9:/# ls /
root@e270eef21ad9:/# cd app/
root@e270eef21ad9:/app# ls
从以上示例中可以看到,在宿主机的挂载目录下创建的文件也会在容器中出现。
下面在容器中创建文件,并返回宿主机观察目录内容。
在容器的挂载数据卷中创建一个c.txt文件
root@e270eef21ad9:/app# touch c.txt
root@e270eef21ad9:/app# ls
返回宿主机终端查看挂载卷,多了c.txt文件
[root@Docker webapp]# ls
从以上示例中可以看出,Docker数据卷能够实现Docker容器与宿主机间的数据共享,并且能够将容器中产生的数据永久保存下来,随时在宿主机查看与修改。
在生产环境中,容器服务的配置文件通常采用数据卷的方式挂载至容器内。为了防止容器内的误操作修改配置文件,在挂载时可以进行权限设置。这样就可以达到在宿主机修改代码,在容器内查看修改结果的目的。
下面创建容器并为数据卷设置权限。
[root@Docker ~]# docker run -it --name volume -v /src/test:/webapp:ro docker.io/nginx /bin/bash
以上示例创建了一个名为volume的Nginx容器,并将挂载数据卷权限设置为ro只读。
下面查看数据卷是否挂载成功。
使用docker inspect 查看挂载数据卷信息
[root@Docker ~]# docker inspect volume
root@b9f75fdc40cf:/# ls
查看容器挂载目录webapp
[root@Docker ~]# cd /src/test/
[root@Docker test]# ls
从以上示例中可以看到,数据卷已经挂载成功,且宿主机挂载目录中没有任何文件。
下面为宿主机目录中添加文件,并返回容器中查看效果。
在宿主机的挂载目录中创建一个文件并写入"hello world"
[root@Docker test]# echo hello world > a.txt
[root@Docker test]# cat a.txt
在容器中查看
root@b9f75fdc40cf:/# cd webapp/
root@b9f75fdc40cf:/webapp# ls
root@b9f75fdc40cf:/webapp# cat a.txt
从以上示例中可以看到,在容器挂载目录中可以查看文件内容。
下面在容器挂载目录下的文件中修改内容。
root@b9f75fdc40cf:/webapp# echo 1 > a.txt
以上示例中,在容器挂载目录中修改文件时发生报错,这是因为在容器中文件为只读类型,无法进行修改。
下面在容器挂载目录中创建新的文件。
root@b9f75fdc40cf:/webapp# touch b.txt
以上示例中,在容器挂载目录中创建文件时发生报错,这是因为在容器中挂载目录同样为只读类型,无法对其进行实质性的操作。
2.通过Dockerfile挂载数据卷
用户创建镜像时,通常会在Dockerfile文件中加上VOLUME[/date]来创建含有数据卷的镜像,并使用该镜像创建包含数据卷的容器。
Dockerfile可以创建多个数据卷,与使用docker run命令创建数据卷不同,Dockerfile中的数据卷不能映射到已经存在的本地目录。在启动容器时,才会创建Dockerfile中指定的数据卷,并且以Docker中指定的名称命名。运行同样镜像的容器创建的数据卷是不一样的(可以看到不同容器的数据卷地址也是不一样的)。当容器中的数据卷地址不一样时,容器之间就无法共享数据了。
下面使用Dockerfile中的VOLUME选项来指定挂载数据卷,首先创建Dockerfile并添加内容。
[root@Docker ~]# cat Dockerfile
以上示例创建了一个Dockerfile文件并添加了内容,即挂载三个数据卷。
下面通过该Dockerfile创建新镜像。
[root@Docker ~]# docker build -t volume .
以上示例中,镜像已经构建完成,并且执行了挂载三个数据卷的命令。
使用构建完成的镜像运行容器。
[root@Docker ~]# docker run -it volume
[root@41acce96831b /]# ls
查看容器信息。
[root@Docker ~]# docker inspect 41acce96831b
从以上示例中可以看出,该容器挂载了三个数据卷,容器内的目录为Dockerfile中指定的目录,宿主机中数据卷的位置在/var/lib/Docker/volumes/下生成,并且数据卷的地址是随机生成的。
如此一来,就实现了通过Dockerfile创建镜像,运行容器时就会自动挂载数据卷。
三.数据卷容器
运行容器时宿主机会随机生成挂载目录,无法保持目录地址一致,所以无法实现容器间的数据共享。数据卷容器可以有效地解决这个问题:讲已命名的容器挂载数据卷,其他容器通过挂载这个容器实现数据共享,挂载数据卷的容器叫做数据卷容器。数据卷容器挂载了一个宿主机目录,其他容器连接数据卷容器来实现数据的共享。如图所示。
启动一个名为volume-container容器,此容器包含两个数据卷/volume1和/volume2(这两个数据卷目录是在容器中的,运行容器时会自动创建)。
[root@Docker ~]# docker run -it --name volume-container -v /volume1 -v /volume2 docker.io/centos /bin/bash
[root@797dc5449542 /]# ls
上述示例中,容器volume-container已经创建完成,数据卷也挂载完成。
下面使用Ctrl+P+Q组合键退出当前容器终端,查看挂载信息并在宿主机中为数据卷添加文件。
[root@Docker ~]# docker inspect volume-container | grep volume
在宿主机中分别为两个数据卷添加文件及文件内容。
[root@Docker ~]# cd /var/lib/docker/volumes/6a68b39d8560a0db2aab2ab2f9a0345fc92cd648f58518db5c4cbabb9ef1ab0e/_data
[root@Docker _data]# ls
[root@Docker _data]# echo hello container1 > a.txt
[root@Docker _data]# cd /var/lib/docker/volumes/7efa445b04fdf67b8b01c6fb0f527e5e7ea4344ce5d6060ad689be26f5cfed8c/_data
[root@Docker _data]# echo hello container2 > b.txt
下面创建容器test1-container容器,用—volumes-from参数挂载volume-container中的数据卷。
[root@Docker _data]# docker run -it --name test1-container --volumes-from volume-container docker.io/centos /bin/bash
[root@29ba9f6a1132 /]# ls
[root@29ba9f6a1132 /]# cat volume1/a.txt
[root@29ba9f6a1132 /]# cat volume2/b.txt
从以上示例中可以看出,两个容器实现了数据共享。
下面将初始数据卷容器删除,观察数据卷还能否正常工作。
删除数据卷容器。
[root@Docker ~]# docker ps -a
[root@Docker ~]# docker stop volume-container
[root@Docker ~]# docker rm volume-container
[root@Docker ~]# docker ps -a
成功挂载数据卷。
[root@Docker ~]# docker run -it --name test2-container --volumes-from test1-container docker.io/centos /bin/bash
[root@93a2921c71b3 /]# ls
[root@93a2921c71b3 /]# cat /volume1/a.txt
[root@93a2921c71b3 /]# cat /volume2/b.txt
从以上示例中可以看到,即使删除了初始的数据卷容器volume-container或其他容器,只要有容器在使用该数据卷,里面的数据就不会丢失。
下面删除所有容器,再观察数据卷。
删除所有容器。
[root@Docker ~]# docker ps -a
[root@Docker ~]# docker rm 29ba9f6a1132
[root@Docker ~]# docker stop 93a2921c71b3
[root@Docker ~]# docker rm 93a2921c71b3
[root@Docker ~]# docker ps -a
观察数据卷。
[root@Docker ~]# cd /var/lib/docker/volumes/7efa445b04fdf67b8b01c6fb0f527e5e7ea4344ce5d6060ad689be26f5cfed8c/_data
[root@Docker _data]# ls
[root@Docker _data]# cat b.txt
以上示例中,即使删除了所有容器,数据卷也保留在宿主机中,这大大保证了数据的安全性。
四.备份数据卷
企业中,业务数据不容有失,人们通常会对数据进行一次备份或多次备份,以保证数据的安全性。
下面创建一个名为data-volume的CentOS容器,准备对其挂载的两个数据卷/var/volume1和/var/volume2进行备份操作。
[root@Docker ~]# docker run -it --name data-volume -v /var/volume1 -v /var/volume2 docker.io/centos /bin/bash
[root@e8fab5bd0603 /]# ls
[root@e8fab5bd0603 /]# ls /var/
以上示例中,容器已经创建完成,数据卷也成功挂载。
下面在容器挂载目录中创建文件并添加内容。
[root@e8fab5bd0603 /]# echo hello container1 > /var/volume1/a.txt
[root@e8fab5bd0603 /]# echo hello container > /var/volume2/b.txt
[root@e8fab5bd0603 /]# cat /var/volume1/a.txt
[root@e8fab5bd0603 /]# cat /var/volume2/b.txt
以上示例分别在容器挂载目录volume1与volume2中创建了a.txt与b.txt两个文件,并在文件中添加了内容。
简而言之,备份数据卷就是使用-volumes-from参数来创建一个挂载数据卷的容器,从宿主机挂载要存放备份数据的目录到容器的备份目录,并备份数据卷中的数据。完成后使用--rm参数删除容器,此时备份数据已经保存在当前的目录下。
[root@Docker ~]# docker run --rm --volumes-from data-volume -v /root/backup:/backup docker.io/centos tar cvf /backup/backup1.tar /var/volume1
[root@Docker ~]# docker ps -a
[root@Docker ~]# ls /root/backup/
以上示例成功将容器中/var/volume1/a.txt文件备份到宿主机中,并在备份之后将容器删除。宿主机中/root/backup/backup1.tar文件就是备份数据,只是该文件经过了压缩。
下面以同样的方式对容器中/var/volume2/b.txt文件进行备份。
[root@Docker ~]# docker run --rm --volumes-from data-volume -v /root/backup:/backup docker.io/centos tar cvf /backup/backup2.tar /var/volume2
[root@Docker ~]# ls /root/backup/
此时,两份文件都已备份到宿主机中。
下面解压备份文件,并查看其目录结构。
[root@Docker ~]# tar -xf backup/backup1.tar
[root@Docker ~]# tree .
注:如果提示-bash: tree: 未找到命令。则先安装tree命令,命令如下:
[root@Docker ~]# yum install -y tree
在以上示例中,仍可以看到备份之前的路径。
另外,也可以使用一条命令完成多个文件备份。
[root@Docker ~]# docker run -it --rm --volumes-from data-volume -v /root/backup:/backup docker.io/centos tar cvf /backup/backup.tar /var/volume1 /var/volume2
[root@Docker ~]# ls /root/backup/
以上示例中仅通过一条命令就成功备份了两份数据,通常在需要备份多份数据时将会使用此方式。
五.数据卷的恢复与迁移
1.恢复数据卷
恢复数据卷是将备份数据恢复到原容器中。
下面模拟数据丢失来对容器数据卷进行恢复。
[root@Docker ~]# ls /root/backup/
[root@Docker ~]# docker ps -a
[root@Docker ~]# docker attach data-volume
[root@e8fab5bd0603 /]# ls
[root@e8fab5bd0603 /]# ls /var/volume1
[root@e8fab5bd0603 /]# ls /var/volume2
[root@e8fab5bd0603 /]# rm -rf /var/volume1/a.txt
[root@e8fab5bd0603 /]# rm -rf /var/volume2/b.txt
以上示例为了模拟数据丢失,将容器中文件/var/volume1/a.txt与var/volume2/b.txt删除。
下面进行数据恢复。
[root@Docker ~]# docker run --rm --volumes-from data-volume -v /root/backup/:/backup docker.io/centos tar xvf /backup/backup.tar -C /
以上示例通过创建新容器对数据卷容器进行挂载,并将宿主机数据目录挂载到容器,再将解压后的数据存放到指定路径下。
下面进入容器查看数据是否成功恢复。
[root@Docker ~]# docker attach data-volume
[root@e8fab5bd0603 /]# ls /var/volume1
[root@e8fab5bd0603 /]# ls /var/volume2
从以上示例中可以看到,数据文件a.txt与b.txt都已经成功恢复。
2.迁移数据卷
迁移数据卷是将备份数据恢复到新建容器中。
新建容器并解压备份文件到新的容器数据卷。
[root@Docker ~]# docker run -it -v /var/volume1 -v /var/volume2 --name new-container docker.io/centos /bin/bash
[root@0bde19ffb9f7 /]# ls /var/volume1/
[root@0bde19ffb9f7 /]# ls /var/volume2/
以上示例新建了容器new-container,并挂载了数据卷目录volume1与volume2,但数据卷中并没有数据。
下面使用Ctrl+P+Q组合键退出当前容器终端,将备份数据迁移到容器new-container中。
[root@Docker ~]# docker run -it --rm --volumes-from new-container -v /root/backup:/backup docker.io/centos tar xvf /backup/backup.tar -C /
[root@Docker ~]# docker attach new-container
[root@0bde19ffb9f7 /]# ls /var/volume1
[root@0bde19ffb9f7 /]# ls /var/volume2
从以上示例中可以看到,数据已经迁移成功。
建议新容器创建时挂载的数据卷路径与先前备份的数据卷路径保持一致,否则会出现数据恢复不全的情况。
只挂载了一个目录。
[root@Docker ~]# docker run -it -v /var/volume1 --name new-container1 docker.io/centos /bin/bash
[root@d5ef081a31de /]# ls /var/
[root@d5ef081a31de /]# ls /var/volume1/
[root@Docker /]# docker run -it --rm --volumes-from data-volume -v /root/backup/:/backup docker.io/centos tar xvf /backup/backup.tar -C /
[root@Docker /]# docker attach new-container
[root@6e50f3ba2799 /]# ls /var/
[root@6e50f3ba2799 /]# ls /var/volume1
以上示例中,路径没有保持一致,备份之后发现只恢复了volume1中的数据,volume2中的数据没有恢复。
为了避免这种情况的发生,可以修改-C参数后面的路径,使数据正常恢复。
新建容器,已挂载数据卷,没有文件数据。
[root@Docker ~]# docker run -it -v /var/data --name new-container2 docker.io/centos /bin/bash
[root@d6d0970bfd88 /]# ls /var/data
进入容器查看。
[root@Docker ~]# docker run -it --rm --volumes-from new-container2 -v /root/backup/:/backup docker.io/centos tar xvf /backup/backup.tar -C /var/data
[root@Docker ~]# docker attach new-container2
[root@d6d0970bfd88 /]# ls /var/data
[root@d6d0970bfd88 /]# ls /var/data/var
[root@d6d0970bfd88 /]# ls /var/data/var/volume1
[root@d6d0970bfd88 /]# ls /var/data/var/volume2
查看目录结构。
[root@d6d0970bfd88 ~]# tree /var/data
以上示例通过修改路径,使数据完整的备份到容器new-container中。
六.管理数据卷
1.与容器关联
数据卷最大的优势是可以用来做持久化数据,它的生命周期是独立的,Docker不会在容器被删除后自动删除数据卷,也不存在类似垃圾回收的机制来处理没有被任何容器使用的数据卷。但难免会有无用的数据卷,用户可以通过在删除容器的命令中添加参数,在删除容器的同时删除数据卷。
Docker数据卷可以通过命令与容器关联,删除容器时,数据卷也随之删除。
docker rm -v
删除容器时添加-v参数会将数据卷一并删除。
docker run --rm
创建、运行容器时添加--rm参数,容器运行结束时容器与数据卷会被一并删除。
如果不对数据卷进行及时清理,/var/lib/Docker/volumes/目录下就会产生许多残留目录。但删除的数据卷是无法找回的,建议再三确认之后在执行操作。
创建一个容器并挂载数据卷,再将容器删除查看数据。
运行一个容器饼并为其挂载数据卷。
[root@Docker /]# docker run -it -v /data --name test docker.io/centos
查看数据卷挂载目录。
[root@Docker ~]# docker inspect test | grep Source
数据卷所在目录。
[root@Docker ~]# ls /var/lib/docker/volumes/
不添加-v参数,删除容器。
[root@Docker ~]# docker rm test
容器已被删除
[root@Docker ~]# docker inspect test | grep Source
[root@Docker ~]# ls /var/lib/docker/volumes/
以上示例中,将挂载了数据卷的容器删除之后,容器挂载的数据卷还存在。
下面在删除容器的同时添加-v参数,删除该容器的数据卷。
[root@Docker ~]# docker run -it -v /data --name test2 docker.io/centos
[root@Docker /]# docker inspect test2 | grep Source
[root@Docker /]# ls /var/lib/docker/volumes/
删除容器是添加-v参数。
[root@Docker /]# docker stop test2
[root@Docker /]# docker rm -v test2
[root@Docker /]# ls /var/lib/docker/volumes/
以上示例在删除容器时添加了-v参数,数据卷也同时被删除,清理了无用的数据卷,节省了磁盘空间。
创建容器时,在命令中添加--rm参数,终止容器时会自动删除容器及数据卷。
[root@Docker /]# docker run -it --rm --name test3 -v /data docker.io/centos /bin/bash
以上示例在创建容器命令中添加了--rm参数,并执行了该命令。
下面通过另一个终端查看宿主机的挂载目录。
[root@Docker ~]# ls /var/lib/docker/volumes/
上述示例中,容器创建成功,数据卷目录也挂载成功。
下面通过exit命令退出终端,并再次查看宿主的挂载目录。
[root@87f864465cfd /]# exit
[root@Docker /]# ls /var/lib/docker/volumes/
以上示例中,使用exit命令退出容器之后,数据卷也被删除。
2.命令管理
Docker中有专门的容器数据卷命令供用户来管理容器数据卷。
下面通过示例介绍容器数据卷命令的一些参数。
create
创建数据卷。
[root@Docker /]# docker volume create test
以上示例中通过在docker volume命令中添加create参数,创建出了命名为test的新容器数据卷。
ls
列出数据卷。
[root@Docker /]# docker volume ls
以上示例通过docker volume命令添加ls参数查看数据卷,可以看到刚刚创建的test数据卷。
另外,在宿主机的挂载目录中也可以查看数据卷信息。
[root@Docker /]# ls /var/lib/docker/volumes/
上述示例通过宿主机挂载目录查看容器数据卷,与数据卷目录下查看到的结果相同。
inspect
显示一个或多个数据卷的详细信息。
首先创建一个容器,并为其挂载刚刚创建的容器数据卷。
[root@Docker /]# docker run -it -d --name test-container -v test:/volume docker.io/centos
以上示例将刚刚创建的容器数据卷test作为新容器test-container的数据卷,也就是将test数据卷在宿主机上的目录/var/lib/Docker/volumes/test/_data挂载到容器内的/volume中。
接着,通过命令查看容器test-container的数据卷信息。
[root@Docker /]# docker inspect test-container
上述示例通过查看容器挂载信息得知容器test-container成功挂载test数据卷。
另外,还可以在宿主机中使用docker inspect命令查看指定数据卷的信息。
[root@Docker /]# docker volume inspect test
rm
删除一个或多个数据卷。
首先将容器停止并删除,再查看数据卷信息。
[root@Docker /]# docker stop test-container
[root@Docker /]# docker rm test-container
[root@Docker /]# docker volume ls
以上示例中,容器test-container已经被删除,但数据卷test仍然存在。
下面通过在命令中添加rm参数对数据卷test进行删除并查看数据卷信息。
[root@Docker /]# docker volume rm test
[root@Docker /]# docker volume ls
以上示例中,数据卷被成功删除。
prune
删除所有未被使用的数据卷。
在前面的示例中可以看到,本地残留了一些未被使用的数据卷,用户可以使用一条命令将其删除。
[root@Docker /]# docker volume prune
以上示例通过命令删除了三个未使用的数据卷,释放了33B的空间。在执行命令时,Docker会询问是否要删除没有被使用的数据卷,如果确定,在终端输入“y”即可,否则输入“N”。
下面查看数据卷是否被成功删除。
[root@Docker /]# docker volume ls
从以上示例中可以看到,没有被使用的数据卷已经被成功删除,本地没有数据卷。