Docker 容器网络不通:Tomcat 无法解析 MySQL、Nginx 无法反代 Tomcat 的生产级排查与治理
一、问题背景
在单机 Docker 环境中,常见这样的部署组合:
mysql8-server:MySQL 8.0 容器,接在自定义网络my_nettomcat-old:老 Tomcat 容器,接在my_net,运行正常tomcat-new:新起的 Tomcat 容器,用docker run默认网络启动,启动后报Could not create connection to database servernginx-web:新起的 Nginx 容器,同样默认网络,反代 Tomcat 时 502
表面看是"数据库连不上""Nginx 配错了",实际根因只有一个:新容器落在了 Docker 默认 bridge 网络(172.17.0.0/16),而老容器和 MySQL 在自定义网络 my_net(172.18.0.0/16),默认 bridge 不支持按容器名做 DNS 解析。
本文把这个问题的定位过程、修复方案、持久化治理和预防规范完整写清楚,适用于生产环境的容器编排排查。
二、根因:Docker 默认 bridge 与自定义网络的 DNS 差异
2.1 两种网络的本质区别
| 特性 | 默认 bridge(docker0,172.17.x.x) |
自定义网络(docker network create,172.18.x.x 起) |
|---|---|---|
| 内置 DNS(127.0.0.11) | ❌ 不生效 | ✅ 生效 |
| 按容器名/别名解析 | ❌ 只能 IP 直连 | ✅ mysql8-server 可直接解析 |
| 网络隔离 | 所有默认 bridge 容器混在一起 | 按网络隔离 |
| 生产推荐 | 不推荐 | 推荐 |
2.2 为什么报错是 InetAddress.getAllByName
Java JDBC 连接串写的是 jdbc:mysql://mysql8-server:3306/...。驱动先调 InetAddress.getAllByName("mysql8-server") 做 DNS 解析。容器在默认 bridge 里,Docker DNS 不接管,宿主机的 /etc/resolv.conf 也没有这个名字,解析失败 → 抛 UnknownHostException → Druid 重试 3 次后放弃 → 报 Could not create connection to database server。
关键点:报错看起来像"数据库连不上",实际是"主机名解析不了",方向完全不同。
三、快速定位(5 分钟)
3.1 确认容器所在网络
bash
docker inspect mysql8-server --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect tomcat-old --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect tomcat-new --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect nginx-web --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
典型输出:
mysql8-server → my_net
tomcat-old → my_net
tomcat-new → bridge ← 问题容器
nginx-web → bridge ← 问题容器
3.2 在问题容器内验证 DNS
bash
docker exec -it tomcat-new getent hosts mysql8-server
docker exec -it nginx-web getent hosts tomcat-new
- 无输出 / 失败 → 确认 DNS 解析不了
- 输出
172.18.x.x mysql8-server→ 网络已通
3.3 确认 MySQL 的网络别名
bash
docker inspect mysql8-server --format '{{json .NetworkSettings.Networks.my_net.Aliases}}'
正常应包含 "mysql8-server"(--name 起的容器默认带名字作别名)。若没有,需用 IP 或 --network-alias 补。
3.4 看容器内 resolv.conf
bash
docker exec -it tomcat-new cat /etc/resolv.conf
- 有
nameserver 127.0.0.11→ 走了 Docker DNS - 只有宿主机 DNS → 说明没接入自定义网络,DNS 不生效
四、修复方案(按生产推荐度排序)
方案 A:把问题容器接入自定义网络(运行时热修复)
bash
docker network connect my_net tomcat-new
docker network connect my_net nginx-web
# 验证
docker exec -it tomcat-new getent hosts mysql8-server
docker exec -it nginx-web getent hosts tomcat-new
优点 :秒级生效,不用改代码、不用重建容器。
缺点 :docker network connect 是运行时热插拔,容器 docker rm 重建后失效。只适合应急。
接入后 Nginx 需要 reload 让它重新解析 upstream:
bash
docker exec -it nginx-web nginx -t
docker exec -it nginx-web nginx -s reload
Tomcat 需重启应用让连接池重建连接:
bash
docker restart tomcat-new
方案 B:重建容器时显式指定网络(持久化,推荐)
先看原容器启动参数(镜像、端口、挂载、环境变量):
bash
docker inspect tomcat-new --format '{{json .Config}}' | python3 -m json.tool
docker inspect nginx-web --format '{{json .HostConfig.PortBindings}}' | python3 -m json.tool
重建:
bash
# Tomcat
docker rm -f tomcat-new
docker run -d \
--name tomcat-new \
--network my_net \
-p 8688:8080 \
-v /your/app:/usr/local/tomcat/webapps/your-app \
tomcat:9-jdk8
# Nginx(配置挂载,避免重建丢配置)
docker rm -f nginx-web
docker run -d \
--name nginx-web \
--network my_net \
-p 8689:80 \
-v ~/nginx-web/conf.d:/etc/nginx/conf.d:ro \
nginx:latest
方案 C:用 docker-compose 统一网络(生产级首选)
yaml
version: "3.8"
networks:
my_net:
name: my_net
driver: bridge
services:
mysql8-server:
image: mysql:8.0.25
container_name: mysql8-server
networks: [my_net]
ports:
- "3307:3306"
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- ./mysql-data:/var/lib/mysql
restart: unless-stopped
tomcat-new:
image: tomcat:9-jdk8
container_name: tomcat-new
networks: [my_net]
ports:
- "8688:8080"
depends_on:
mysql8-server:
condition: service_healthy
restart: unless-stopped
nginx-web:
image: nginx:latest
container_name: nginx-web
networks: [my_net]
ports:
- "8689:80"
volumes:
- ./nginx-web/conf.d:/etc/nginx/conf.d:ro
depends_on: [tomcat-new]
restart: unless-stopped
docker compose up -d 后所有服务自动在同一 my_net,容器名互相可解析,重建也不丢网络配置。
方案 D:JDBC URL 改用宿主机 IP + 映射端口(不依赖 Docker 网络)
MySQL 映射到宿主机 3307,容器在默认 bridge 时网关为 172.17.0.1:
jdbc:mysql://172.17.0.1:3307/kejichaxin?useSSL=false&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&autoReconnect=true&failOverReadOnly=false&allowPublicKeyRetrieval=true
验证连通性:
bash
docker exec -it tomcat-new sh -c "nc -zv 172.17.0.1 3307"
优点 :容器重建不受影响。
缺点:需改配置;多容器场景下 IP 管理不如服务名清晰。
方案 E:临时 hosts 注入(仅调试)
bash
docker exec -it tomcat-new sh -c 'echo "172.18.0.3 mysql8-server" >> /etc/hosts'
或启动时 --add-host mysql8-server:172.18.0.3。容器重建即丢,仅用于临时验证,不进生产。
五、Nginx 反代 Tomcat 的完整配置
Nginx 接入 my_net 后,proxy_pass 必须写容器名 + 容器内端口(不是宿主机映射端口):
nginx
server {
listen 80;
server_name _;
location / {
proxy_pass http://tomcat-new:8080; # 容器名:容器内端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
}
}
生产注意:upstream 解析缓存问题
Nginx 在启动时解析一次 proxy_pass 的主机名,Tomcat 容器重建、IP 变化后 Nginx 不会自动重解析,会连旧 IP 或报 no resolver defined。生产建议用 resolver 动态解析:
nginx
server {
listen 80;
server_name _;
resolver 127.0.0.11 valid=10s ipv6=off; # Docker 内置 DNS
set $tomcat_upstream http://tomcat-new:8080;
location / {
proxy_pass $tomcat_upstream;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
注意:
proxy_pass用变量时,URL 里的路径部分不会被自动拼接,需自行处理 rewrite。
六、验证清单(修复后逐项确认)
bash
# 1. 网络归属
docker inspect tomcat-new --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
docker inspect nginx-web --format '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}'
# 期望:my_net
# 2. DNS 解析
docker exec -it tomcat-new getent hosts mysql8-server
docker exec -it nginx-web getent hosts tomcat-new
# 3. TCP 连通
docker exec -it tomcat-new sh -c "nc -zv mysql8-server 3306"
docker exec -it nginx-web sh -c "nc -zv tomcat-new 8080"
# 4. 应用日志
docker logs --tail 100 tomcat-new | grep -i "connection\|exception"
docker logs --tail 50 nginx-web | grep -i "error\|emerg"
# 5. Nginx 配置语法与生效
docker exec -it nginx-web nginx -t
docker exec -it nginx-web nginx -T | grep -A10 "server {"
# 6. HTTP 访问
curl -I http://localhost:8689
# 期望:200,且响应来自 Tomcat 应用
七、生产环境治理建议
-
禁止裸
docker run起业务容器 :统一用docker-compose.yml或编排文件,网络、挂载、重启策略显式声明,避免"默认 bridge"踩坑。 -
一个业务域一个自定义网络 :如
my_net承载 MySQL/Tomcat/Nginx,跨域用external network显式连接,不混用默认 bridge。 -
配置一律挂载 :Nginx
conf.d、Tomcatserver.xml、应用application.yml都挂宿主机卷,容器重建不丢。 -
连接串优先用服务名 :
jdbc:mysql://mysql8-server:3306/...,配合 compose 的depends_on+ 健康检查,避免硬编码 IP。 -
Nginx 用
resolver 127.0.0.11:后端容器 IP 可能变,动态解析比启动时解析稳。 -
端口映射只暴露必要端口:MySQL 3306 不必映射到宿主机公网,仅内网网络互通即可;对外只暴露 Nginx 的 80/443。
-
加健康检查 :
yamlhealthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 3s retries: 5配合
depends_on: { mysql8-server: { condition: service_healthy } },避免 Tomcat 先于 MySQL 就绪启动失败。 -
监控与告警 :对
docker events(容器重启)、Nginx 5xx 率、JDBC 连接池活跃数做监控,网络类故障早发现。
八、常见误区
| 误区 | 正解 |
|---|---|
改宿主机 /etc/hosts 加 mysql8-server |
容器有独立 /etc/hosts,改宿主机的无效 |
docker restart 能应用新的 --network |
不能,网络参数只在 docker run 时生效,须重建 |
proxy_pass 写宿主机映射端口 8688 |
容器间通信走容器内端口 8080,不是映射端口 |
docker network connect 一劳永逸 |
容器重建失效,需持久化到 compose/启动命令 |
报 Could not create connection 就是 MySQL 挂了 |
先看是否 UnknownHostException,多半是 DNS/网络问题 |
九、总结
本次故障的根因是新容器落在默认 bridge 网络,无法通过 Docker DNS 解析自定义网络中的服务名。修复分三层:
- 应急 :
docker network connect my_net <容器>+ 重启应用 / Nginx reload,分钟级恢复。 - 持久 :重建容器加
--network my_net,或用 docker-compose 统一网络声明。 - 治理:配置挂载、服务名连接、Nginx 动态 resolver、健康检查与监控,从流程上杜绝同类问题。
记住一句话:Docker 里"连不上"先分三层查------网络归属 → DNS 解析 → 端口连通,绝大多数容器间通信故障都出在第一层。