背景
在一次 Spring Boot 应用对接 Nacos 配置中心和 MySQL 数据库的过程中,遇到了一个典型的 Docker 网络问题:应用容器和 Nacos、MySQL 容器都运行在同一台宿主机的同一个自定义 Docker 网络中,但应用始终无法通过宿主机的物理 IP(10.2.4.4)连接 Nacos 和 MySQL。宿主机本身通过 curl 却能正常访问 Nacos 容器的映射端口(返回 404,证明网络通)。
现象总结
-
宿主机
curl 10.2.4.4:8848能连接 Nacos(返回 404,说明服务在) -
宿主机
curl 127.0.0.1:8848也能连接 -
Docker 容器
ontostar通过10.2.4.4:8848无法连接 Nacos -
Docker 容器
ontostar通过10.2.4.4:3306无法连接 MySQL -
三个容器(
ontostar、nacos、mysql)都在同一个自定义网络docker-services_default中
根本原因分析
1. 容器访问宿主机物理 IP 的特殊性
当容器试图通过宿主机的物理 IP(如 10.2.4.4)访问另一个容器时,流量路径为:
text
容器A → 宿主机eth0(物理网卡) → 宿主机iptables DNAT规则 → 目标容器
这条路径依赖 Linux 内核的 NAT 回流(Hairpin NAT) 功能,而 Docker 默认的桥接网络实现对此支持不完整,导致从容器发出的包经过宿主机后,无法正确返回到源容器。
2. 自定义网络的本应行为
在同一个用户自定义的桥接网络中,容器之间理应可以通过 IP 地址直接通信,而不需要经过宿主机物理网卡。但是在实际环境中,可能因为以下因素导致 IP 互通失败:
-
宿主机 iptables FORWARD 链被阻断(例如默认策略为 DROP,或没有 Docker 插入的允许规则)
-
容器内服务监听地址错误(例如监听 127.0.0.1 而非 0.0.0.0)
-
Docker 守护进程设置了
--icc=false(本例中未设置) -
防火墙规则清除了 Docker 自动创建的规则
3. 为什么通过宿主机 IP 访问不行,但直接用容器 IP 访问可以?
理论上,在同一个自定义网络中使用容器 IP 或容器名访问是最直接、最可靠的方式,因为流量完全在 Docker 虚拟网桥内部转发,不经过宿主机物理网卡和 NAT 规则。本例中由于未知原因(可能是 iptables 规则残留),导致容器间通过 IP 访问也失败,但宿主机却能访问映射端口 -- 这强烈暗示 FORWARD 链或 Docker 网络隔离策略存在异常。
排查步骤及命令
以下是按照执行顺序整理的排查操作:
1. 确认容器处于同一网络
bash
docker inspect ontostar --format='{{.HostConfig.NetworkMode}}'
docker inspect nacos --format='{{.HostConfig.NetworkMode}}'
docker inspect mysql --format='{{.HostConfig.NetworkMode}}'
输出均为 docker-services_default,确认网络一致。
2. 验证宿主机端口映射的 DNAT 规则
bash
sudo iptables -t nat -L -n | grep 8848
结果存在:
text
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8848 to:172.19.0.3:8848
说明端口映射正确。
3. 在宿主机上测试连通性
bash
curl -v 10.2.4.4:8848 # 成功返回404,服务可达
curl -v 127.0.0.1:8848 # 也成功
4. 检查 Docker 守护进程的 icc 设置
bash
docker system info | grep -i icc
输出只有 cgroup v1 的警告,没有 --icc=false,说明容器间通信未被全局禁用。
5. 从应用容器内部测试网络(关键)
bash
# 进入应用容器
docker exec -it ontostar /bin/sh
# 尝试 ping Nacos 容器的 IP(假设为 172.19.0.3)
ping 172.19.0.3
如果 ping 不通,说明容器间的 IP 层路由或 iptables FORWARD 链有问题。
若 ping 通但端口不通,则需要检查 Nacos 容器内服务监听地址。
6. 检查容器内服务的监听地址
bash
# Nacos 容器
docker exec nacos netstat -tulnp | grep 8848
# MySQL 容器
docker exec mysql netstat -tulnp | grep 3306
确保监听地址为 0.0.0.0 而非 127.0.0.1。
7. 检查宿主机 iptables FORWARD 链
bash
sudo iptables -L FORWARD -n -v
如果看到 Chain FORWARD (policy DROP) 且没有任何 ACCEPT 规则,则所有跨容器流量都会被丢弃。Docker 通常会在 FORWARD 链中插入自己的规则允许容器间通信,但若规则被清除,就会出现问题。
8. 尝试重启 Docker 服务恢复默认规则
bash
sudo systemctl restart docker
docker restart nacos mysql ontostar
9. 验证网络本身可用性(创建临时网络测试)
bash
docker network create test-net
docker network connect test-net nacos
docker run -it --rm --network test-net alpine ping nacos
如果这个测试成功,说明 Docker 网络功能正常,问题可能局限在旧网络的 iptables 残留规则上。
最终解决方案
经过排查,根本原因是在同一个自定义网络中,容器之间本应 IP 互通,但由于某种原因(可能是之前手动修改过 iptables 或安全策略干扰)导致通过 IP 访问失败。而通过宿主机物理 IP 访问的方式又因为 Docker NAT 回流的限制不可靠。
因此,最佳解决方案是放弃使用宿主机 IP,转而使用 Docker 自定义网络内置的 DNS 解析功能,直接通过容器名(或服务名)进行通信。
具体修改步骤
-
修改应用配置
将原本配置中的 Nacos 地址从
10.2.4.4:8848改为 nacos:8848;将 MySQL 地址从
10.2.4.4:3306改为mysql:3306。 -
确保 Nacos 注册的地址为宿主机 IP(可选,如需外部访问)
在启动 Nacos 容器时添加环境变量:
bash
-e PREFER_HOST_MODE=hostname -e NACOS_SERVER_IP=10.2.4.4这样其他需要从宿主机外部访问的服务仍能通过
10.2.4.4调用 Nacos。 -
授权 MySQL 允许来自任意主机的连接
sql
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'your_password' WITH GRANT OPTION; FLUSH PRIVILEGES; -
修改 MySQL 监听地址为 0.0.0.0
在容器内的 MySQL 配置文件(如
/etc/mysql/my.cnf)中设置:ini
bind-address = 0.0.0.0然后重启 MySQL 容器。
-
重启所有服务
bash
docker-compose down # 如果使用 compose docker-compose up -d
验证
bash
# 从应用容器内测试容器名连通性
docker exec ontostar curl -v nacos:8848/nacos/
docker exec ontostar mysql -h mysql -u root -p -e "SELECT 1"
经验与总结
-
永远不要依赖宿主机物理 IP 来让同一台机器上的 Docker 容器之间通信。这种"迂回"方式既低效又容易踩坑(NAT 回流问题)。
-
对于自定义桥接网络,直接使用容器名/服务名访问是最佳实践。Docker 内置的 DNS 解析稳定可靠。
-
如果出现容器间 IP 不通的情况,优先检查 iptables FORWARD 链和 Docker 守护进程的
icc配置。重启 Docker 服务通常可以恢复默认规则。 -
对于生产环境,应避免将数据库(如 MySQL)的访问权限开放给
%,而应指定具体的 Docker 子网段或使用网络别名进行精细化控制。 -
故障排查时,分层测试:先测 IP 层(ping),再测传输层(nc/telnet),最后测应用层(curl)。这样能快速定位问题环节。