Docker的数据卷

转载:原文地址docker-朱双印博客 (zsythink.net)

一个容器运行了一段时间,肯定会产生一些数据,比如日志、数据库数据、新改的配置文件、等等,如果这些数据文件存放在容器中,当我们删除容器时,这些数据也会被随之删除(因为这些数据存放在容器可读写层中,所以会随容器被删除),这可能并不是我们想要的结果,通常情况下,我们想要持久的保存这些数据,以便可以随时使用它们,那么,怎样做才能在删除容器时,仍然保留这些数据呢?在docker中,有一种存储数据的方法,叫做数据卷(后文简称卷,卷的英文为volume),借助,即可达到我们的目的。

那么什么是卷呢?
我们可以把理解成宿主机中的目录,当我们把某个卷和容器中的某个目录建立映射关系后,就相当于把宿主机中的某个目录和容器中的某个目录建立了映射关系。
举个例子:
假设现在有一个名为testA的卷,我们把testA卷和容器A中的/data目录进行了映射,映射后,运行容器A,容器A中的程序将日志写入到了容器的/data/A.log文件中,由于testA卷与容器A/data目录是映射过的,所以我们可以在testA卷中,看到一个名为A.log的日志文件,testA卷中的A.log其实就是容器A中的/data/A.log,当我们删除容器A以后,testA卷中的A.log仍然存在于宿主机中,并不会随容器A被删除,也就是说,卷中的数据是独立于容器的生命周期之外的,卷能让我们将容器中的数据保存到宿主机上,从而确保我们即使删除了容器,对应的数据也能保存下来。

了解了什么是卷以后,就可以来实际动手操作一下了。
我们先来创建一个卷,使用如下命令,创建一个名为testA的卷

[root@cos7-1 ~]# docker volume create testA

testA

如你所见,docker volume命令就是专门用来管理卷的命令,上例中的create就是docker volume的子命令,docker volume create表示创建一个卷,执行上述命令后,使用ls子命令,可以查看当前主机中的卷,如下:

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     testA

可以看到,在本地已经有一个名为testA的卷了,我们可以使用inspect子命令,查看对应卷的详细信息。

[root@cos7-1 ~]# docker volume inspect testA

