专注于Docker、Container等技术的交流和分享
挖掘最有价值的资讯动态、教程、国内外优秀资源

从Docker零基础到懂一点实践教程(八)

Docker容器的数据管理

Docker容器的数据卷

Docker的设计理念是提供应用程序之间的隔离,因此Docker容器的生存周期通常与应用程序是一致的,但我们对数据的生存周期却有着不一样的需求。此外,我们也需要一个在容器之间共享数据的机制。这些因素催生了Docker数据卷的产生。

Docker数据卷是经过特殊设计的目录,可以绕过联合文件系统(UFS),为一个或多个容器提供访问。数据卷设计的目的在于对数据的持久化,它完全独立于容器的生命周期,因此Docker不会在删除容器时删除其挂载的数据卷,也不会存在类似垃圾收集的机制。

┌────────────────────────────────────────┐
│             Docker Host                │
│                                        │
│┌────────────────┐┌────────────────────┐│
││    Docker      ││ Local File System  ││
││                ││                    ││
││ ┌──────────┐   ││                    ││
││ │Container1│-------------            ││
││ └──────────┘   ││        |           ││
││ ┌──────────┐   ││   ┌──────────────┐ ││
││ │Container2│--------│Directory/File│ ││
││ └──────────┘   ││   └──────────────┘ ││
││ ┌──────────┐   ││        |           ││
││ │Container3│-------------            ││
││ └──────────┘   ││                    ││
│└────────────────┘└────────────────────┘│
└────────────────────────────────────────┘

Docker数据卷的特点:
1. 数据卷是存在于Docker宿主机操作系统中的目录或文件;
2. 数据卷可以实现容器与宿主机之间的数据共享;
3. 数据卷在容器启动时初始化,如果容器使用的镜像在挂载点包含数据,这些数据会被拷贝到数据卷中;
4. 数据卷可以在多个容器之间共享和重用;
5. 数据卷里的内容可以被容器直接且实时地修改;
6. 数据卷的变化不会影响到镜像的更新;
7. 数据卷会一直存在,即便挂载数据卷的容器已经被删除;

通过命令挂载数据卷

我们可以使用docker run命令的-v选项,指定数据卷在宿主机中的位置,容器中的挂载点,以及挂载时的权限(默认是可读可写的):$ sudo docker run -it -v ~/container_data:/data:ro ubuntu /bin/bash

下面我们就来创建两个容器,它们都将宿主机中的“~/container_data”目录作为数据卷,并挂载到了自己的“/data”上,唯一的不同是它们的权限是否可写:

schen@scvmu01:~$ mkdir ~/container_data
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run -it -v ~/container_data:/data --name dv_test1 ubuntu /bin/bash
root@4f2393e92f0a:/# 
root@4f2393e92f0a:/# ls /data
root@4f2393e92f0a:/# 
root@4f2393e92f0a:/# echo "I'm in container dv_test1!" > /data/dv1
root@4f2393e92f0a:/# 
root@4f2393e92f0a:/# ls /data
dv1
root@4f2393e92f0a:/# 
root@4f2393e92f0a:/# schen@scvmu01:~$ 
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run -it -v ~/container_data:/data:ro --name dv_test2 ubuntu /bin/bash
root@880aa5bac52c:/# 
root@880aa5bac52c:/# ls /data
dv1
root@880aa5bac52c:/# 
root@880aa5bac52c:/# cat /data/dv1 
I'm in container dv_test1!
root@880aa5bac52c:/# 
root@880aa5bac52c:/# echo "I'm in container dv_test2!" > /data/dv2
bash: /data/dv2: Read-only file system
root@880aa5bac52c:/# 
root@880aa5bac52c:/# ls /data/
dv1
root@880aa5bac52c:/# 
root@880aa5bac52c:/# schen@scvmu01:~$ 
schen@scvmu01:~$ 
schen@scvmu01:~$ docker inspect dv_test1
......
        "Mounts": [
            {
                "Source": "/home/schen/container_data",
                "Destination": "/data",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }
        ],
