记录生活中的点点滴滴

0%

Docker学习4:容器数据卷

这一篇写一下容器数据卷技术,怎样将Docker内文件与宿主机进行同步!

容器数据卷概念

什么是数据卷volume?

为了了解什么是Docker Volume,首先我们需要明确Docker内的文件系统是如何工作的。

Docker镜像被存储在一系列的只读层。当我们开启一个容器,Docker读取只读镜像并添加一个读写层在顶部。

如果正在运行的容器修改了现有的文件,该文件将被拷贝出底层的只读层到最顶层的读写层。

在读写层中的旧版本文件隐藏于该文件之下,但并没有被不破坏 - 它仍然存在于镜像以下。

当Docker的容器被删除,然后重新启动镜像时,将开启一个没有任何更改的新的容器 - 这些更改会丢失。

此只读层及在顶部的读写层的组合被Docker称为Union File System(联合文件系统)。

为了能够保存(持久)数据以及共享容器间的数据,Docker提出了Volumes的概念。

很简单,volumes是目录(或者文件),它们是外部默认的联合文件系统或者是存在于宿主文件系统正常的目录和文件。

为什么使用数据卷volume?

Docker的镜像是由一系列的只读层组合而来,当启动一个容器的时候,Docker加载镜像的所有只读层,并在最上层加入一个读写层。

这个设计使得Docker可以提高镜像构建、存储和分发的效率,节省了时间和存储空间,然而也存在如下问题:

  • 容器中的文件在宿主机上存在形式复杂,不能在宿主机上很方便的对容器中的文件进行访问

  • 多个容器之间的数据无法共享

  • 当删除容器时,容器产生的数据将丢失

为了解决这些问题,Docker引入了数据卷(volume)机制。

volume是存在一个或多个容器中的特定文件或文件夹,这个目录能够独立于联合文件系统的形式在宿主机中存在,并为数据的共享与持久提供以下便利:

  • volume在容器创建时就初始化,在容器运行时就可以使用其中的文件
  • volume能在不同的容器之间共享和重用
  • 对volume中的数据的操作会马上生效
  • 对volume中数据操作不会影响到镜像本身
  • volume的生存周期独立于容器的生存周期,即使删除容器,volume仍然会存在,没有任何容器使用的volume也不会被Docker删除

如何使用容器数据卷

方式一:从容器挂载volume(-v /path)

在使用 docker run 创建新容器的时候,可以使用 -v 标签为容器添加数据卷 volume,以下用法是从容器中的某个文件夹创建 volume,如果容器中指定的文件夹不存在,会自动生成

先创建一个新的centos容器,将它的home下的gs目录挂载在主机上:

1
2
3
4
[root@localhost /]# docker run -it --name test01 -v /home/gs centos /bin/bash
[root@9eca9e8976c2 /]# cd home/gs/
[root@9eca9e8976c2 gs]# ls
[root@9eca9e8976c2 gs]# [root@localhost /]# #ctrl p q退出

接下来使用 docker inspect xx 命令,查看下容器详情,找Mounts挂载:

这就是gs文件夹挂载的目录,我们先cd到这里面,然后创建一个文件并写入内容

1
2
3
4
5
[root@localhost /]# cd /var/lib/docker/volumes/f8072ff6fda01441bfa0e51e74fcefab2937b1e352a8d5293232e2befea5fb45/_data
[root@localhost _data]# ls
[root@localhost _data]# vim a.txt
[root@localhost _data]# cat a.txt
hello world!

接下来我们看看 centos 里面的gs目录下是什么情况?

1
2
3
4
5
6
[root@localhost _data]# docker exec -it 9eca9e8976c2 /bin/bash
[root@9eca9e8976c2 /]# cd home/gs/
[root@9eca9e8976c2 gs]# ls
a.txt
[root@9eca9e8976c2 gs]# cat a.txt
hello world!

可以看到,当容器上的volume有变动时,宿主机也会跟着变动,那反过来呢?经测试也是一样的。不管是容器挂载点发生变动还是宿主机对挂载目录进行操作,令一方都会跟着变动。

