Docker 容器网络不通:Tomcat 无法解析 MySQL、Nginx 无法反代 Tomcat 的生产级排查与治理

Docker 容器网络不通:Tomcat 无法解析 MySQL、Nginx 无法反代 Tomcat 的生产级排查与治理

一、问题背景

在单机 Docker 环境中,常见这样的部署组合:

  • mysql8-server:MySQL 8.0 容器,接在自定义网络 my_net
  • tomcat-old:老 Tomcat 容器,接在 my_net,运行正常
  • tomcat-new:新起的 Tomcat 容器,用 docker run 默认网络启动,启动后报 Could not create connection to database server
  • nginx-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 应用

七、生产环境治理建议

  1. 禁止裸 docker run 起业务容器 :统一用 docker-compose.yml 或编排文件,网络、挂载、重启策略显式声明,避免"默认 bridge"踩坑。

  2. 一个业务域一个自定义网络 :如 my_net 承载 MySQL/Tomcat/Nginx,跨域用 external network 显式连接,不混用默认 bridge。

  3. 配置一律挂载 :Nginx conf.d、Tomcat server.xml、应用 application.yml 都挂宿主机卷,容器重建不丢。

  4. 连接串优先用服务名 :jdbc:mysql://mysql8-server:3306/...,配合 compose 的 depends_on + 健康检查,避免硬编码 IP。

  5. Nginx 用 resolver 127.0.0.11:后端容器 IP 可能变,动态解析比启动时解析稳。

  6. 端口映射只暴露必要端口:MySQL 3306 不必映射到宿主机公网,仅内网网络互通即可;对外只暴露 Nginx 的 80/443。

  7. 加健康检查 :

    yaml 复制代码
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 3s
      retries: 5

    配合 depends_on: { mysql8-server: { condition: service_healthy } },避免 Tomcat 先于 MySQL 就绪启动失败。

  8. 监控与告警 :对 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 解析自定义网络中的服务名。修复分三层:

  1. 应急 :docker network connect my_net <容器> + 重启应用 / Nginx reload,分钟级恢复。
  2. 持久 :重建容器加 --network my_net,或用 docker-compose 统一网络声明。
  3. 治理:配置挂载、服务名连接、Nginx 动态 resolver、健康检查与监控,从流程上杜绝同类问题。

记住一句话:Docker 里"连不上"先分三层查------网络归属 → DNS 解析 → 端口连通,绝大多数容器间通信故障都出在第一层。

相关推荐
YWamy8 小时前
如何降低RTC SDK在复杂网络环境下的运行成本
网络·音视频·实时音视频
北京盛世宏博8 小时前
信创国产化智慧档案馆管库系统:十二防一体化管控平台落地笔记
网络·方案·温湿度·智慧档案馆建设
w-w0w-w9 小时前
SQL注入
网络·数据库·sql
一条泥憨鱼9 小时前
【计算机网络】短连接、长连接与连接池:从 TCP 握手到池化复用的完整链路
网络·数据库·网络协议·tcp/ip·计算机网络
北京盛世宏博9 小时前
数字孪生 3D 可视化实战:档案十防体系温湿度数据联动与告警定位实现
网络·档案八防·十防
螺蛳粉 螺蛳粉10 小时前
第 05 篇:etcd 深入:Raft 共识、watch 机制与快照备份恢复实战(底座认知 05)
docker·云原生·容器·kubernetes·etcd·分布式存储·kube-apiserver
Lynne30910 小时前
EtherCAT网络中,多个节点如何配置才能互不干扰?
网络·网络配置·ethercat·工业网络
Yang961111 小时前
实验室里的多面手:鼎讯信通TM-150信号发生器技术拆解与实测思考
网络
默|笙13 小时前
【Linux网络】传输层协议TCP(1)---TCP报文介绍
linux·网络·tcp/ip