遇到个罕见bug,记录一下
核心就是Docker 默认不会自动开启网桥混杂模式,在老旧内核(3.10)上必须手动设置,否则二层转发失效
🔧 环境信息
| 组件 | 版本 |
|---|---|
| 操作系统 | CentOS Linux release 7.x (Core) |
| 内核 | 3.10.0-327.10.1.el7.x86_64 |
| Docker | 26.1.4, build 5650f9b |
| Nginx 镜像 | nginx:1.29.5 |
🚨 问题现象
-
使用
docker run -d --name nginx -p 80:80 nginx启动容器。 -
宿主机执行
curl http://localhost返回:curl: (56) Recv failure: Connection reset by peer -
改用
--network host模式,curl http://localhost正常。 -
宿主机直接访问容器 IP:
curl http://172.17.0.2报错:
curl: (7) Failed connect to 172.17.0.2:80; 没有到主机的路由
🔍 排查过程
1️⃣ 确认端口监听与 docker-proxy
bash
ss -tlnp | grep :80
输出显示 docker-proxy 已正确监听 *:80 和 [::]:80,且无其他进程占用 80 端口。
2️⃣ 检查 iptables 规则
bash
iptables -t nat -L DOCKER -n -v # DNAT 规则存在
iptables -L FORWARD -n -v # FORWARD 链策略为 DROP
修正 FORWARD 策略:
bash
iptables -P FORWARD ACCEPT
问题依旧。
3️⃣ 防火墙与 SELinux
bash
systemctl status firewalld # inactive
getenforce # Permissive
均未造成影响。
4️⃣ 容器内网络自检
bash
docker exec nginx curl http://localhost # 成功,返回 nginx 欢迎页
说明 nginx 服务正常。
5️⃣ 宿主机 ↔ 容器 IP 连通性
bash
ping 172.17.0.2 # 无响应(后确认路由表存在)
docker inspect -f '{{.NetworkSettings.IPAddress}}' nginx # 172.17.0.2
路由表存在 172.17.0.0/16 dev docker0,但 ping 不通。
6️⃣ 深入二层网络诊断
-
检查 veth 对挂载状态
bashbridge link show | grep veth输出显示 veth 设备已挂载至
docker0,状态UP。 -
查看网桥 MAC 地址表
bashbridge fdb show dev docker0只有
docker0自身的永久条目,没有容器的 MAC 地址。 -
抓取 ARP 包
bashtcpdump -i docker0 -e -n arp执行
ping 172.17.0.2,无任何 ARP 请求/应答包。 -
容器内网络参数
bashdocker exec nginx cat /proc/net/route # 路由表正常,默认网关 172.17.0.1 docker exec nginx cat /proc/sys/net/ipv4/conf/eth0/arp_ignore # 0 docker exec nginx cat /proc/sys/net/ipv4/conf/eth0/arp_announce # 0 docker inspect nginx | grep -A15 "Networks" # IP、网关、MAC 分配正确 -
容器内访问网关
bashdocker exec nginx curl http://172.17.0.1 # 卡住,无响应证明容器到网关的二层路径完全中断。
根本原因分析
二层转发链路断裂 :docker0 网桥未启用混杂模式(promiscuous mode),导致网桥无法接收/转发来自 veth 端口的数据帧,同时也无法将 ARP 请求广播到容器端。
- 网桥在未开启混杂模式时,只接受目的 MAC 为自身或广播帧,不会学习动态 MAC 地址。
- 容器启动后未主动发送免费 ARP,网桥 FDB 表为空。
- 宿主机发出 ARP 请求(目标 IP 为容器 IP),网桥将此请求广播到所有端口,但由于混杂模式未开,容器端的 veth 接口收不到该广播帧(或被网桥过滤),容器无响应。
- 结果:ARP 解析失败 → "没有到主机的路由" → 端口映射链路也被迫中断(docker-proxy 无法与容器建立连接)→
Connection reset by peer。
✅ 解决方案(完整修复步骤)
1. 彻底重置 Docker 网络栈
bash
# 停止所有容器
docker rm -f $(docker ps -aq) 2>/dev/null
# 停止 Docker 服务(注意 docker.socket 联动)
systemctl stop docker
# 删除 docker0 网桥及网络缓存
ip link delete docker0 2>/dev/null
rm -rf /var/lib/docker/network
rm -rf /var/lib/docker/nv 2>/dev/null
# 确保 bridge-nf 参数
modprobe br_netfilter # 若内核支持;CentOS 7 内核可能无此模块,跳过不影响
sysctl -w net.bridge.bridge-nf-call-iptables=1
sysctl -w net.bridge.bridge-nf-call-ip6tables=1
echo "net.bridge.bridge-nf-call-iptables=1" >> /etc/sysctl.conf
echo "net.bridge.bridge-nf-call-ip6tables=1" >> /etc/sysctl.conf
# 启动 Docker
systemctl start docker
2. 启用 docker0 混杂模式(核心修复)
bash
ip link set docker0 promisc on
说明:Docker 默认不会自动开启网桥混杂模式,在老旧内核(3.10)上必须手动设置,否则二层转发失效。
3. 重新运行容器并刷新 ARP
bash
docker run -d --name nginx -p 80:80 nginx
# 强制刷新网桥 ARP 缓存
ip neigh flush dev docker0
# 测试连通性
ping -c 3 172.17.0.2 # 应立刻收到响应
curl http://172.17.0.2 # 返回 nginx 欢迎页
curl http://localhost # 返回 nginx 欢迎页
4. 验证网桥 FDB 表
bash
bridge fdb show dev docker0
此时应出现类似:
02:42:ac:11:00:02 dev vethxxxxx master docker0
验证
curl http://172.17.0.2成功curl http://localhost成功docker exec nginx curl -s http://172.17.0.1成功
为何之前重置无效?
首次重置后未启用混杂模式,虽然 docker0 重新创建,但依然处于非混杂状态,二层转发依然被阻断。
混杂模式是此环境的必选项,而非可选优化。
预防措施与长期建议
1. 开机自动启用 docker0 混杂模式
创建 systemd drop-in 或 rc.local:
bash
cat > /etc/systemd/system/docker.service.d/promisc.conf <<EOF
[Service]
ExecStartPost=/sbin/ip link set docker0 promisc on
EOF
systemctl daemon-reload
2. 升级内核(推荐)
CentOS 7 默认内核 3.10 存在多个网络模块缺陷,建议升级到长期支持版(kernel-lt):
bash
rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
yum install -y https://www.elrepo.org/elrepo-release-7.el7.elrepo.noarch.rpm
yum --enablerepo=elrepo-kernel install -y kernel-lt
grub2-set-default 0
reboot
新内核(如 5.4+)对 Docker 网络兼容性更好,通常无需手动设置混杂模式。
3. 升级 Docker
当前版本 26.1.4 较新,但配合老旧内核仍可能触发此问题。保持 Docker 最新即可。
📚 参考资料
- Docker 文档:
bridge驱动工作原理 - Linux 内核网桥模块:
bridge(8)、brctl(8) - 相关 issue:
笔记作者 :likeghee
记录时间 :2026-02-13
状态:已解决,归档备查