Docker 容器数据卷

⚠ 转载请注明出处:作者:ZobinHuang,更新日期:July 11 2021


知识共享许可协议

    本作品ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。


目录

有特定需要的内容直接跳转到相关章节查看即可。

    Section 1. Motivation:为什么需要数据卷?

    Section 2. 通过命令构建数据卷:演示了通过命令构建数据卷的方法

    Section 3. 基于数据卷挂载部署 MySQL:演示了基于数据卷挂载的 MySQL 部署

    Section 4. 具名挂载和匿名挂载:演示了挂载时不声明主机挂载目录的情况

    Section 5. 增设权限:演示了给挂载的数据卷增设只读权限的操作

    Section 6. 通过 DockerFile 进行挂载:演示了在 DockerFile 中添加挂载信息来实现构建一个启动容器之后能够自动挂载数据卷的镜像

    Section 7. 数据卷容器:演示了如何在多个容器之间共享数据卷

1. Motivation

    考虑我们创建一个 MySQL 容器,我们将这个数据库容器的数据全部存储在容器内部,也就是 R/W Thin Layer 中。这样一来,当我们删除我们的容器的时候,我们的这部分数据也会随之丢失,因此是十分不安全和不方便的。

    因此,就有了容器数据卷。其可用于将容器内部文件系统的路径同宿主机的文件系统路径关联起来,这样一来,当我们在容器内部对关联的文件目录进行操作的时候,就会同步到宿主机的文件系统中,使得数据得以在宿主机上实现持久化。另外,将数据存储在宿主机上还可以实现容器之间的数据共享

2. 通过命令构建数据卷

    在启动容器的时候,使用命令行进行挂载:

1
2
3
4
5
6
7
8
9
10
11
$ docker run -it -v /home/container_home:/home centos /bin/bash

$ cd /home/

$ touch test.c

$ exit
exit

$ ls /home/container_home/
test.c

    我们可以使用 "docker inspect [容器ID]" 来查看数据卷挂载信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ docker inspect 4fb86cd3156b
