专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Docker入门篇(四):Docker 数据卷

我们在上一章节成功的从官网中下载并正确启动了一个Tomcat容器。假设现在Tomcat容器中正在运行这一些服务,那么产生的数据该何去何从呢?在这一张,我们将探讨如何将Docker容器内产生的数据持久化。

cue:计算机的内存也不会存放数据。当计算机关闭电源时,内存中的内容将消失。因此如果要将某个计算结果长久地保存下来,就应当将它写入到文件中,并将这个文件保存到硬盘上。

我们之前掌握了如何将容器的文件拷贝到宿主机*上:

$ docker cp ${containerID}:${sourcePath} ${localPath}

这里的宿主机指带Docker所依赖的虚拟机环境(后文同。实际操作的机器笔者使用物理机来称呼),因为笔者是在虚拟机中运行的CentOS环境。在实际环境中,这个宿主机可能还指代你租赁或者搭建的云服务器。

的确!使用crond定时脚本或许可以解决这个问题,但它一定不是最佳方法。

Docker的理念

尽管Docker强调每个容器之间的运行环境是封闭且独立的,但是Docker希望容器之间的数据是可以被共享的,或者说,容器内的数据不会丢失。

一般来说,Docker容器产生的数据,如果不通过Docker commit生成新的镜像,让数据伴随着镜像保存下来,假使我们删除了这个容器,那么数据就一同消失了。而避免这个现象的办法只有一个,就是不断地通过commit把数据也封装到新的images上,或者写一个crond脚本不断地从容器内部拷贝数据。

因此我们使用数据卷来解决这个问题。数据卷允许宿主机和虚拟容器共享某个文件夹。虚拟容器所产生的数据放到这个共享文件夹当中,当数据需要迁移时,我们只需要直接将这个共享文件夹拷贝到远端即可。

类比Redis的数据持久化

Redis是一个运行在内存的NoSQL数据库,我们通常拿它用作消息队列或者消息缓冲用。那为什么Redis既然运行在内存中,它怎么做到每次电脑重新启动并加载时都能找到之前的数据呢?

它会将这些信息写入到rdb和aof文件当中。(在这里不用关心它们是做什么的,总之Redis是通过文件保存的数据)Docker容器卷的作用与它们类似,其作用主要有二:

1、 容器的持久化
2、 容器间继承+共享数据

Docker容器卷的特点

1、 数据卷可以在容器间共享。
2、 卷中的更改直接作用到数据卷上。
3、 数据卷中的更改不会包含到镜像的更新中。
4、 数据卷的声明周期持续到没有容器使用它为止。

添加数据卷的三种方式

v命令添加(可读写权限)

在运行一个镜像的之后,通过-v(Volume)参数添加数据卷,这个数据实质上是保存在宿主机当中。

$ docker run -it -v ${localPath}:${volumePath} ${imageID}

如果宿主机中没有${localPath}文件夹,则Docker会自动根据该路径创建一个文件夹,${volumePath}同理。

我们再次根据centos的镜像创建一个新的容器,并且希望添加一个容器卷,使得容器内的/dataVolume和宿主机根目录下的/dktemp文件夹相连通:

$ docker run -it -v /dktemp:/dataVolume --centos7inst centos

我们使用inspect命令,以json形式输出该容器的内部信息,可以发现有如下内容:

"Binds": ["/dktemp:/dataVolume"]

从这段描述当中我们就知道,这两个容器已经被绑定在一起了。我们另起一个终端,在主机的/dktemp下新建任意一个文件:

$ cd /dktemp
$ touch hello.txt

然后进入到容器内的对应文件夹下使用查看命令,就可以发现hello.txt的存在了:

$ cd /dataVolume
$ ll

另外,即便是容器被停止的期间,如果宿主机向/dktemp存放了新的文件,在这个容器重新启动的时候能够查看到它。

v命令添加(只读)

有时我们数据卷内的数据会被保护起来,只希望宿主机可以对此自由读写,而容器内只有读权限

$ docker run -it -v ${localPath}:${volumePath}:ro ${imageID}

我们需要在容器路径的后面添加:ro表示容器内是read-only的。注意,这不是创建了一个../ro文件夹。当以这种形式启动容器时,容器只能通过读方式获取数据卷内的信息,但无法做出更改。

