在容器化应用部署过程中,网络连通性问题是最常见也最令人头疼的故障之一。典型的场景是:宿主机网络一切正常,但容器内部却无法访问外网,或者外部无法连接容器提供的服务。而MySQL数据库如果部署在容器里出现无法访问时,该如何排查和解决呢?
本文根据真实案例的排坑过程来复现,包含容器方式部署MySQL数据库服务、异常复现及抓包等排查过程。
一、 MySQL容器部署
为了复现问题,先部署一个MySQL方式部署的数据库实例(各版本均可),不影响本次问题的复现。
- 拉取镜像
cs
[root@c7 ~]# docker pull swr.cn-south-1.myhuaweicloud.com/library/mysql:8.0

完成后如下:

- 重命名镜像
将镜像改回标准名字,方便后续部署
apache
docker tag swr.cn-south-1.myhuaweicloud.com/library/mysql:8.0 mysql:8.0

- 查看镜像

- 部署镜像
进行镜像部署及启动
apache
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v mysql-data:/var/lib/mysql \ mysql:8.0
完成后进入容器,并测试是否可以登录:
ruby
[root@c7 ~]# docker exec -it mysql8 bash root@01b28da68a54:/# mysql -uroot -p123456

以上可以看到已完成MySQL容器的部署。
二、 问题复现
- 正常情况
正常情况下,在同网段其他服务器上访问端口如下:
cs
[root@vbox ~]# telnet 192.168.56.106 3306 Trying 192.168.56.106...Connected to 192.168.56.106.Escape character is '^]'.J8.0.13 nN;*a}ÿ z.>hcaching_sha2_passwordConnection closed by foreign host.
可以看到端口能正常访问

- 复现操作
Linux中清理swap及调整vm.swappiness内核参数是比较常见的操作(并非实际原因,只是模拟异常),本次也进行此操作,即在 /etc/sysctl.conf文件中添加:
ini
vm.swappiness = 1
完成后执行sysctl -p生效。

- 异常现象
3.1 同网段机器访问
上述操作完毕后,发现其他节点无法访问容器里的数据库,在刚才同网段的机器探测端口如下:

此时发现端口已无法访问。
3.2 数据库的本机访问
在数据库本机上查看端口及探测端口现象如下:

可见,本机是可以正常访问的。
进入数据库也正常。

三、 问题排查
- 检查防火墙及安全策略等
通常本机能访问,其他机器不能访问时,最先想到的便是防火墙是否正常(因为上面已经确认数据库端口及服务在本机能正常访问),于是检查如下:
perl
[root@c7 ~]# systemctl status firewalld● firewalld.service - firewalld - dynamic firewall daemon Loaded: loaded (/usr/lib/systemd/system/firewalld.service; disabled; vendor preset: enabled) Active: inactive (dead) Docs: man:firewalld(1)[root@c7 ~]# getenforce Disabled

可见,以上情况均正常,不会影响访问。
注意: 如果是云服务器还需要检查安全组和白名单等配置。
- 抓包检查
最直观的判断请求是否进入数据库服务器主机可以用抓包的方式进行,例如本次用tcpdump方式抓包。
nginx
# 如果没有安装的先执行安装tcpdumpyum install -y tcpdump

在数据库机器上抓包:
apache
tcpdump -i any -nn -s 0 -A 'host 192.168.56.106 and port 3306'
再在其他机器上进行访问(登录数据库或者telnet探测均可)

此时发现数据库机器上的抓包有接受到对应的请求,只是没有回应。

从以上的情况来看,可以确定的是:数据库服务正常;外部请求到数据库主机能通(只是对应的数据库端口无反应)。
- 重启容器
以上的种种情况表明,数据库机器到容器端口的请求有异常,因此进行容器重启。 逐步重启如下:
3.1 重启MySQL容器
cs
[root@c7 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES01b28da68a54 mysql:8.0 "docker-entrypoint..." 3 hours ago Up 3 hours 0.0.0.0:3306->3306/tcp, 33060/tcp mysql8[root@c7 ~]# docker restart mysql8 mysql8[root@c7 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES01b28da68a54 mysql:8.0 "docker-entrypoint..." 3 hours ago Up 3 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp mysql8

重启完毕后,其他机器再次探测(涛声依旧)。

3.2 重启容器服务
cs
[root@c7 ~]# docker start mysql8 mysql8[root@c7 ~]# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES01b28da68a54 mysql:8.0 "docker-entrypoint..." 3 hours ago Up 3 seconds 0.0.0.0:3306->3306/tcp, 33060/tcp mysql8

此时再在其他机器再次探测,居然好了。

- 问题复现
由于只处理过内核参数调整,执行sysctl -p操作,因此,我们再次做此操作。

完成后,mysql容器正常,但是其他节点的访问确定又异常了:

此时已经可以确定就是此操作导致的。
- 确认问题
以上的sysctl -p操作中有个不被注意的参数:
ini
net.ipv4.ip_forward = 0
这个参数控制Linux内核是否允许转发网络数据包。为什么影响Docker呢? 经查,原因如下:
-
Docker的容器网络(默认是Bridge模式)依赖于宿主机的网络转发功能
-
当外部请求到达宿主机(例如访问3306端口)时,宿主机需要把这个数据包转发给容器(例如172.17.0.*的3306端口)
-
如果net.ipv4.ip_forward=0,宿主机就像一个"死胡同",收到数据包后发现自己不是最终目的地(因为IP是容器的),就会直接丢弃,导致外部无法连接
我们当前的容器的网络确实也就是默认的:
cs
[root@c7 ~]# docker inspect -f '{{.HostConfig.NetworkMode}}' mysql8default

- 恢复
我们再手动将net.ipv4.ip_forward设置为1,例如:
ini
[root@c7 ~]# sysctl -p net.ipv4.ip_forward = 1net.ipv4.tcp_keepalive_time = 120net.ipv4.tcp_keepalive_probes = 3net.ipv4.tcp_keepalive_intvl = 15net.ipv4.tcp_retries2 = 5vm.swappiness = 1

此时,其他节点也能正常访问了,说明确实是此参数引起的。

- 为何重启容器能恢复
我们之前在重启容器服务时也能恢复,是什么原因呢?经过查询,发现了容器重启后该参数也自动调整为1了。
ini
# 先手动调整net.ipv4.ip_forward为0 [root@c7 ~]# sysctl -w net.ipv4.ip_forward=0 net.ipv4.ip_forward = 0# 重启容器[root@c7 ~]# systemctl restart docker # 再次查看net.ipv4.ip_forward[root@c7 ~]# sysctl net.ipv4.ip_forwardnet.ipv4.ip_forward = 1

四、 总结
Docker网络问题虽然复杂,但大多有迹可循。通过分层排查方式进行,从底层连通性到应用层配置,逐步缩小范围。 另外,熟练掌握抓包的方式来分析,可以更准确、高效地解决问题。同时,也提醒大家用容器方式部署MySQL(或应用)需要掌握更多的知识,对于问题分析的难度也更大。
欢迎大家在留言区分享相关的数据库问题及排查过程,让更多的小伙伴避坑。