[

    {

        "CreatedAt": "2022-05-07T00:05:37+08:00",

        "Driver": "local",

        "Labels": {},

        "Mountpoint": "/var/lib/docker/volumes/testA/_data",

        "Name": "testA",

        "Options": {},

        "Scope": "local"

    }

上文说过,卷其实就是宿主机中的某个目录,从上述详细信息中可以看出,testA卷其实就是宿主机中的/var/lib/docker/volumes/testA/_data目录,在linuxdocker主机中创建一个卷时,其对应的目录路径就是/var/lib/docker/volumes/卷名/_data

testA卷已经创建完成,但是没有任何容器使用这个卷,现在,我们来创建一个测试容器,让这个测试容器使用testA卷,创建容器的命令如下:

docker run -td --name testAcon --volume testA:/data alpine

上述命令表示创建一个名为testAcon的容器,将testA卷映射到testAcon容器的/data目录中,--volume选项就是用来映射卷和容器目录的,--volume testA:/data表示将testA卷映射到容器的/data目录,上例是基于alpine镜像创建的容器,alipine镜像中默认是没有/data目录的,当卷映射的目录在容器中不存在时,会自动在容器中创建对应的目录。
容器创建后,进入容器的/data目录,创建一个名为A.log的文件,如下

[root@cos7-1 ~]# docker exec -it testAcon sh

/ # ls /data

/ # cd /data

/data # echo "test volume A" > /data/A.log

/data # cat /data/A.log

test volume A

/data # exit

[root@cos7-1 ~]#

回到宿主机,查看/var/lib/docker/volumes/testA/_data目录,发现多了一个A.log文件,这个文件正是我们在容器中创建的A.log

[root@cos7-1 ~]# ls /var/lib/docker/volumes/testA/_data/

A.log

[root@cos7-1 ~]# cat /var/lib/docker/volumes/testA/_data/A.log

test volume A

如果此时我们在宿主机中修改A.log文件的内容,会发现容器中的A.log的内容也随之变化了,因为它们就是同一个文件。
现在,我们停止并删除testAcon容器,可以发现,testA卷中的数据仍然保留在宿主机中。

[root@cos7-1 ~]# docker ps

CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES

75c09d483a20   alpine    "/bin/sh"   8 minutes ago   Up 8 minutes             testAcon

[root@cos7-1 ~]# docker stop testAcon

testAcon

[root@cos7-1 ~]# docker rm testAcon

testAcon

[root@cos7-1 ~]# ls /var/lib/docker/volumes/testA/_data/

A.log

这就是我们要的效果,容器中产生的数据不随容器被删除,仍然保留下来,以便随时可以继续使用,比如,我再创建一个新的容器,仍然使用这个卷中的数据

[root@cos7-1 ~]# docker run -td --name testBcon --volume testA:/data alpine

9b6d44174554066de29c5bc1399d757bf678d8bc4bf615cad48d70ce0b843fe9

[root@cos7-1 ~]#

[root@cos7-1 ~]# docker exec -it testBcon sh

/ #

/ # ls /data

A.log

/ # cat /data/A.log

test volume A

/ # exit

[root@cos7-1 ~]#

可以看到,testA卷中的数据可以直接在testBcon中访问到。

当把卷映射到容器目录时,可以理解为把卷对应的宿主机目录挂载到了容器的目录中,卷虽然可以映射到容器的目录中,但其实是独立于容器之外的,当我们使用docker commit命令基于容器A创建镜像时,如果容器A使用了某个卷,卷中的数据并不会随容器A被提交,即新创建的镜像中并不会包含容器A对应卷中的数据。

--volume选项是长选项,它有一个对应的短选项-v,它们两个是等效的,即如下两个命令等效

docker run -td --name testCon --volume testA:/data alpine

docker run -td --name testCon -v testA:/data alpine

一个容器可以使用多个卷,只需要多次使用-v选项指定即可

[root@cos7-1 ~]# docker run -td --name testCcon -v testA:/data -v testB:/var/log alpine

d8d7470c5d86cd42fb8474d94fc39cc8a8ed2defba327835fcf2fa7c2cd5f690

执行上述命令后会发现,我并没有提前使用docker volume create命令创建testB卷,而是直接在docker run命令中指定了名为testB的卷,docker run命令可以正常执行,并没有报错,此时,我们查看一下卷的信息,会发现testB卷被自动创建了。

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     testA

local     testB

可见,当指定的卷不存在时,docker会自动创建对应的卷。

一个卷可以同时被多个容器使用,从而达到数据共享的目的,就像上文的testBcontestCcon两个容器同时使用了testA卷一样。

在使用--volume选项或者-v选项时,可以省略卷名,只指定一个容器中的目录,当省略卷名时,docker会自动创建一个新的卷,并为这个卷生成一个ID,如下

[root@cos7-1 ~]# docker run -td --name testDcon -v /data1 alpine

60afd20098ed4cb7631ee6eaff5bf4cc9fb5fe8e623c044d95075b637f5d6c9d

[root@cos7-1 ~]#

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3

local     testA

local     testB

上例中,创建testDcon容器时,并没有指定卷名,而是只指定了一个/data1目录,这种情况下,docker自动创建了一个卷,这个卷的名字是一串很长的生成的ID,上例生成的卷ID86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3,这种用docker生成的ID作为卷名的卷被称之为匿名卷,匿名卷的英文原文为Anonymous volume,而上文中的testA卷和testB卷,是我们自定义名称的卷,这种卷被称之为命名卷,命名卷的英文原文为Named volumes,上例中的匿名卷对应的宿主机目录也是在/var/lib/docker/volumes/ID/_data下,无论是命名卷还是匿名卷,都是被docker管理的,即使是匿名卷,我们也可以使用它的ID作为卷名,从而映射到其他容器的某个目录中,例如
-v 86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3:/data2
上例表示将匿名卷86bb1c映射到容器的/data2目录中。

如果确定卷中的数据不需要再使用了,则可以删除对应的卷。
使用docker volume rm命令即可删除对应的卷,但是删除卷之前,需要先确保卷没有被任何容器引用,如果有容器使用了对应的卷,会提示无法删除,需要先删除对应的容器,示例如下

#查看当前卷,有三个,两个命名卷,一个匿名卷,此处以删除匿名卷为例,命名卷操作也是相同的

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3

local     testA

local     testB

#尝试删除匿名卷,出现提示,卷正在被容器60afd2...使用,这个ID就是testDcon容器的ID

[root@cos7-1 ~]# docker volume rm 86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3

Error response from daemon: remove 86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3: volume is in use - [60afd20098ed4cb7631ee6eaff5bf4cc9fb5fe8e623c044d95075b637f5d6c9d]

#先停止容器并删除容器

[root@cos7-1 ~]# docker stop testDcon

testDcon

[root@cos7-1 ~]# docker rm testDcon

testDcon

#再次删除卷,已经正常删除了

[root@cos7-1 ~]# docker volume rm 86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3

86bb1cfa654bbeeefc766131de443ebafbadbccb1e789c4e4cd160698c3bead3

[root@cos7-1 ~]#

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     testA

local     testB

可以看到,删除卷的前提是,没有引用对应卷的容器存在。
所以,在删除卷或者删除容器时,你可能会有下面两种需求
一,查看某个容器都使用了哪些卷
二,查看某个卷都被哪些容器使用了
通过如下两条命令,可以满足我们的需求

#通过下例查看testCcon容器都使用了哪些卷

#如下命令其实就是过滤出容器的详细信息中的Mounts段

#如下示例的testCcon容器使用了两个卷,testA和testB

[root@cos7-1 ~]# docker inspect testCcon -f '{{.Mounts}}'

[{volume testB /var/lib/docker/volumes/testB/_data /var/log local z true } {volume testA /var/lib/docker/volumes/testA/_data /data local z true }]

#通过下例查看testA卷被哪些容器使用了

#如下示例中testA卷被testBcontestCcon两个容器使用了

[root@cos7-1 ~]# docker ps -a -f "volume=testA"

CONTAINER ID   IMAGE     COMMAND     CREATED        STATUS                      PORTS     NAMES

9b6d44174554   alpine    "/bin/sh"   21 hours ago   Exited (137) 13 hours ago             testBcon

d8d7470c5d86   alpine    "/bin/sh"   24 hours ago   Exited (137) 13 hours ago             testCcon

除了单独删除某个卷,我们还可以批量删除卷,使用docker volume prune命令,可以批量删除所有的没有被任何容器使用的卷,由于我的测试环境的testA卷和testB卷都有容器在使用,所以使用docker volume prune命令并不能删除它们,为了展示效果,我先提前删除对应的容器

#先删除对应的容器

[root@cos7-1 ~]# docker ps -a

CONTAINER ID   IMAGE     COMMAND     CREATED        STATUS                      PORTS     NAMES

9b6d44174554   alpine    "/bin/sh"   24 hours ago   Exited (137) 16 hours ago             testBcon

d8d7470c5d86   alpine    "/bin/sh"   27 hours ago   Exited (137) 16 hours ago             testCcon

[root@cos7-1 ~]# docker rm testBcon

testBcon

[root@cos7-1 ~]# docker rm testCcon

testCcon

#查看现有卷

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     testA

local     testB

#执行批量删除命令,会出现确认提示,输入y确认删除所有的没有被任何容器引用的卷

[root@cos7-1 ~]# 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:

testA

testB

Total reclaimed space: 14B

#删除完成后,再次查看卷,已经删除

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

匿名卷和命名卷相比,多了一个自动删除的特性,当匿名卷与docker run命令的--rm选项配合使用时,如果容器被停止删除,匿名卷也会随之被自动删除,如果想要搞明白匿名卷自动删除的特性,最好先搞明白docker run命令的--rm选项的作用,--rm选项表示当容器停止时,自动删除对应的容器,示例如下:

#创建一个testAcon容器,使用--rm选项,表示当testAcon容器停止时,会被自动删除

[root@cos7-1 ~]# docker run --rm -td --name testAcon alpine

21f6be3ab1fcf96c72586872cc2faccfbdb5fc1e459d7a3128b04ca743f01449

#查看容器已经正常运行

[root@cos7-1 ~]# docker ps

CONTAINER ID   IMAGE     COMMAND     CREATED         STATUS         PORTS     NAMES

21f6be3ab1fc   alpine    "/bin/sh"   6 seconds ago   Up 5 seconds             testAcon

#停止testAcon容器

[root@cos7-1 ~]# docker stop testAcon

testAcon

#再次查看容器,testAcon容器已经被自动删除

[root@cos7-1 ~]# docker ps -a

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

如上所示,如果创建容器的时候使用了--rm选项,当容器停止时,会被自动删除,当我们频繁的进行测试实验时,--rm选项非常好用,我们可以多次重复进行实验,避免实验过程中不断的生成多余的容器。

如果创建容器时使用了--rm选项,并且同时使用了匿名卷和命名卷,会发现,当容器停止时,匿名卷会随容器被自动删除,而命名卷不会,实验过程如下

#查看容器,查看卷,目前没有任何容器和卷

[root@cos7-1 ~]# docker ps

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

#创建容器时,使用--rm选项,同时使用匿名卷和命名卷

[root@cos7-1 ~]# docker run --rm -td --name testAcon -v /data1 -v testA:/data2 alpine

dc7b0414fc411d7e9ad6770767196927c7d117cad66d2c2718e911de832bd1f3

#再次查看卷和容器

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     e598d33c3a568d4d5e7de8b932c01d750c92c1ceb071d40d183a555742acd16f

local     testA

[root@cos7-1 ~]#

[root@cos7-1 ~]# docker ps

CONTAINER ID   IMAGE     COMMAND     CREATED          STATUS          PORTS     NAMES

dc7b0414fc41   alpine    "/bin/sh"   16 seconds ago   Up 16 seconds             testAcon

#停止容器

[root@cos7-1 ~]# docker stop testAcon

testAcon

#发现容器和匿名卷都自动删除了,但是命名卷仍然保留了下来

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     testA

[root@cos7-1 ~]#

[root@cos7-1 ~]# docker ps -a

CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

无论是命名卷还是匿名卷,它们都存放在/var/lib/docker/volumes目录中,这个目录是固定的,它们都能被docker volume命令管理,除了命名卷和匿名卷,还有一种映射宿主机目录的方法,这种方法被称之为绑定挂载,绑定挂载的英文原文为bind mounts
绑定挂载能够将指定的宿主机目录挂载到容器中,绑定挂载的使用方法也很简单,仍然是使用--volume选项或者-v选项,我们只需要将卷名替换成宿主机上的目录路径即可,示例如下:

#宿主机上的/root/test1目录如下

[root@cos7-1 ~]# ls /root/test1

a  b

#创建一个容器,将宿主机的/root/test1目录映射到容器的/data1目录中,在进行实验时,可以将/root/test1换成任意其他目录。

[root@cos7-1 ~]# docker run --rm -td --name testAcon -v /root/test1:/data1 alpine

9ccbb91679dd92909817d0de851fe9074345fb668787838ec2304fce8047f735

#进入容器,查看/data1目录,与宿主机中的内容相同,退出容器

[root@cos7-1 ~]# docker exec -it testAcon sh

/ #

/ # ls /data1

a  b

/ # exit

绑定挂载不会生成任何卷,它直接将指定的宿主机目录映射到容器中,所以,docker volume命令无法查看或管理到绑定挂载的路径。官方建议使用卷,而不是绑定挂载,但是,绑定挂载有一个优势,就是绑定挂载可以直接将宿主机中的文件(非目录)直接挂载到容器中,比如,将宿主机中的/etc/localtime文件映射到容器中的/etc/localtime文件

docker run --rm -td --name testAcon -v /etc/localtime:/etc/localtime alpine

通常,使用绑定挂载就是为了将宿主机中的配置文件挂载到容器中,如果是整个目录的数据,建议使用卷,卷只能映射目录,不能映射文件。

即使通过绑定挂载将宿主机的单个文件挂载到容器中,基于容器通过docker commit创建出新的镜像后,新镜像中也不会包含对应文件的数据的。
我们可以做一个实验,让A容器使用绑定挂载,把宿主机的a.log挂载到容器的/opt/a.log(注意:实验时请使用有内容的文件,即确保a.log非空),然后基于A容器创建b镜像,最后基于b镜像创建B容器,进入B容器后,会发现/opt/a.log是存在的,但是,B容器中的/opt/a.log是一个空文件,没有任何内容,也就是说,A容器中绑定挂载的a.log的内容并没有通过docker commit命令提交过来,这是为什么呢?之前说过,卷中的数据是独立于容器的,绑定挂载也一样,它们都是宿主机中的数据,当我们把宿主机的a.log挂载到容器的/opt/a.log时,容器会创建'/opt/a.log路径'作为宿主机文件的挂载点,而'/opt/a.log挂载点路径'是在容器A的可读写层中创建的,所以,当基于容器A创建新镜像时,挂载点路径(空文件)也被提交到了新镜像中,但是真正的宿主机文件(数据)并没有随之提交,这就是出现上述现象的原因,具体过程这里就不演示了,快动手试一下吧。

在使用卷或者绑定挂载进行实验时,你可能会纠结一个问题,就是数据覆盖问题,在映射之前,卷和宿主机目录可能为空或者非空,容器目录可能为空或者非空,那么在映射后,到底以哪里的数据为准呢?我总结了一张表,表中写明了映射前后的数据关系(有些容器目录比较特殊,比如/etc,因为/etc目录中有挂载的hosts文件等)。

上文中,无论是使用命名卷、匿名卷还是绑定挂载,都在使用--volume选项或者-v选项,其实,除了这两个选项,还有另一个选项,也能实现同样的效果,这个选项就是--mount--mount选项的使用示例如下

#创建容器test1,使用命名卷testA,映射到容器的/data目录

docker run -td --name test1 --mount type=volume,source=testA,target=/data alpine

#创建容器test2,使用匿名卷,映射到容器的/data目录

docker run -td --name test2 --mount type=volume,target=/data alpine

#创建容器test3,使用绑定挂载,将宿主机的/root/test1目录挂载到/data目录

docker run -td --name test3 --mount type=bind,source=/root/test1/,target=/data alpine

可见,--mount选项的写法似乎更加清晰,在--mount选项中,type用于指定挂载类型,上例中的type=volume表示使用卷,type=bind表示使用绑定挂载,无论是命名卷还是匿名卷,都使用type=volume,当使用卷时,如果使用的是命名卷,就使用source参数指定对应的卷名,如果使用匿名卷,则省略source参数,target参数用于指定将卷或者宿主机目录挂载到容器的哪个目录上,如果使用绑定挂载,source参数对应宿主机上的目录。

上例中,source参数可以写成srctarget参数可以写成dst或者destination,即如下两条命令是等效的。

docker run --rm -td --name test4 --mount type=volume,src=testB,dst=/data alpine

docker run --rm -td --name test4 --mount type=volume,source=testB,target=/data alpine

除了上文聊到的-v选项、--mount选项,还有一个选项我们可能也会用到,它就是--volumes-from选项,--volumes-from选项可以让一个容器去使用另一个容器的卷,比如:
容器A使用了两个卷,当我们创建容器B时,明确告诉容器B容器A用了哪些卷,你就用哪些卷,就好像让B去抄A的作业一样,示例如下

#首先,查看一下当前卷列表,没有任何卷

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

#创建一个test1容器,如下,test1容器使用了一个匿名卷,一个testA卷,一个绑定挂载的宿主机目录

[root@cos7-1 ~]# docker run -td --name test1 -v /data -v testA:/data1 -v /root/test1:/data2 alpine

c1fd78c3ef6ac1e0544646deaf65aed24d8aef47d849e5199c954b6a9308dc52

#再次查看卷列表,匿名卷和testA卷已经自动创建了

[root@cos7-1 ~]# docker volume ls

DRIVER    VOLUME NAME

local     7f58548a996cb64f17c0a1e4679e9cf88c6fc87751623f090280f9651b85db81

local     testA

#此时,创建一个test2容器,下例命令中的--volumes-from test1表示让test2照抄test1的卷(以及绑定挂载)

[root@cos7-1 ~]# docker run -td --name test2 --volumes-from test1  alpine

d7ac879750abb71480cfe630c379466db5d2c713b80b39a2451d0493ec814c49

#查看test1容器和test2容器的挂载列表,发现它们挂载的卷(以及宿主机目录)都是相同的,对应的容器中的目录也是相同的。

[root@cos7-1 ~]# docker inspect test1 -f '{{.Mounts}}'

[{volume testA /var/lib/docker/volumes/testA/_data /data1 local z true } {bind  /root/test1 /data2   true rprivate} {volume 7f58548a996cb64f17c0a1e4679e9cf88c6fc87751623f090280f9651b85db81 /var/lib/docker/volumes/7f58548a996cb64f17c0a1e4679e9cf88c6fc87751623f090280f9651b85db81/_data /data local  true }]

[root@cos7-1 ~]#

[root@cos7-1 ~]# docker inspect test2 -f '{{.Mounts}}'

[{volume 7f58548a996cb64f17c0a1e4679e9cf88c6fc87751623f090280f9651b85db81 /var/lib/docker/volumes/7f58548a996cb64f17c0a1e4679e9cf88c6fc87751623f090280f9651b85db81/_data /data local  true } {volume testA /var/lib/docker/volumes/testA/_data /data1 local  true } {bind  /root/test1 /data2   true rprivate}]

[root@cos7-1 ~]#

--volumes-from选项也可以多次使用,从而指定多个容器,比如

--volumes-from test3 --volumes-from test5

这篇文章中总结了数据卷相关的常用选项,卷还有一些其他内容,文章中没有总结,这些内容我们可以参考官网文档的volumes章节,地址如下
https://docs.docker.com/storage/volumes 

 转载:原文地址docker-朱双印博客 (zsythink.net)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值