我们使用inspect命令检查容器配置,可以看到有如下一项:

"Mounts": [
            {
                "Type": "bind",
                "Source": "/dktemp",
                "Destination": "/dataVolume",
                "Mode": "ro",
                "RW": false,
                "Propagation": "rprivate"
            }
]

注意到"RW"一项已经被标注了false

DockerFile添加

DockerFile是Docker中一个重要的组成部分。我们在时候去专门介绍DockerFile的具体内容。

在此之间简单了解DockerFile。我们都知道所有的容器都依赖image创建生成。那谁来负责对image文件本身进行描述的呢?正是DockerFile。

在这一小节,我们通过编写DockerFile来实现为一个镜像绑定一个或多个数据卷。它和v命令添加的方式有什么区别呢?

我们刚才说过,通过v命令想要为添加数据卷,要同时绑定${localPath}还有{imagePath}但是,我们一般不这么做。原因是出于可移植和可分享的考虑,并不保证所有宿主机中的${localPath}路径都是一致的。

我们自定义一个工作目录,并touch一个DockerFile文件。

#use local image 'centos'
FROM centos

#set volume
VOLUME ["/dataVolumeContainer1","/dataVolumeContainer2"]
CMD echo "finished"
CMD /bin/bash

我们在VOLUME行当中,为centos镜像绑定了2个新的数据卷(这是指定了容器内部的路径。那对应宿主机的目录哪去了?我们等会再去讨论)。保存退出。随后通过build命令让Docker根据这个DockerFile文件生成新的image镜像:

$ docker build -f ./dockerFile -t ljh/centos .

其中./dockerFile为笔者DockerFile的文件名,ljh/centos为笔者自定义的镜像名称,注意这个命令后面有个.

当运行这个命令时,屏幕打印出了如下信息(imageID会有区别):

Step 1/4 : FROM centos
 ---> 470671670cac
Step 2/4 : VOLUME ["/dataVolumeContainer1","/dataVolumeContainer2"]
 ---> Running in 48817b55708b
Removing intermediate container 48817b55708b
 ---> 975e445781db
Step 3/4 : CMD echo "finished"
 ---> Running in ee08adab0f3d
Removing intermediate container ee08adab0f3d
 ---> 133c078c0b71
Step 4/4 : CMD /bin/bash
 ---> Running in 18183f9d3ea3
Removing intermediate container 18183f9d3ea3
 ---> ff471674859b

由此我们也可以推断Docker构建image文件的方式:基于联合文件系统UnionFS一层一层铺开。

我们使用docker images命令,就可以看到基于centos镜像生成的ljh/centos新镜像。我们不妨直接基于这个镜像创建一个新的容器,然后观察内部是否有刚才的DockerFile所配置的两个文件夹:

#我们刚才通过DockerFile配置过数据卷,再这里不需要再使用-v参数了。
$ docker run -it ljh/centos

但是,我们知道了容器内的容器卷对应目录,那宿主机下对应的目录在哪里呢?实际上,Docker会自动帮我们生成一个默认的文件夹,并避免了和宿主机原来的文件夹重名的情况。