省略...
"Mounts": [
{
"Type": "bind",
"Source": "/home/container_home",
"Destination": "/home",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
],
省略...

3. 基于数据卷挂载部署 MySQL

    我们使用下面的命令将 mysql 的配置文件和存储数据的目录暴露到宿主机中,然后启动 mysql 容器。

1
2
$ docker run -d -p 3310:3306 -v /home/mysql/conf:/etc/mysql/conf.d -v /home/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=Xusir666! --name mysql_daxecture mysql:latest
b78e05ec4fa2a55da843b4f4f801314991b9db5b900a4f90d8a088613de06da2

    我们尝试远程连接,测试成功:

    然后我们在远程 Client 尝试创建一个数据库:

    我们在宿主机和容器中都可以看见目录下新创建的的文件 (e.g. DaXecture),说明文件路径绑定成功:

1
2
3
4
5
6
7
[root@ks data]$ ls
auto.cnf client-cert.pem ibdata1 mysql.ibd sys
binlog.000001 client-key.pem ib_logfile0 performance_schema undo_001
binlog.000002 DaXecture ib_logfile1 private_key.pem undo_002
binlog.index '#ib_16384_0.dblwr' ibtmp1 public_key.pem
ca-key.pem '#ib_16384_1.dblwr' '#innodb_temp' server-cert.pem
ca.pem ib_buffer_pool mysql server-key.pem
1
2
3
4
5
6
7
8
[root@ks data]$ docker exec -it b78e05ec4fa2 /bin/bash
root@b78e05ec4fa2:/$ ls /var/lib/mysql
'#ib_16384_0.dblwr' binlog.000002 ib_buffer_pool mysql.ibd sys
'#ib_16384_1.dblwr' binlog.index ib_logfile0 performance_schema undo_001
'#innodb_temp' ca-key.pem ib_logfile1 private_key.pem undo_002
DaXecture ca.pem ibdata1 public_key.pem
auto.cnf client-cert.pem ibtmp1 server-cert.pem
binlog.000001 client-key.pem mysql server-key.pem

4. 具名挂载和匿名挂载

    如果我们在挂载的时候,如果我们仅仅指明了要把容器内的哪个目录映射出来,而没有显式地指明我们要把容器目录映射到哪个主机目录,这就叫做匿名挂载。当我们进行完匿名挂载后,我们挂载卷会出现在 /var/lib/docker/volumes/ 下的某一个子文件夹中,子文件夹的名称应该是某种哈希值,我们可以使用如下命令来观察:

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
$ docker run -d -P --name nginx02 -v /etc/nginx nginx
80d3e2ef53068c9cca8e2b75ac0a4ef4c8bd6965484bcefebb4069cf37d59aa6

$ docker volume ls
DRIVER VOLUME NAME
local 0c9bea67e5f00285253b8ac775e8c77352b9a34bfaf4a9778fa1491469bf2659
local 18e02f2cf8e82c36561e906713876690c4895fbc226ccc3a34313ce8185c8d17
local 25b3b8a79356900b286f3b8695c8c0f6a7ce3cefc73d54f140812e89e94c8222
local 266f4201a1d9707edf69494052dee1a761b04dc728863e3bc3a8b71222e126a9
local bc57b375d60ae643b0c92ea3860b9d2b0d05e412308d71f90769cae9f6398e6a
local c88039e255dce081bd06f91e98ef6c1b00b167ffef688d1ab1366414057a7dad
local d58cabf702cbbcd1b8b1b0b63719c67e3237ea71913199d1643ef9e7bfdb11e0
local d96b0f4d34a6d509de2951863de80b3682d3991c44d0fde297a21c844a94c6a8
local dab0efd4b9e18bba022188e29c3839fb0540e759bb71217b5666614a05da2b3d
local db9ad59f15ebbea69f2efc761d9f90f308e98ec3edc7d343036ae569f85f2f14
local db3027433e14e1f128bea6348e2f8e61e6a2cfb87f8b76db5dd3a2e93727dabd
local portainer_data
local real_name_mount

# 通过前后比较可以发现多了下面这个数据卷,我们查看它的详细信息
$ docker volume inspect c88039e255dce081bd06f91e98ef6c1b00b167ffef688d1ab1366414057a7dad
[
{
"CreatedAt": "2021-07-11T06:27:52-04:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/c88039e255dce081bd06f91e98ef6c1b00b167ffef688d1ab1366414057a7dad/_data",
"Name": "c88039e255dce081bd06f91e98ef6c1b00b167ffef688d1ab1366414057a7dad",
"Options": null,
"Scope": "local"
}
]

$ ls /var/lib/docker/volumes/c88039e255dce081bd06f91e98ef6c1b00b167ffef688d1ab1366414057a7dad/_data
conf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params

    如果我们在匿名挂载的时候给这个挂载卷起了一个名字,那么这个挂载卷出现在 /var/lib/docker/volumes/ 下的文件夹名称就会是我们起的这个名字,这就是具名挂载。如下所示:

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
$ docker run -d -P --name nginx01 -v real_name_mount:/etc/nginx nginx
27ed7a232bd507fd9434554b13d32509de0aa96e750dc470d6cbff354a4722a3

$ docker volume ls
DRIVER VOLUME NAME
local 0c9bea67e5f00285253b8ac775e8c77352b9a34bfaf4a9778fa1491469bf2659
local 18e02f2cf8e82c36561e906713876690c4895fbc226ccc3a34313ce8185c8d17
local 25b3b8a79356900b286f3b8695c8c0f6a7ce3cefc73d54f140812e89e94c8222
local 266f4201a1d9707edf69494052dee1a761b04dc728863e3bc3a8b71222e126a9
local bc57b375d60ae643b0c92ea3860b9d2b0d05e412308d71f90769cae9f6398e6a
local d58cabf702cbbcd1b8b1b0b63719c67e3237ea71913199d1643ef9e7bfdb11e0
local d96b0f4d34a6d509de2951863de80b3682d3991c44d0fde297a21c844a94c6a8
local dab0efd4b9e18bba022188e29c3839fb0540e759bb71217b5666614a05da2b3d
local db9ad59f15ebbea69f2efc761d9f90f308e98ec3edc7d343036ae569f85f2f14
local db3027433e14e1f128bea6348e2f8e61e6a2cfb87f8b76db5dd3a2e93727dabd
local portainer_data
local real_name_mount

$ docker volume inspect real_name_mount
[
{
"CreatedAt": "2021-07-11T06:24:03-04:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/real_name_mount/_data",
"Name": "real_name_mount",
"Options": null,
"Scope": "local"
}
]

$ ls /var/lib/docker/volumes/real_name_mount/_data
conf.d fastcgi_params mime.types modules nginx.conf scgi_params uwsgi_params

5. 增设权限

    我们可以为映射到宿主机的路径增加权限:只读 (ro):

1
docker run -d -P --name nginx01 -v real_name_mount:/etc/nginx:ro nginx

    如果增设了只读权限,则这个目录中的内容就只能从宿主机进行改变,容器内部无法改变。

6. 通过 DockerFile 进行挂载

    在本节中我们将尝试在 DockerFile 中来挂载数据卷。DockerFile 的相关内容请查阅 DockerFile 一文。

    首先我们在一个空目录下创建一个文件:

1
2
3
[root@ks docker-test-volume]$ pwd
/home/docker-test-volume
[root@ks docker-test-volume]$ vim dockerfile1

    写入如下内容:

1
2
3
4
5
6
FROM centos

VOLUME ["/volume01", "/volumn02"]

CMD echo "-----end-----"
CMD /bin/bash

    然后我们通过 "docker build" 来根据我们的 DockerFile 来生成我们的镜像,其中 -f 参数用于指定我们的 dockerfile 的路径,-t 参数用于指明我们最终生成的镜像名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@ks docker-test-volume]$ docker build -f ./dockerfile1 -t zobinhuang/centos .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM centos