......
schen@scvmu01:~$ 
schen@scvmu01:~$ docker inspect dv_test2
......
        "Mounts": [
            {
                "Source": "/home/schen/container_data",
                "Destination": "/data",
                "Mode": "ro",
                "RW": false,
                "Propagation": "rprivate"
            }
        ],
......
schen@scvmu01:~$ 

通过构建镜像使用数据卷

我们也可以通过Dockerfile的VOLUME指令构建带有数据卷的镜像,与使用docker run命令挂载数据卷不同的是,VOLUME指令所创建的数据卷是不能映射到已经存在的本地文件或目录中的,在镜像构建时指定的数据卷会在容器启动时创建对应的数据卷,并且相同镜像所启动的不同容器,会创建不同的数据卷。

为了演示,我们创建一个Dockerfile,它包含有一条VOLUME指令,并且指定创建两个数据卷:

schen@scvmu01:~/dockerfile/dv_test1$ cat Dockerfile 
# Data volume test
FROM ubuntu:16.04
VOLUME ["/datavolume1", "/datavolume2"]
CMD /bin/bash
schen@scvmu01:~/dockerfile/dv_test1$ 

下面我们通过这个Dockerfile构建镜像,并启动两个容器:

schen@scvmu01:~/dockerfile/dv_test1$ docker build -t shichen/dvt .
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:16.04
 ---> bd3d4369aebc
Step 2 : VOLUME /datavolume1 /datavolume2
 ---> Running in d9145d832e91
 ---> 7e241dced923
Removing intermediate container d9145d832e91
Step 3 : CMD /bin/bash
 ---> Running in 5a8c39fb2f70
 ---> 4be45feabc99
Removing intermediate container 5a8c39fb2f70
Successfully built 4be45feabc99
schen@scvmu01:~/dockerfile/dv_test1$ 
schen@scvmu01:~/dockerfile/dv_test1$ docker run -it --name dv_test3 shichen/dvt
root@c7daa2b38455:/# ls -la /datavolume*
/datavolume1:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 05:48 .
drwxr-xr-x 36 root root 4096 Nov  6 05:48 ..

/datavolume2:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 05:48 .
drwxr-xr-x 36 root root 4096 Nov  6 05:48 ..
root@c7daa2b38455:/# 
root@c7daa2b38455:/# schen@scvmu01:~/dockerfile/dv_test1$
schen@scvmu01:~/dockerfile/dv_test1$ 
schen@scvmu01:~/dockerfile/dv_test1$ docker run -it --name dv_test4 shichen/dvt
root@0bfb674ae6b3:/# 
root@0bfb674ae6b3:/# ls -la /datavolume*
/datavolume1:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 05:49 .
drwxr-xr-x 36 root root 4096 Nov  6 05:49 ..

/datavolume2:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 05:49 .
drwxr-xr-x 36 root root 4096 Nov  6 05:49 ..
root@0bfb674ae6b3:/# 
root@0bfb674ae6b3:/# schen@scvmu01:~/dockerfile/dv_test1$ 
schen@scvmu01:~/dockerfile/dv_test1$ 

我们看到,这两个容器各有两个数据卷,虽然挂载点的名字相同,但实际它们是不能互相访问的。我们可以通过docker inspect命令清楚地看到它们在本地映射的并不是相同的数据卷:

schen@scvmu01:~$ docker inspect --format='{{range $i := .Mounts}}{{$i.Destination}} -> {{$i.Source}}
{{end}}' dv_test3
/datavolume1 -> /var/lib/docker/volumes/aaf345c38ea3d92bc71fcd5392cad0318f1c38af0ac2c5301e816ce9c30f0311/_data
/datavolume2 -> /var/lib/docker/volumes/922d5a4f29d9e02123d780017b3e2415215b9eaf3b7ac76e9605e15716f3163b/_data

schen@scvmu01:~$ 
schen@scvmu01:~$ docker inspect --format='{{range $i := .Mounts}}{{$i.Destination}} -> {{$i.Source}}
{{end}}' dv_test4
/datavolume1 -> /var/lib/docker/volumes/e7fa53efde00770ee77dd3045e02fa2e9b9181934ba3610ddb921d5cf80ea90f/_data
/datavolume2 -> /var/lib/docker/volumes/9f1e70acd2a046207325981f7a299d4cb16be29f6d7bd01f221688400ea46e59/_data