具体可以使用inspect来检查这个被创建出来的容器:

        "Mounts": [
            {
                "Type": "volume",
                "Name": "c87509fb79c5ed0c3c484f5b146f01dfcb96228063190c9ab58aca7efd396db5",
                "Source": "/var/lib/docker/volumes/c87509fb79c5ed0c3c484f5b146f01dfcb96228063190c9ab58aca7efd396db5/_data",
                "Destination": "/dataVolumeContainer1",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            },
            {
                "Type": "volume",
                "Name": "1a5c4cc8e94167f5279b6c5b48e2d3073b417e9499fc5ff1dba470eeccb8b929",
                "Source": "/var/lib/docker/volumes/1a5c4cc8e94167f5279b6c5b48e2d3073b417e9499fc5ff1dba470eeccb8b929/_data",
                "Destination": "/dataVolumeContainer2",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ]

可以看到,Source文件夹的命名相当之长。

Docker数据卷容器

注意,数据卷和数据卷容器是两个不同的概念。数据卷容器表示:这个容器正挂在数据卷,来为其它的子容器提供数据共享渠道

我们还是使用ljh/centos镜像来创建一个新的容器,并命名为doc1,我们将它称之为数据卷容器。

$ dokcer run -it --name doc1 ljh/centos

我们再创建容器doc2,doc3,并希望它使用doc1容器内的数据卷,通过--volumes-from来实现这种容器和容器间的数据共享。

$ docker run -it --name doc2 --volumes-from doc1 ljh/centos
$ docker run -it --name doc3 --volumes-from doc1 ljh/centos

我们实际上是建立了这样的一个关系:

104_1.png容器 doc2, doc3正在共享 doc1内的数据,三者的数据是互通的。我们可以在 doc2dataVolumeContainer1当中创建一个新文件,然后去 doc3容器的相同目录下检查是否存在该文件。

假如说将doc1容器删除掉,会不会造成很严重的后果?这样的数据共享关系还会存在吗?

$ docker rm -f doc1

我们将doc1删除掉,随后再attachdoc2的对应目录底下,发现,文件仍然是被保留的。并且,doc2下创建新文件,我们仍然也可以从doc3文件中再获取到它,这说明doc2doc3之间的数据共享关系仍然没有被破坏。

104_2.png那这两个数据卷的生命周期到底是什么样的呢? 当没有任何一个子容器再挂载该数据卷时,这个剩余的数据卷才会被彻底删除。换句话说,当 doc2doc3也被一并删除的时候,原来 doc1中的数据卷才会被真正删除。

常用数据库安装:安装MySQL

这一节我们将学习如何在Docker中安装MySQL数据库。

$ docker search mysql
$ docker pull mysql

启动MySQL数据库,设置数据卷以及初始化的ROOT密码。

$ docker run -p 10000:3306 \
--name mysql -v /root/mysqlTmp/conf:/etc/mysql/conf.d \
-v /root/mysqlTmp/logs:/logs \
-v /root/mysqlTmp/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 -d mysql

(可选)更改数据库加密规则

注意,MySQL 8版本之前的加密规则是mysql_native_password。而MySQL之后加密规则是caching_sha2_password。如果Navicat无法连接此数据库(错误代码:1251),则主要通过两种渠道解决,这里我们选择第二种方式:

1、 升级Navicat驱动。
2、 更改MySQL登录的加密规则。

进入到容器内,然后打开mysql命令行:

$ docker exec -it ${containerID | containerName} /bin/bash
$ mysql -uroot -p

其中,容器ID为mysql容器的ID号。通过密码登录mysql终端,密码为初始设定的MYSQL_ROOT_PASSWORD

注意,每行mysql命令以;符号结尾。

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password'

提示:root可以替换成你指定的用户名称。

localhost表示只允许本机访问,这样除了本机测试以外,外部是无法连接的。因此替换成'*.*.*.*'的IP。通常情况下仅开放有限的IP地址(出于安全状况考虑)。如果允许任何IP连接进来,则设置为'%'

password为你指定设置的密码。

在设置完毕后,刷新配置。

FLUSH PRIVILEGES;

这样,在物理机端,我们打开Navicat,就可以便捷地操作来自于容器的mysql表了。注意端口号的设置,是在run命令时所指定的Docker容器开放的端口,未必是3306(取决于你的设置)。

如果您是在阿里云,腾讯云等平台进行的操作,要记得设置入站规则,允许外部访问Docker对应的端口。

(建议,如果您是Java程序员)设置时区

对于MySQL 8.0+的版本,我们在使用基于JDBC的数据库驱动来编写持久层组件时,可能都遇到过由于MySQL的默认时区与系统时区不同导致无法连接的问题。

依赖JDBC驱动连接MySQL的工具可能也会存在这个问题,比如DataGrip

第一种方式是,在每次连接时MySQL都带上中国时区。

private static final String url = 
"jdbc:mysql://127.0.0.1/dataBase?serverTimezone=Asia/Shanghai"

第二种方式是,直接进入MySQL数据库内更改配置。

$ docker exec -it ${containerID | containerName} /bin/bash
$ mysql -uroot -p
#设置东8区。
mysql> set global time_zone = '+08:00';
#检查是否更改成功。
#默认情况下time_zone的值是SYSTEM。
mysql> show variable like "%time_zone%";

参见:Spring连接Mysql

文章永久链接:https://tech.souyunku.com/41978

未经允许不得转载:搜云库技术团队 » Docker入门篇(四):Docker 数据卷

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们