利用docker commit生成新镜像,然后docker run -it 运行新镜像,发现容器挂载目录下没有任何文件了。说明生成新镜像时,是不保存挂载文件的。

方式二:从宿主机挂载volume(-v /host-path:/container-path)

上面我们没有设置主机的同步目录,docker会自动设置,我们可以通过 docker inspect xxx 命令查到具体挂载的目录,下面我们自己设置具体要挂载到哪里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 创建新的centos容器,将/home/gs与主机的/root/gs关联
[root@localhost _data]# docker run -it --name test02 -v /root/gs:/home/gs centos /bin/bash
# 下面进入到了刚创建的centos容器里
[root@eee855e9ad89 /]# ls
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var

# 退出容器,回到主机
[root@eee855e9ad89 /]# [root@localhost _data]#
[root@localhost _data]#
# 进入/root/gs创建文件并写入内容
[root@localhost _data]# cd /root/gs
[root@localhost gs]# ls
[root@localhost gs]# vim a.txt
[root@localhost gs]# cat a.txt
hello world!
# 再次进入centos容器查看
[root@localhost gs]# docker exec -it eee855e9ad89 /bin/bash
[root@eee855e9ad89 /]# cd home/gs/
# 可以看到文件已经和主机的进行了同步
[root@eee855e9ad89 gs]# cat a.txt
hello world!
[root@eee855e9ad89 gs]#

我们还可以查看容器的详细信息,看看Mounts挂载的信息:

实战:mysql同步化数据

利用容器数据卷技术来完成mysql数据的同步化:

1
2
3
4
5
6
7
# 创建mysql容器并进行同步化
[root@localhost ~]# docker run -d -p 3310:3306 -v /root/mysql/conf:/etc/mysql/conf.d -v /root/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mys
ql:5.7
d9c5178525a2d50c383cb71531099ebf4713485b1dbd55f0881f168ed4904dd0
# 列出root目录下,发现多了这个mysql文件夹
[root@localhost ~]# ls
anaconda-ks.cfg download gs gs.txt mysql

因为我们上面将mysql的3306端口映射到了主机的3310端口,所以我们通过SQLyog客户端进行连接,发现可以连接成功。我们可以在上面随便建立数据库和表如下:

这个时候,回到我们的主机查看文件夹里面,可以看到数据确实是同步的。

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# cd mysql/
[root@localhost mysql]# ls
conf data
[root@localhost mysql]# cd data/
[root@localhost data]# ls
auto.cnf ca.pem client-key.pem ibdata1 ib_logfile1 mydatabase performance_schema public_key.pem server-key.pem
ca-key.pem client-cert.pem ib_buffer_pool ib_logfile0 ibtmp1 mysql private_key.pem server-cert.pem sys
[root@localhost data]# cd mydatabase/
[root@localhost mydatabase]# ls
db.opt stu.frm stu.ibd

这就完成了数据的同步。

具名挂载和匿名挂载

上面我们讲的两种挂载方式其实就是匿名挂载和容器内挂载,还有一个具名挂载!

什么意思呢?其实就是挂载的文件目录对应一个主机上的一个docker数据卷,就像上面我们不指定挂载目录,docker会自动帮我们生产一个目录挂载,这就是对应的数据卷,我们可以通过 docker volumes 命令查看,先看看帮助文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost ~]# 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.

先看看 ls

1
2
3
4
5
6
7
[root@localhost ~]# docker volume ls
DRIVER VOLUME NAME
local 96a5647b8409c79dd928d13ac12b3be06ee8b6ded6fcbc3045412f67731fe9fd
local f8072ff6fda01441bfa0e51e74fcefab2937b1e352a8d5293232e2befea5fb45
local f615391401a4b44ddd020282e9ad8610aff4f47c1148effddd59493f32a4d963
local fa56fe8b0a0bccc133ac32154c20f8092464bc017c41b55eaa7838232c84cffc
[root@localhost ~]#