schen@scvmu01:~$ 

那么,当我们不能访问到本地目录时,如何在容器之间共享数据呢?这就涉及到我们下一章节所讲到的“数据卷容器”的概念。

Docker的数据卷容器

数据卷容器

命名的容器挂载数据卷,其他容器通过挂载这个容器实现数据共享,挂载数据卷的容器,就叫做数据卷容器。

┌────────────────────────────────────────────────────────────┐
                        Docker Host                         
                                                            
│┌───────────────────────────────────┐┌─────────────────────┐│
││              Docker               ││  Local File System  ││
││                                   ││                     ││
││ ┌──────────┐                      ││                     ││
││ │Container1│----------            ││                     ││
││ └──────────┘          |           ││                     ││
││ ┌──────────┐    ┌──────────────┐  ││   ┌──────────────┐  ││
││ │Container2│----│Data Container│-------│Directory/File│  ││
││ └──────────┘    └──────────────┘  ││   └──────────────┘  ││
││ ┌──────────┐          |           ││                     ││
││ │Container3│----------            ││                     ││
││ └──────────┘                      ││                     ││
│└───────────────────────────────────┘└─────────────────────┘│
└────────────────────────────────────────────────────────────┘

使用数据卷容器

我们可以通过使用docker run命令的--volumes-from选项挂载数据卷容器。该命令的完整格式为:$ docker run --volumes-from [CONTAINER_NAME],这里的“CONTAINER_NAME”就是包含有数据卷的容器的名字。

首先,我们来创建一个数据卷容器,并在其中写入一个文件:

schen@scvmu01:~$ docker run -it --name dv_test5 shichen/dvt
root@0268b9e7c96a:/# 
root@0268b9e7c96a:/# touch /datavolume1/dvt5_1
root@0268b9e7c96a:/# 
root@0268b9e7c96a:/# ls /datavolume1
dvt5_1
root@0268b9e7c96a:/# 
root@0268b9e7c96a:/# exit   
exit
schen@scvmu01:~$ 

然后我们将这个容器“dv_test5”作为数据卷容器,运行两个新的容器“dv_test6”和“dv_test7”,并在其中各写入一个文件:

schen@scvmu01:~$ docker run -it --name dv_test6 --volumes-from dv_test5 ubuntu
root@e6cdc884e449:/# 
root@e6cdc884e449:/# touch /datavolume1/dvt6_1 
root@e6cdc884e449:/# 
root@e6cdc884e449:/# ls /datavolume1
dvt5_1  dvt6_1
root@e6cdc884e449:/#  
root@e6cdc884e449:/# exit
exit
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run -it --name dv_test7 --volumes-from dv_test5 ubuntu
root@0286d712eb0a:/# 
root@0286d712eb0a:/# touch /datavolume1/dvt7_1      
root@0286d712eb0a:/# 
root@0286d712eb0a:/# ls /datavolume1          
dvt5_1  dvt6_1  dvt7_1
root@0286d712eb0a:/# 
root@0286d712eb0a:/# exit
exit
schen@scvmu01:~$ 

我们发现容器“dv_test6”和“dv_test7”都使用了“dv_test5”的数据卷,通过docker inspect命令我们可以更直观地看到这个信息:

schen@scvmu01:~$ docker inspect --format='{{range $i := .Mounts}}{{$i.Destination}} -> {{$i.Source}}
{{end}}' dv_test5
/datavolume1 -> /var/lib/docker/volumes/3c72e900d0ba4a5bc78d262356cd547efeef2994b8603269d2c5f8355b35c134/_data
/datavolume2 -> /var/lib/docker/volumes/1dd96105a517f850af8530b6d2e3177545f8f7854c6239285bea26cfe180dc3c/_data

