Docker容器网络互通排障全流程:从Bridge到iptables的深度排查
前言
容器网络互通是Docker部署中最容易出问题的环节之一。相比传统VM网络,容器网络多了好几层抽象——Bridge、iptables、veth pair、overlay……任何一个环节出问题,都会导致容器之间无法通信。
本文记录一次典型的跨宿主机容器网络互通问题排查全过程,从现象到根因到解决,提供可复用的排障思路和一键脚本。
问题背景
业务场景
在多台服务器上使用Docker Compose部署了一套微服务架构,各容器之间需要通过内部网络互通:
web 服务(前端Nginx)
api 服务(Python/Go后端)
db 服务(MySQL数据库)
三台服务器各运行部分容器,部分请求需要跨服务器访问。例如p14(某VPS)上的web容器需要访问VM152上的api容器。
问题现象
部署完成后,同一宿主机上的容器之间通信正常,但跨宿主机通信全部失败:
1 2 3 4 5
| curl http://api:8000/health ✅ 正常
curl http://api-host:8000/health ❌ 连接超时
|
错误信息:Connection timed out 或 No route to host
环境信息
| 节点 |
IP |
容器网络 |
说明 |
| p14(某VPS) |
某VPS_IP |
172.18.0.0/16 |
Docker Bridge: docker0 |
| VM152 |
某内网IP |
172.19.0.0/16 |
Docker Bridge: docker0 |
| VM151 |
某内网IP |
172.20.0.0/16 |
Docker Bridge: docker0 |
三个网络地址完全不同,网段也不在同一大段——这为互通埋下了隐患。
排查过程
第一步:确认Docker网络配置
先检查各宿主机的Docker网络配置:
1 2 3 4
| docker network ls docker network inspect bridge docker network inspect <自定义网络名>
|
发现各节点使用的是默认的 bridge 网络,且网段配置如下:
1 2 3
| p14: 172.17.0.0/16 VM152: 172.18.0.0/16 VM151: 172.19.0.0/16
|
问题初步定位:三个节点的Docker Bridge网段完全不同,且互不重叠。
Docker默认的Bridge模式下,不同宿主机的容器处于完全隔离的网络中,无法直接互通。这是正常行为,不是bug。
第二步:理解Docker网络模式
Docker提供多种网络模式:
| 模式 |
说明 |
跨主机互通 |
| bridge |
默认模式,使用docker0网桥 |
❌ 不能直接互通 |
| host |
容器共享宿主机网络 |
✅ 可以,但易端口冲突 |
| overlay |
Docker Swarm模式,自带VXLAN隧道 |
✅ 可以 |
| macvlan |
给容器分配真实MAC,挂在物理网络上 |
✅ 可以 |
要实现跨主机互通,有三种常用方案:
- Docker Compose + external networks:使用自定义bridge网络配合
--net=host
- Docker overlay:需要Docker Swarm,比较重
- macvlan + 物理网络打通:最接近物理网络的方案
考虑到生产环境的简洁性,我们选择 macvlan 方案。
第三步:检查宿主机网络是否支持macvlan
macvlan需要物理网卡支持”混杂模式”。检查方法:
1 2 3 4 5 6 7
| ip link show eth0
ethtool eth0 | grep -i promis
|
如果网卡不支持macvlan,需要改用overlay或自定义bridge方案。
第四步:创建macvlan网络
在各宿主机上执行以下命令(注意:需要先删除旧的bridge容器):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| docker network create \ --driver macvlan \ --subnet=172.18.0.0/16 \ --gateway=172.18.0.1 \ -o parent=eth0 \ macvlan_net
docker network create \ --driver macvlan \ --subnet=172.19.0.0/16 \ --gateway=172.19.0.1 \ -o parent=eth0 \ macvlan_net
|
第五步:发现新问题——网关不通
创建macvlan网络后,发现容器仍然无法跨宿主机通信。
用 ping 测试:
问题定位:宿主机之间的路由不通。
检查网络架构:
1 2
| p14所在网络: 192.168.160.0/24 (假设) VM152所在网络: 192.168.102.0/24 (假设)
|
两个内网不在同一网段,需要通过路由器/三层交换转发。
第六步:检查iptables和路由表
在VM152上检查iptables规则:
1 2 3 4 5 6 7
| iptables -t nat -L -n
cat /proc/sys/net/ipv4/ip_forward
|
发现 ip_forward 是关闭的。开启后再次测试:
1 2 3 4 5
| echo 1 > /proc/sys/net/ipv4/ip_forward
sysctl -w net.ipv4.ip_forward=1
|
但问题依然存在。
深入检查iptables的FORWARD链:
1 2 3 4 5
| iptables -L FORWARD -n -v
iptables -P FORWARD ACCEPT
|
测试发现:iptables的FORWARD策略默认是DROP的,阻止了跨宿主机流量。
第七步:配置正确的iptables规则
1 2 3 4 5 6 7 8 9
|
iptables -A FORWARD -s 172.18.0.0/16 -d 172.19.0.0/16 -j ACCEPT iptables -A FORWARD -s 172.19.0.0/16 -d 172.18.0.0/16 -j ACCEPT
iptables -A FORWARD -s 172.0.0.0/8 -d 172.0.0.0/8 -j ACCEPT
|
但这种逐条配置的方式太繁琐,且不够安全。
更好的方案是:在Docker服务启动时自动配置正确的iptables规则:
1 2 3 4 5 6 7 8 9 10
| vim /etc/systemd/system/docker.service.d/10-macvlan.conf
[Service] ExecStart= ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 \ --ip-masq=true \ --ip-forward=true \ --iptables=true
|
第八步:重新测试连通性
1 2 3 4 5 6 7
| systemctl daemon-reload systemctl restart docker
docker run --rm --net=macvlan_net alpine ping -c 3 172.19.0.x
|
根因分析
为什么默认bridge网络不能跨主机互通?
Docker默认的bridge网络是”单主机”的。docker0 网桥只在本机生效,不同宿主机的docker0网桥完全隔离。
当你执行 docker network inspect bridge 时,会发现:
1 2 3 4 5 6
| { "Name": "bridge", "Driver": "bridge", "Subnet": "172.17.0.0/16", "Gateway": "172.17.0.1" }
|
这个172.17.0.0/16网段只在当前宿主机内有效。另一台宿主机的容器即使也用了172.17.0.0/16,也不会和第一台冲突——因为它们根本不在同一个二层网络中。
为什么需要macvlan或overlay?
要让容器跨主机通信,需要在”容器网络”和”物理网络”之间架设桥梁:
- macvlan:让容器直接”挂”在物理网络上,容器有自己的MAC和IP,就像真实的物理机一样
- overlay:在物理网络之上建立VXLAN隧道,容器在overlay网络中互通,物理网络只负责传输VXLAN数据包
两种方案各有优劣:
| 方案 |
优点 |
缺点 |
| macvlan |
性能最好,接近物理网络 |
需要网卡支持混杂模式,配置复杂 |
| overlay |
Docker原生支持,易配置 |
性能略有损耗,需要额外存储 |
完整解决方案
方案一:macvlan方案(推荐用于生产环境)
前置条件:
- 网卡支持混杂模式
- 宿主机网络允许macvlan流量
步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| docker network create \ --driver macvlan \ --subnet=<各节点独立子网> \ --gateway=<各节点网关> \ -o parent=<物理网卡名> \ --internal \ macvlan_internal
docker run --rm --net=macvlan_internal --ip=<固定IP> -it alpine sh
iptables -A FORWARD -s <各节点子网> -j ACCEPT iptables -A FORWARD -d <各节点子网> -j ACCEPT
|
方案二:overlay方案(推荐用于Docker Swarm环境)
1 2 3 4 5 6 7 8
| docker swarm init
docker network create -d overlay --attachable my_overlay_net
docker run --rm --net=my_overlay_net -it alpine sh
|
一键排障脚本
以下脚本可快速排查容器网络问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #!/bin/bash
set -e
echo "========== Docker 容器网络排障 ==========" echo "时间:$(date '+%Y-%m-%d %H:%M:%S')" echo ""
echo "[1/7] Docker网络列表:" docker network ls echo ""
echo "[2/7] docker0网桥状态:" ip addr show docker0 2>/dev/null || echo " docker0不存在(可能使用其他网络)" echo ""
echo "[3/7] IP转发状态:" IP_FORWARD=$(cat /proc/sys/net/ipv4/ip_forward) echo " ip_forward = $IP_FORWARD" if [ "$IP_FORWARD" != "1" ]; then echo " ⚠️ IP转发未开启,可能导致跨宿主机通信失败" fi echo ""
echo "[4/7] iptables FORWARD策略:" FORWARD_POLICY=$(iptables -L FORWARD -n | grep "^Chain FORWARD" | awk '{print $4}') echo " 默认策略: $FORWARD_POLICY" if [ "$FORWARD_POLICY" = "DROP" ]; then echo " ⚠️ FORWARD策略为DROP,可能阻止容器通信" fi echo ""
echo "[5/7] 各Docker网络详情:" for net in $(docker network ls --format '{{.Name}}' | grep -v -E '^(none|host)$'); do echo " --- $net ---" docker network inspect $net --format '{{range .IPAM.Config}}Subnet: {{.Subnet}} Gateway: {{.Gateway}}{{end}}' 2>/dev/null echo "" done
echo "[6/7] 跨宿主机连通性测试:" echo " 说明:以下为示例测试,请根据实际环境修改目标IP"
echo ""
echo "[7/7] 容器DNS配置:" docker run --rm --net=bridge alpine cat /etc/resolv.conf 2>/dev/null || echo " 无法获取DNS配置" echo ""
echo "========== 排障完成 ==========" echo "" echo "常见问题处理建议:" echo "1. IP转发未开:sysctl -w net.ipv4.ip_forward=1" echo "2. FORWARD被DROP:iptables -P FORWARD ACCEPT" echo "3. 跨主机不通:检查物理网络路由或使用overlay/macvlan" echo "4. DNS解析失败:检查自定义网络的DNS配置"
|
使用方法:
1 2
| chmod +x /opt/scripts/docker-network-check.sh /opt/scripts/docker-network-check.sh
|
常见问题解答
Q1:为什么容器能ping通宿主机,但ping不通其他宿主机上的容器?
A:这通常意味着:
- 宿主机之间的路由不通(检查网络设备和路由表)
- iptables的FORWARD策略阻止了流量(检查iptables -L FORWARD)
- IP转发未开启(检查/proc/sys/net/ipv4/ip_forward)
Q2:macvlan容器无法获取IP怎么办?
A:可能的原因:
- 物理网卡不支持混杂模式(用
ip link show eth0 检查)
- 父网络(parent)配置错误
- 子网与物理网络冲突
Q3:overlay网络创建失败怎么排查?
A:overlay需要Docker Swarm环境。先检查是否已初始化Swarm:
1 2 3
| docker info | grep Swarm
docker swarm init
|
Q4:容器内无法解析其他容器的名字怎么办?
A:Docker的嵌入式DNS(Embedded DNS)只对用户自定义网络(如docker network create创建的网络)生效。默认bridge网络不支持容器间DNS解析。
解决方案:
1 2 3 4 5 6 7 8 9
| docker network create my_net
docker run --net=my_net --name=container_a -d image_a docker run --net=my_net --name=container_b -d image_b
docker exec container_b ping container_a
|
Q5:如何在不停容器的情况下修复网络问题?
A:Docker允许动态创建网络并让运行中的容器连接:
1 2 3 4 5 6 7 8
| docker network create -d bridge --subnet=172.20.0.0/16 my_new_net
docker network connect my_new_net <运行中的容器ID>
docker network disconnect bridge <容器ID>
|
总结
Docker容器网络互通问题排查的核心思路:
- 先确认是单主机问题还是跨主机问题:同主机通、跨主机不通,通常是网络层问题
- 理解Docker网络模式:bridge/host/overlay/macvlan各有特点,选对方案是关键
- 逐层排查:物理网卡 → 网桥 → iptables → 路由表 → 应用层
- 善用工具:
ip、iptables、docker network inspect、tcpdump都是排查利器
希望这篇文章能帮到你。如果有问题,欢迎在评论区讨论。
作者:小六,一个在上海努力搬砖的程序员