最近工作涉及到一些Docker的学习,因此将Docker相关知识简单总结一下。
1. 目标
通过docker生成一个包含MySQL的镜像,此镜像和宿主机有端口映射,可以通过宿主机上的Java代码来访问docker上MySQL的数据,并且使用数据卷(volume)持久化docker上MySQL的数据,保证在删除容器后,数据不丢失。
拆解一下任务:
- 生成包含MySQL的镜像
- 根据生成的镜像来创建容器实例(container)
- Java访问docker上MySQL的数据
接下来挨个实现
2. 生成包含MySQL的镜像
这步比想象中的要复杂一些,踩了一些坑,也因为对docker的不熟悉,走了一些弯路,在此做下记录。
Dockerfile中用到的几个文件,结构和文件内容如下:
1 | - scripts/ |
2.1 Dockerfile
1 | # 从mysql基础镜像进入 |
此处先不对各个关键字的功能做详细记录,后续在docker学习中总结。
此处需要注意的几个点:
- Dockerfile无法实现端口映射,只能通过EXPOSE关键字暴露端口,端口映射需要使用
docker run -p 3307:3306
类似的命令或者docker-compose.yml
来实现。 - Dockerfile无法指定本地数据卷和容器内目录映射,只能通过
VOLUME
指定匿名数据卷,指定后docker会在安装目录下的特定目录里生成一个目录来映射容器数据卷。如果需要指定数据卷映射,需要使用docker run -v /User/LocalMachine/data:/var/MySQL/data
或者docker-compose.yml
来实现。
2.2 Dockerfile用到的脚本
init.sql
— 建表语句,放到容器/docker-entrypoint-initdb.d/
文件夹下,MySQL会自动执行。
1 | -- 创建database |
2.3 生成镜像
完成Dockerfile的debug后,我们在本地使用docker build
语句生成一个镜像:
1 | # -t 是为镜像设定tag和名称,可以设置多个 |
创建成功后通过docker images
命令查看现有镜像:
3. 根据生成的镜像来创建容器实例
此处需要考虑2个点:
- 镜像和宿主机的端口映射
- 使用数据卷来对需要持久化的数据进行本地映射
使用docker run
命令指定端口映射、数据卷本地映射后生成容器实例:
1 | # 本地3307端口映射容器的3306,因为本地的3306被本地装的MySQL占用了 |
先通过docker ps查看容器是否启动成功:
可以看到已经成功启动,容器name为jdbc-1。
使用docker port
命令查看容器和宿主机的端口映射,可以看到3306端口映射到了本地的3307端口。
最后通过docker exec
命令进入容器 CLI界面:
出现bash标志表示此处已经成功进入docker 容器的cli。
通过mysql -uroot -p
进入docker内部的MySQL,输入密码后成功进入。
输入show databases;
查看当前的库,可以看到myjdbcdemo在其中:
输入命令use myjdbcdemo;
使用此库,可以使用select语句查看表中数据。
3.1 MySQL root密码问题
docker给了3个环境变量来控制MySQL的密码问题,分别是MYSQL_RANDOM_ROOT_PASSWORD
, MYSQL_ROOT_PASSWORD
和MYSQL_ALLOW_EMPTY_PASSWORD
,三个优先级有先后,挨个介绍下:
1 | # 如果MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD没有配置则默认为true。如果为true, |
MYSQL_RANDOM_ROOT_PASSWORD
。如果MYSQL_ROOT_PASSWORD
,MYSQL_ALLOW_EMPTY_PASSWORD
没有配置则默认为true。如果为true,mysql会自动生成一个密码,会打印在控制台,格式是:[GENERATED ROOT PASSWORD: .....]
。MYSQL_ROOT_PASSWORD
。设置MySQL root的密码。这个没什么可说的,就是正常的密码设置,例如ENV MYSQL_ROOT_PASSWORD=123456
。MYSQL_ALLOW_EMPTY_PASSWORD
。为true的话,root用户的密码就是空。此处使用debug时可以进入docker容器,通过以下命令查看MySQL用户信息。
1
2# 在docker容器内进行MySQL
bash> mysql -uroot -p123456;1
2
3
4
5
6
7
8
9
10
11
12
13
14# 进入MySQL内部后,选择mysql库
mysql> use mysql;
# 查看用户信息
mysql> select user, host, authentication_string from user;
+------------------+-----------+------------------------------------------------------------------------+
| user | host | authentication_string |
+------------------+-----------+------------------------------------------------------------------------+
| root | % | $A$005$|[lde}}}^RXsbBUMKjoSEwZkvPMuCFiecEQqa8UIN9o6b1dKRtcyTQv0C |
| mysql.infoschema | localhost | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| mysql.session | localhost | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| mysql.sys | localhost | $A$005$THISISACOMBINATIONOFINVALIDSALTANDPASSWORDTHATMUSTNEVERBRBEUSED |
| root | localhost | $A$005$MEKL81XEj[C.>rB2luQaqISkkixQjSffptkkhsWs2xm3y7D/qYgPMg970/ |
+------------------+-----------+------------------------------------------------------------------------+
5 rows in set (0.00 sec)
3.2 远程登录失败问题
使用的时候一直显示会连接失败:
查了很多结果,有说和防火墙有关的,有的需要修改my.cnf
文件中bind-address=0.0.0.0
的。但是最后发现。。。是因为容器没有完全启动起来,所以连接失败了,等待其启动结束后,再连接就好了。搞了一个大乌龙 T_T。。。
不过查问题的过程中也看到了不少知识,还是打算记录下来。
修改my.cnf
文件中bind-address=0.0.0.0
后,MySQL会监听所有网络端口的连接,具体参见这个回答。
Note: if you use
bind-address = 0.0.0.0
your MySQL server will listen for connections on all network interfaces. That means your MySQL server could be reached from the Internet ; make sure to setup firewall rules accordingly.
如果不做bind-address
的配置,MySQL的bind-address
默认是*
,*
也表示所有IP都可以连接,这里可以参见官方文档。
1 | # 查看docker ip |
4. Java访问docker上MySQL的数据
此处除了端口改为3307,其它和访问本机MySQL实例流程完全一致,不再赘述。
5. 开发过程中遇到的问题
由于Dockerfile是在网上找的,debug过程中遇到了一系列问题。
当时思路是:
- 自己写脚本来启动和初始化MySQL(尽管后面发现这是不必要的)
- 初始密码由脚本来设置,而非MySQL 镜像提供的
MYSQL_ROOT_PASSWORD
字段设置(这是谬误2,如果有官方提供的入口,不使用而打算自己处理,那么就要做好面对各种异常的准备。)
5.1 使用shell脚本插入数据问题
先来列举一下文件内容,[D]
表示最新代码中已经被删掉。
1 | - scripts/ |
当时Dockerfile是打算使用setup.js
进行MySQL初始化的:
1 | # 把setup.js放到docker-entrypoint-initdb.d文件夹下,这样容器第一次启动时,setup.js就会执行。 |
原始的setup.sh
— 启动脚本,执行一些建表、给root用户新增密码等操作。
1 |
|
如果没有设置MySQL的root密码,在容器的bash命令行里是直接可以输入mysql < some_sql.sql
来执行sql文件的。
但是在shell脚本中使用此命令插入sql文件时,遇到了问题。命令如下:
1 | # setup.js文件中的语句 |
可以看到,报错中user为'mysql'@'localhost'
,但我们使用的明显不是这个user,而是'root'@'localhost'
。尝试指定了一下user,问题解决:
1 | # setup.js文件中的语句 |
这里为什么用户是mysql呢??https://stackoverflow.com/a/11216911/8706905 这个可以作为参考,但明显不是答案。
后面在官方文档里找到,如果不通过-u
或者--user
选项来覆盖MySQL用户名,类Unix系统(如MacOS或者Linux)中会使用当前OS的用户名来作为MySQL的用户名,这在用户名没指定的情况下只是为了方便。
On Unix, most MySQL clients by default try to log in using the current Unix user name as the MySQL user name, but that is for convenience only.
我们在报错的bash语句前加一句打印当前用户的命令:
1 | # 打印当前用户 |
可以看到,在docker镜像启动之时,使用的用户是mysql,推测是MySQL初始化时创建了专门的OS用户来操作初始化,所以才会报上面提到的错误。
5.2 容器启动失败
还是和被删掉的setup.sh
文件有关。
MySQL镜像如果拉取下来后,如果Dockerfile最后一句为
1 | CMD ["sh", "setup.sh"] |
会报ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)
的错误。
查了许多地方,这个回答中提到:数据库还未初始化。
解决方式是最后一句由CMD ["sh", "setup.sh"]
改为执行CMD ["mysqld"]
。同时通过Docker官方MySQL镜像文档,把(文件已经删除,所以也没有这一说,但是学到如何使用这个文件夹 ^_^)。文档中是这么说的:setup.js
文件copy到/docker-entrypoint-initdb.d/
文件夹下
容器在第一次启动时,会执行在
/docker-entrypoint-initdb.d/
文件夹下的.sh
,.sql
和.sql.gz
文件。
另外在MySQL官网mysqld的页面有明确的解释,其实mysqld就是MySQL server,需要启动它对MySQL服务端进行初始化。
mysqld, also known as MySQL Server, is a single multithreaded program that does most of the work in a MySQL installation.
另一个佐证是MySQL官方docker镜像的image layers描述中,最后一句也是 CMD ["mysqld"]
。
在使用这个解决方法后,之前报错找不到的/var/run/mysqld/mysqld.sock
文件出现了。
修改后:
5.3 使用SQL脚本修改MySQL的密码
privilege.sh
— 给root用户添加密码的SQL脚本,这里有些需要注意的点。
1 | USE mysql; |
后面发现其实只需要设置MYSQL_ROOT_PASSWORD
不为空即可,这段就删掉了,放在这里权做记录。
6. 结论
尽量还是寻找比较正式的文档进行二次开发,遇到的坑比较少,例如这个文档.,虽然不是Dockerfile生成MySQL镜像的,但整体相当顺利。。
官方成本可以降低学习成本,不过如果时间空闲较多的话,倒也可以深入探究一下,在查问题的过程中,的确收获了不少新的知识。
源码地址:https://github.com/StephenHuge/blog-code/tree/master/MyJdbcDemo/scripts