schen@scvmu01:~$ 
schen@scvmu01:~$ docker inspect --format='{{range $i := .Mounts}}{{$i.Destination}} -> {{$i.Source}}
{{end}}' dv_test6
/datavolume1 -> /var/lib/docker/volumes/3c72e900d0ba4a5bc78d262356cd547efeef2994b8603269d2c5f8355b35c134/_data
/datavolume2 -> /var/lib/docker/volumes/1dd96105a517f850af8530b6d2e3177545f8f7854c6239285bea26cfe180dc3c/_data

schen@scvmu01:~$ 
schen@scvmu01:~$ docker inspect --format='{{range $i := .Mounts}}{{$i.Destination}} -> {{$i.Source}}
{{end}}' dv_test7
/datavolume1 -> /var/lib/docker/volumes/3c72e900d0ba4a5bc78d262356cd547efeef2994b8603269d2c5f8355b35c134/_data
/datavolume2 -> /var/lib/docker/volumes/1dd96105a517f850af8530b6d2e3177545f8f7854c6239285bea26cfe180dc3c/_data

schen@scvmu01:~$ 

由此可见,使用--volumes-from选项,会将数据卷容器中有关数据卷的信息拷贝到新启动的容器中。

现在我们来做一个试验,在容器全部停止的状态下,使用docker rm -v命令来删除容器“dv_test5”,指定-v选项是为了告诉Docker在删除容器的同时,也删除掉与容器相关联的数据卷:

schen@scvmu01:~$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
schen@scvmu01:~$ 
schen@scvmu01:~$ docker rm -v dv_test5
dv_test5
schen@scvmu01:~$ 
schen@scvmu01:~$ docker restart dv_test6
dv_test6
schen@scvmu01:~$ 
schen@scvmu01:~$ docker attach dv_test6
root@e6cdc884e449:/# 
root@e6cdc884e449:/# touch /datavolume1/dvt6_2
root@e6cdc884e449:/# 
root@e6cdc884e449:/# ls /datavolume1
dvt5_1  dvt6_1  dvt6_2  dvt7_1
root@e6cdc884e449:/# 
root@e6cdc884e449:/# exit
exit
schen@scvmu01:~$ 

试验的结果是Docker并没有删除容器“dv_test5”所关联的数据卷,这是因为该数据卷仍被其他容器所关联,在这种情况下Docker并不会删除这些数据卷。

小结

使用数据卷容器,可以很方便地在容器之间共享数据,同时我们并不需要使用者确切地连接到一个已知的Docker宿主机上的文件或目录,这一点在多租户环境中十分重要,因为在这种情况下我们并不想暴露服务器过多的实际信息。

Docker数据卷的备份和还原

既然使用数据卷是为了保存数据,那么就不可避免地涉及到数据的备份、还原以及迁移等操作。在这些情况下,我们可以通过使用数据卷技术,完成对数据进行操作的各种任务。

数据备份

我们可以使用如下的命令备份数据卷中的数据:
docker run --volumes-from [CONTAINER_NAME] -v $(pwd)/backup:/backup ubuntu tar -cvf /backup/data_volume_name.tar [DATA_VOLUME_IN_CONTAINER]

为了方便理解,请看下面的图示:

┌───────────────────────────────────────┐
│             Docker Host               │
│                                       │
│┌────────────────┐┌───────────────────┐│
││    Docker      ││ Local File System ││
││                ││                   ││
││ ┌──────────┐   ││   ┌─────────────┐ ││
││ │Container1│<-------│ DATA_VOLUME │ ││
││ └──────────┘   ││   └─────────────┘ ││
││    |    ..................:         ││
││    v    v      ││                   ││
││ ┌──────────┐   ││   ┌─────────────┐ ││
││ │Container2│<-------│$(pwd)/backup│ ││
││ └──────────┘   ││   └─────────────┘ ││
│└────────────────┘└───────────────────┘│
└───────────────────────────────────────┘

执行该备份命令的容器就是图中的“Container2”,首先它会通过--volumes-from选项从要备份的容器“Container1”处引入其数据卷,这步完成后就建立了图中“DATA_VOLUME”到“Container2”的映射关系,然后它会通过-v选项挂载用于备份的数据卷,也就是图中“$(pwd)/backup”到“Container2”的映射关系。同时映射了这些数据卷后,不论是通过复制还是打包,都能轻易地将数据备份出来。这里我们正是通过tar命令完成备份操作的。