可以看到这些匿名的数据卷,我们还可以用 inspect 来查看具体数据卷的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]# docker volume inspect 96a5647b8409c79dd928d13ac12b3be06ee8b6ded6fcbc3045412f67731fe9fd
[
{
"CreatedAt": "2021-01-15T22:16:33+08:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/96a5647b8409c79dd928d13ac12b3be06ee8b6ded6fcbc3045412f67731fe9fd/_data",
"Name": "96a5647b8409c79dd928d13ac12b3be06ee8b6ded6fcbc3045412f67731fe9fd",
"Options": null,
"Scope": "local"
}
]
[root@localhost ~]#

可以看到具体这个具体数据卷的详细信息。

我们其实可以自己指定这个数据卷的名称,这就是具名挂载。

1
2
3
4
5
6
7
8
9
10
11
# 使用具名挂载
[root@localhost ~]# docker run -d -v nginx02:/etc/nginx -p 8080:80 --name nginx02 nginx
99d460f3c9f4e4544538cbdfb749900c943cda129d9249ed572301bcf4458708
# 查看数据卷,可以看到我们具名的nginx02数据卷
[root@localhost ~]# docker volume ls
DRIVER VOLUME NAME
local 96a5647b8409c79dd928d13ac12b3be06ee8b6ded6fcbc3045412f67731fe9fd
local f8072ff6fda01441bfa0e51e74fcefab2937b1e352a8d5293232e2befea5fb45
local f615391401a4b44ddd020282e9ad8610aff4f47c1148effddd59493f32a4d963
local fa56fe8b0a0bccc133ac32154c20f8092464bc017c41b55eaa7838232c84cffc
local nginx02

注意这和具体容器内挂载不一样:

1
2
3
4
5
6
7
8
# 具体容器内挂载 -v 后面是/路径
docker run -d -v /root/nginx:/etc/nginx centos

# 具名挂在 -v 后面没有/,就是一个名字
docker run -d -v nginx02:/etc/nginx nginx

# 匿名挂载
docker run -d --v /etc/nginx centos

拓展:

1
2
3
4
5
# 通过 -v 容器内路径:ro 只可读 rw 可读可写
docker run -d -v /root/nginx:/etc/nginx:ro centos
docker run -d -v /root/nginx:/etc/nginx:rw centos

# 默认是rw,ro说明只能通过主机来操作,容器内部是无法操作的

初始Dockerfile

Dockerfile 就是用来构建 docker 镜像的构建文件!命令脚本!

我们之前通过 commit 的方式可以手动提交来构建我们自定义的镜像,当然我们也可以通过 Dockerfile 这种命令脚本来构建我们自己的 docker 镜像!

镜像是一层一层的,所以脚本是一层一层的命令,每个命令都是一层!

下面我们先简单体验一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
[root@localhost ~]# pwd
/root
# 创建Dockerfile文件,并写入内容
[root@localhost ~]# vim Dockerfile
[root@localhost ~]# cat Dockerfile
FROM centos

VOLUME ["volume01","volume02"]

CMD echo "---end---"
CMD /bin/bash
# 通过 docker build . 命令来构建我们自定义的镜像
[root@localhost ~]# docker build -f Dockerfile -t gs/centos:1.0 .
# 下面是具体的构建过程
Sending build context to Docker daemon 396.1MB
Step 1/4 : FROM centos
---> 300e315adb2f
Step 2/4 : VOLUME ["volume01","volume02"]
---> Running in 868208fbc4f4
Removing intermediate container 868208fbc4f4
---> 08026b1be1ec
Step 3/4 : CMD echo "---end---"
---> Running in 6e542ea81df4
Removing intermediate container 6e542ea81df4
---> 32f75f46ac84
Step 4/4 : CMD /bin/bash
---> Running in 7bae5028e39d
Removing intermediate container 7bae5028e39d
---> 0e1fa7e929d3
Successfully built 0e1fa7e929d3
Successfully tagged gs/centos:1.0