---> 300e315adb2f
Step 2/4 : VOLUME ["/volume01", "/volumn02"]
---> Running in d05b7059314c
Removing intermediate container d05b7059314c
---> 8f0328052b6b
Step 3/4 : CMD echo "-----end-----"
---> Running in d6374b482311
Removing intermediate container d6374b482311
---> 7c73254f2739
Step 4/4 : CMD /bin/bash
---> Running in 8a073f99c741
Removing intermediate container 8a073f99c741
---> 5f6f63cecca4
Successfully built 5f6f63cecca4
Successfully tagged zobinhuang/centos:latest

    我们查看一下目前我们本地的镜像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@ks docker-test-volume]$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
zobinhuang/centos latest f48cc5310d5b About a minute ago 209MB
tomcat_fixed 1.0 fedc472232ce 5 hours ago 672MB
nginx latest 4cdc5dd7eaad 4 days ago 133MB
tomcat latest 36ef696ea43d 8 days ago 667MB
portainer/portainer-ce latest 8bd64518b976 2 weeks ago 210MB
mysql 5.7 09361feeb475 2 weeks ago 447MB
mysql latest 5c62e459e087 2 weeks ago 556MB
gitlab/gitlab-ce latest 9214d01ae986 4 weeks ago 2.25GB
hello-world latest d1165f221234 4 months ago 13.3kB
centos latest 300e315adb2f 7 months ago 209MB
elasticsearch 7.6.2 f29a1ee41030 15 months ago 791MB
jpillora/dnsmasq latest 2ccfbd72b378 2 years ago 19.7MB

    现在我们启动一下这个镜像的容器:

1
2
3
4
$ docker run -it f48cc5310d5b /bin/bash
$ ls
bin etc lib lost+found mnt proc run srv tmp var volumn02
dev home lib64 media opt root sbin sys usr volume01

    我们看到在容器内部,在根目录下生成了两个文件夹 "volume01" 和 "volume02",这两个目录是生成镜像的时候自动挂载的,并且与宿主机上的某两个文件夹是同步的。

    我们可以在宿主机上使用 "docker inspect" 命令来查看这两个匿名挂载的卷是放在了宿主机上的哪个目录下:

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
$ docker inspect 3ec00625e39e
省略...
"Mounts": [
{
"Type": "volume",
"Name": "ace2f6a47af55c1584e6307808e39efc9ea14a8d0982c851546e16d5d83b5a92",
"Source": "/var/lib/docker/volumes/ace2f6a47af55c1584e6307808e39efc9ea14a8d0982c851546e16d5d83b5a92/_data",
"Destination": "/volume01",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
},
{
"Type": "volume",
"Name": "0f7bf338db80ecbe5fe4c055bd692dbcaf94161b347a9c2fbb58662e37efc287",
"Source": "/var/lib/docker/volumes/0f7bf338db80ecbe5fe4c055bd692dbcaf94161b347a9c2fbb58662e37efc287/_data",
"Destination": "/volumn02",
"Driver": "local",
"Mode": "",
"RW": true,
"Propagation": ""
}
],
省略...

7. 数据卷容器

    上文我们有提到,多个容器之间是可以共享数据卷的,这一小节我们来学习一下如何实现。

    我们首先起三个我们在上一小节中创建的 zobinhuang/centos 镜像的容器,其中第二个容器 centos02 和第三个容器 centos03 都是继承自第一个容器 centos01,我们是使用参数 "--volumes-from [父容器名]" 来实现这一点的。

1
docker run -it --name centos01 f48cc5310d5b /bin/bash
1
docker run -it --name centos02 --volumes-from centos01 f48cc5310d5b
1
docker run -it --name centos03 --volumes-from centos01 f48cc5310d5b

    这样一来,我们在 centos02 容器和 centos03 容器中的根目录下看到的 /volume01 和 /volume02 文件夹就和 centos01 是同步的:

1
2
# centos 01
$ touch ./volume01/test.go
1
2
3
# centos 02
$ ls ./volume01/
test.go
1
2
3
# centos 03
$ ls ./volume01/
test.go

    此时,假如说我们把被继承的父容器 centos01 给删除了,给删除了,这三个容器间原本共享的数据卷是不会丢失的,centos02 和 centos03 对原本数据卷的映射关系是不会丢失的,centos02 和 centos 03 之间仍然可以访问那两个文件夹。