花拾录
← 返回知识库

容器连数据库被拒:自定义网络的实际子网和你预设的不一样

云计算 / 运维导入2026/09/220 阅读0 评论

你给应用容器和数据库容器建了一个自定义网络,让它们通过内网互通。数据库那边配置了放行规则,只允许应用所在网段访问。可应用一连就被拒,日志说连接被拒绝或超时。你把放行规则改来改去,就是不通。

现象

现象是"连接被拒"或"认证/网络失败",而且只在容器间通过网络访问时出现——如果你从宿主机用同样的凭据连,却完全正常。

在数据库侧(或它所在主机的防火墙日志里)能看到这样的线索:被拒绝的来源 IP,是一个容器网段的地址,比如 172.18.0.5 之类;而你的放行规则里写的,是另一个网段,比如你"以为"的 172.20.0.0/16。两者对不上,于是被规则拦下。

你翻开自己写放行规则时记的笔记,上面明明写着"容器网段是 172.20",可实际流量却来自 172.18。

根因

关键点在于:Docker 自定义网络的子网,是"分配"出来的,不是你"设定"的那个。

当你创建一个自定义 bridge 网络时,如果没有显式指定 --subnet,Docker 会从它自己的地址池里,挑一个当前没有被占用的网段分给你。不同的宿主机、不同的时间、有没有别的网络先建过,都会影响它挑到哪个。你的经验值("一般就是 172.18 吧")在下一台机器上就可能不成立。

具体地:

  • 你以为的网段,可能来自"上一台机器上看到的值",或者某篇教程里的例子;
  • 实际分配的网段,是 Docker 根据现有网络情况动态选的;
  • 于是放行规则写的是 A 网段,实际流量来自 B 网段,规则自然不匹配。

还有一个更隐蔽的变体:容器重启或重建后,如果网络被重建过,它的子网甚至可能变。把防火墙规则硬编码成某个具体网段,本质上是在赌"它永远不变"。

解决

第一步:查出真实子网,别猜。

# 列出所有网络
docker network ls

# 查某个网络实际分配到的子网
docker network inspect <网络名> \
  --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'

输出的就是这个网络真实的 CIDR,比如 172.18.0.0/16。

第二步:用这个真实值去配放行规则,而不是你记忆里的值。

第三步(更稳的做法):显式指定子网,把控制权拿回来。在 compose 里给网络写明地址段:

networks:
  mynet:
    driver: bridge
    ipam:
      config:
        - subnet: 172.30.0.0/24

这样每次创建都落在这个网段,规则可以放心写死。注意两点:所选网段不能和宿主机已有网络、VPN 网段、公司内网冲突;改动后需要重建网络(docker compose down 再 up -d)。

如果只是临时想确认"容器实际用的是哪个 IP",可以:

docker compose exec app hostname -i
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' <容器名>

延伸与预防

这条坑的通用教训是:凡是被系统"动态分配"的东西,都不要按经验预设。IP 地址、子网、端口、PID、容器 ID,都属于这一类。它们"看起来稳定",只是因为恰好没变过。

工程上正确的态度有两种,二选一:

  • 要么显式固定(显式指定子网、固定端口映射),让它可预测;
  • 要么显式查询(每次用查询命令拿当前值),别把查询结果抄进配置然后遗忘。

最差的做法是第三种:凭印象写死。这正是"上个月明明是 172.18"这类事故的来源。把"先查真实值"变成肌肉记忆,这类问题就再也不会出现。

评论(0)

  • 还没有评论,来抢沙发~

相关文章