本文记录了一次生产环境中Docker Compose网络配置导致的服务器连接问题,详细分析了问题现象、排查过程、冲突原理,并提供了临时和长期解决方案。通过这次事件,我们可以更好地理解Docker的网络模型和IP地址分配机制,以及如何在复杂网络环境中正确配置Docker网络。

问题现象

在一次执行docker compose up -d命令后,测试服务器突然无法连通。测试环境配置如下:

  • 测试服务器拥有公网独立 IP,通过 TP-Link 路由器连接
  • 服务器内网 IP 为192.168.0.100
  • 通过 VPN 连接路由器,VPN 分配的内网 IP 为192.168.100.3

问题出现后,我的电脑可以连接 VPN,但无法连接到测试服务器。

排查过程

  1. 通过远程桌面连接到内网中的另一台机器192.168.0.12
  2. 从该机器登录到测试服务器192.168.0.100
  3. 在服务器上尝试 ping 本机 VPN IP:ping 192.168.100.3
  4. 出现错误信息:192.168.96.1 unreachable
  5. 运行ifconfig查看网络接口,发现192.168.96.1是 Docker Compose 创建的网卡
  6. 执行docker compose down后,问题立即解决,网络恢复正常

原理解析

Docker 网络模型

Docker 默认使用三种网络模式:

  • bridge:默认网络模式,创建独立的网络命名空间
  • host:容器使用主机网络栈
  • none:容器没有网络连接

Docker Compose 创建的默认网络是一个 bridge 网络,它会为每个项目创建独立的网络命名空间。

IP 地址分配机制

Docker 在创建网络时有默认的 IP 分配机制:

  1. 优先使用172.16.0.0/12网段
  2. 当 172 网段被占用或冲突时,会使用192.168.0.0/16网段
  3. 在此案例中,Docker 选择了192.168.96.0/20子网

冲突原因

问题的根本原因在于 Docker 自动选择的网段与我们的 VPN 网段产生了路由冲突:

  • VPN 网段:192.168.100.0/24(包含在192.168.96.0/20中)
  • Docker 网段:192.168.96.0/20

当数据包尝试访问192.168.100.3时,Linux 路由表选择了 Docker 网络接口,而不是 VPN 接口,导致通信失败。

为什么外网连接也受到影响

这个路由冲突不仅影响内部通信,也导致外网无法连接到服务器的原因是:

  1. 路由决策问题

    • 当外部连接通过 VPN 尝试访问服务器(192.168.0.100)时,VPN 网关正确地将请求发送到服务器
    • 但是服务器需要回应这些请求,它会查找路由表决定如何发送响应包
    • 由于 Docker 网络接口声明它负责 192.168.96.0/20 网段(包含了 VPN 的 192.168.100.0/24 网段)
  2. 响应包路由错误

    • 服务器会尝试通过 Docker 网络接口而不是 VPN 接口发送响应数据包
    • 这些响应数据包被错误地路由到了 Docker 的网络空间,而不是返回到 VPN 客户端
    • 结果是:连接请求可以到达服务器,但响应永远无法返回到发起请求的客户端
  3. 网络连接的双向性

    • 建立网络连接需要双向通信成功
    • 即使入站连接可以到达目标,如果出站响应无法返回,连接仍然会失败
    • 这就是为什么看起来”服务器不能连通”的原因

这种情况类似于网络中的”非对称路由”问题,即数据进入和离开使用不同的路径,但在此情况下,离开的路径实际上是无效的。

解决方案

临时解决

  • 执行docker compose down关闭容器和网络

长期解决

  1. docker-compose.yml中明确指定网络配置:
1
2
3
4
5
networks:
default:
ipam:
config:
- subnet: 10.10.0.0/16
  1. 修改 Docker 守护进程默认设置,编辑/etc/docker/daemon.json
1
2
3
{
"default-address-pools": [{ "base": "10.10.0.0/16", "size": 24 }]
}

Docker 守护进程网络配置详解

daemon.json是 Docker 守护进程的主要配置文件,通过修改它可以改变 Docker 的全局行为。上述配置中:

  • default-address-pools: 定义 Docker 在创建新网络时使用的 IP 地址池

    • base: 10.10.0.0/16 - 指定 Docker 网络使用的基础 IP 地址段

      • 这里选择 10.10.0.0/16 是因为它是私有地址空间,且通常与企业内部的网络不冲突
      • 也可以使用 172.17.0.0/16 或其他私有网段,只要不与现有网络重叠
    • size: 24 - 定义每个 Docker 网络分配的子网大小

      • 值 24 意味着每个 Docker 网络会获得一个/24 子网(有 256 个 IP 地址)
      • 较小的 size 值(如 20)会分配更大的子网,较大的值(如 26)会分配更小的子网
      • 根据您的需求选择合适的大小:小型项目可以使用/27 或/28,大型项目可能需要/24 或更大

应用配置步骤

  1. 创建或编辑文件:sudo nano /etc/docker/daemon.json
  2. 添加以上配置内容
  3. 保存并关闭文件
  4. 重启 Docker 守护进程:sudo systemctl restart docker

为什么这能解决问题

  • 通过明确指定 Docker 使用 10.10.0.0/16 网段,避免了与 VPN 使用的 192.168.100.0/24 网段冲突
  • 当 Docker 需要创建新网络时,会从这个指定的地址池中分配 IP 段
  • 由于使用了完全不同的网段,不会与现有网络路由产生冲突

注意事项

  • 修改此配置会影响所有新创建的 Docker 网络,已存在的网络不受影响
  • 重启 Docker 后,可能需要重新创建已有的容器和网络
  • 在选择地址段时,确保与整个企业网络规划协调,避免创建新的冲突

总结

这次事件揭示了 Docker 网络自动配置可能带来的潜在问题。在使用 Docker 的生产或测试环境中,应当注意网络规划,避免 IP 地址段冲突,特别是当环境中已有复杂网络设置(如 VPN)时。明确指定 Docker 网络配置是避免此类问题的最佳实践。