# 可以查看一下现在docker的镜像,就可以发现我们的 gs/centos 了
[root@localhost ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
gs/centos 1.0 0e1fa7e929d3 42 seconds ago 209MB
mytomcat 1.0 55a29a5d0ce3 15 hours ago 653MB
tomcat 9.0 040bdb29ab37 2 days ago 649MB
nginx latest f6d0b4767a6c 3 days ago 133MB
mysql 5.7 cc8775c0fe94 3 days ago 449MB
mysql latest d4c3cafb11d5 3 days ago 545MB
centos latest 300e315adb2f 5 weeks ago 209MB
hello-world latest bf756fb1ae65 12 months ago 13.3kB
[root@localhost ~]#

下面我们开启我们自定义镜像的容器来测试一下:

1
2
3
4
5
[root@localhost ~]# docker run -it gs/centos:1.0 /bin/bash
[root@4448e6241a4a /]# ls
bin etc lib lost+found mnt proc run srv tmp var volume02
dev home lib64 media opt root sbin sys usr volume01
[root@4448e6241a4a /]#

可以看到我们自定义的 volume01 volume02 数据卷目录已经挂载成功了!

我们可以用 docker inspect 查看一下这个容器的详细信息看看挂载在主机的哪里。

数据卷容器

多个mysql同步数据!

我们可以通过 --volumes-from xxx 来做到这一效果,让多个容器之间同步数据!

1
# 下面我们创建三个 gs/centos 容器,让它们三个之间的 volume01 volume02这两个文件夹之间同步数据!
1
2
3
4
5
# 先创建centos01容器
[root@localhost ~]# docker run -it --name centos01 gs/centos:1.0 /bin/bash
[root@ecde4be3e9f0 /]# ls
bin etc lib lost+found mnt proc run srv tmp var volume02
dev home lib64 media opt root sbin sys usr volume01
1
2
3
4
5
6
# 通过 --volumes-from centos01 同步
[root@localhost ~]# docker run -it --name centos02 --volumes-from centos01 gs/centos:1.0 /bin/bash
[root@fb55f3f4d9a1 /]# ls
bin etc lib lost+found mnt proc run srv tmp var volume02
dev home lib64 media opt root sbin sys usr volume01
[root@fb55f3f4d9a1 /]#

下面我们先做个测试,现在centos01容器的volume01目录下创建 a.txt 并写入一些内容:

1
2
3
4
5
6
7
8
[root@localhost ~]# docker exec -it ecde4be3e9f0 /bin/bash
[root@ecde4be3e9f0 /]# ls
bin etc lib lost+found mnt proc run srv tmp var volume02
dev home lib64 media opt root sbin sys usr volume01
[root@ecde4be3e9f0 /]# cd volume01
[root@ecde4be3e9f0 volume01]# vim a.txt
[root@ecde4be3e9f0 volume01]# cat a.txt
hello world!

然后再进入centos02容器看看是否同步:

1
2
3
4
[root@localhost ~]# docker exec -it fb55f3f4d9a1 /bin/bash
[root@fb55f3f4d9a1 /]# cd volume01
[root@fb55f3f4d9a1 volume01]# cat a.txt
hello world!

可以看到已经同步了,同理我们再创建 centos03 容器:

1
2
3
4
5
[root@localhost ~]# docker run -it --name centos03 --volumes-from centos01 gs/centos:1.0 /bin/bash
[root@047d0898517b /]# cd volume01
[root@047d0898517b volume01]# cat a.txt
hello world!
[root@047d0898517b volume01]#

搞定了!

如果想要做到多个mysql之间共享数据的话,利用上面学的命令,很简单就完成了:

1
2
3
4
docker run -d -p 3310:3306 -v /etc/mysql/conf.d /var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 --name mysql01 mysql:5.7


docker run -d -p 3310:3306 -e MYSQL_ROOT_PASSWORD=123456 --name mysql02 --volumes-from mysql01 mysql:5.7

结论:

容器之间配置信息的传递,数据卷的容器的生命周期一直持续到没有容器为止!

但是一旦信息持久化到了本地,这个时候数据是不会被删除的!