我们以备份容器“dv_test6”上的数据卷为例进行演示:

schen@scvmu01:~$ ls
container_data  dockerfile
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run --volumes-from dv_test6 -v $(pwd)/backup:/backup ubuntu tar -cvf /backup/datavolumes.tar /datavolume1 /datavolume2
tar: Removing leading `/' from member names
/datavolume1/
/datavolume1/dvt5_1
/datavolume1/dvt7_1
/datavolume1/dvt6_2
/datavolume1/dvt6_1
/datavolume2/
schen@scvmu01:~$ 
schen@scvmu01:~$ ls
backup  container_data  dockerfile
schen@scvmu01:~$ 
schen@scvmu01:~$ tar -tvf ./backup/datavolumes.tar 
drwxr-xr-x root/root         0 2016-11-06 17:34 datavolume1/
-rw-r--r-- root/root         0 2016-11-06 16:55 datavolume1/dvt5_1
-rw-r--r-- root/root         0 2016-11-06 17:10 datavolume1/dvt7_1
-rw-r--r-- root/root         0 2016-11-06 17:34 datavolume1/dvt6_2
-rw-r--r-- root/root         0 2016-11-06 16:59 datavolume1/dvt6_1
drwxr-xr-x root/root         0 2016-11-06 16:55 datavolume2/
schen@scvmu01:~$ 

还原数据

同样,我们也可以使用如下的命令还原数据卷中的数据:
docker run --volumes-from [CONTAINER_NAME] -v $(pwd)/backup:/backup ubuntu tar -xvf /backup/data_volume_name.tar -C [ROOT_PATH_FOR_DATA_VOLUME_IN_CONTAINER]

schen@scvmu01:~$ docker restart dv_test6
dv_test6
schen@scvmu01:~$ 
schen@scvmu01:~$ docker attach dv_test6
root@e6cdc884e449:/# 
root@e6cdc884e449:/# rm /datavolume*/*
root@e6cdc884e449:/# 
root@e6cdc884e449:/# ls -la /datavolume*
/datavolume1:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 11:33 .
drwxr-xr-x 37 root root 4096 Nov  6 11:32 ..

/datavolume2:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 08:55 .
drwxr-xr-x 37 root root 4096 Nov  6 11:32 ..
root@e6cdc884e449:/# 
root@e6cdc884e449:/# schen@scvmu01:~$ 
schen@scvmu01:~$ 
schen@scvmu01:~$ docker run --volumes-from dv_test6 -v $(pwd)/backup:/backup ubuntu tar -xvf /backup/datavolumes.tar -C /
datavolume1/
datavolume1/dvt5_1
datavolume1/dvt7_1
datavolume1/dvt6_2
datavolume1/dvt6_1
datavolume2/
schen@scvmu01:~$ 
schen@scvmu01:~$ docker attach dv_test6
root@e6cdc884e449:/# 
root@e6cdc884e449:/# ls -la /datavolume*
/datavolume1:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 09:34 .
drwxr-xr-x 37 root root 4096 Nov  6 11:32 ..
-rw-r--r--  1 root root    0 Nov  6 08:55 dvt5_1
-rw-r--r--  1 root root    0 Nov  6 08:59 dvt6_1
-rw-r--r--  1 root root    0 Nov  6 09:34 dvt6_2
-rw-r--r--  1 root root    0 Nov  6 09:10 dvt7_1

/datavolume2:
total 8
drwxr-xr-x  2 root root 4096 Nov  6 08:55 .
drwxr-xr-x 37 root root 4096 Nov  6 11:32 ..
root@e6cdc884e449:/# 

文章转自CSDN,点击查看原文

鲸鱼云公众号:dockercloud

鲸鱼云公众号

转载请加上原文链接和本文链接:鲸鱼云 » 从Docker零基础到懂一点实践教程(八)

分享到:更多 ()

评论 抢沙发

评论前必须登录!