Docker 自定义桥接网络下容器内无法解析域名及无外部网络访问的完整排查与解决

前言

在 Ubuntu 20.04 上的一台曾作为 Kubernetes 节点的服务器,退役后直接用于运行 docker-compose 部署的业务容器。核心症状是:使用自定义桥接网络的容器无法解析外部域名,且无法访问外部网络(ping 外部 IP 100% 丢包),而使用 --network host 模式的容器一切正常。

整个排查过程历经多个层面:DNS 配置、systemd-resolved、Kubernetes 残留、Docker 版本、daemon.json 解析、iptables NAT 规则缺失,最终通过手动补充 MASQUERADE 规则彻底解决。

问题现象

  • 容器内 cat /etc/resolv.conf 显示:
    nameserver 127.0.0.11
    search openstacklocal
    options ndots:0
    
    并注释显示 ExtServers: [8.8.8.8 114.114.114.114 223.5.5.5](证明 daemon.json 中的 dns 配置被读取),但 nslookup baidu.com 超时。
  • 容器内 ping 8.8.8.8 100% 丢包 → 说明不是单纯 DNS 问题,而是整个外部出站网络不通
  • 宿主机网络正常,host 网络模式容器正常。
  • iptables NAT 表为空,没有任何 MASQUERADE 规则。

问题根本原因(多重叠加)

  1. Kubernetes 节点退役后残留

    • kubelet、CNI 插件清理不彻底,导致 Docker daemon 在初始化用户自定义桥接网络时行为异常。
    • 部分 sysctl 参数和 iptables 链残留,干扰 Docker 自动创建 NAT 规则。
  2. Docker 自动 NAT 规则未创建

    • 正常情况下,Docker 会为每个桥接网络(包括用户自定义网络)在 iptables NAT 表的 POSTROUTING 链自动添加 MASQUERADE 规则,实现容器出站地址伪装。
    • 本案例中,该规则始终缺失,导致容器发出的包(源 IP 为 172.18.x.x)无法得到回复。
  3. daemon.json 配置部分失效

    • 虽然新版 Docker 能读取 dns 配置(容器内注释显示 ExtServers),但由于网络初始化异常,内置 DNS 代理(127.0.0.11)无法实际向外部上游 DNS 发送查询(出站不通)。
    • 早期 daemon.json 曾因复制错误带有 < > 尖括号导致整个文件被忽略,后已修复,但 NAT 问题仍存。
  4. 旧版 Docker (19.03.15) 的历史影响

    • 即使升级到新版,旧状态可能部分继承,导致 NAT 规则不自动生成。

完整排查命令与过程

以下命令按实际排查顺序整理,便于复现。

1. 基础诊断

# 查看容器内 DNS 配置与解析情况
docker exec -it <容器名> cat /etc/resolv.conf
docker exec -it <容器名> nslookup baidu.com
docker exec -it <容器名> ping -c 4 8.8.8.8

# 查看宿主机 DNS
cat /etc/resolv.conf

# 查看 Docker 网络信息
docker network inspect wanwu-net
# 关注 Subnet(如 172.18.0.0/16)和 Gateway

2. 检查 systemd-resolved 干扰

systemctl status systemd-resolved
resolvectl status

# 彻底禁用(推荐)
sudo systemctl stop systemd-resolved
sudo systemctl disable systemd-resolved
sudo rm -f /etc/resolv.conf
sudo tee /etc/resolv.conf <<EOF
nameserver 8.8.8.8
nameserver 114.114.114.114
nameserver 223.5.5.5
search openstacklocal
EOF
sudo chattr +i /etc/resolv.conf   # 防止被覆盖

3. 检查并启用必要内核参数

# 加载桥接过滤模块
sudo modprobe br_netfilter
lsmod | grep br_netfilter

# 启用桥接流量通过 iptables
sudo sysctl net.bridge.bridge-nf-call-iptables=1
sudo sysctl net.bridge.bridge-nf-call-ip6tables=1
sudo sysctl net.ipv4.ip_forward=1

# 永久生效
sudo tee /etc/sysctl.d/99-docker.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system

4. 检查 iptables 规则(关键诊断)

sudo iptables -t nat -L -v -n
sudo iptables -t filter -L -v -n
sudo iptables -L DOCKER-USER -v -n

正常应在 NAT 表的 POSTROUTING 链看到针对自定义网络网段的 MASQUERADE 规则。如果为空,就是问题所在。

5. 验证 daemon.json 是否被正确解析

# 查看当前配置
sudo cat /etc/docker/daemon.json

# 临时禁用测试(确认是否因配置导致)
sudo mv /etc/docker/daemon.json /etc/docker/daemon.json.bak
sudo systemctl restart docker
docker-compose down && docker-compose up -d
# 再检查 NAT 表是否出现规则
sudo iptables -t nat -L -v -n

6. 升级 Docker(解决旧版潜在 bug)

# 使用阿里云镜像源加速
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

# 解除可能的 hold
sudo apt-mark unhold docker-ce docker-ce-cli containerd.io
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo systemctl restart docker

7. 最终解决:手动添加缺失的 NAT 规则

# 获取自定义网络网段
docker network inspect wanwu-net | grep Subnet
# 假设为 172.18.0.0/16

# 手动添加 MASQUERADE 规则
sudo iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o br-* -j MASQUERADE

# 验证规则已添加
sudo iptables -t nat -L -v -n | grep MASQUERADE

# 测试容器网络
docker exec -it <容器名> ping -c 4 8.8.8.8
docker exec -it <容器名> nslookup baidu.com

8. 永久化手动规则(防止重启丢失)

sudo apt-get install -y iptables-persistent
sudo netfilter-persistent save   # 保存当前 iptables 规则

为什么 daemon.json 中的 dns 配置在容器内未直接体现?

  • 新版 Docker 在桥接网络下优先使用内置 DNS 代理 127.0.0.11,以同时支持容器间服务发现(容器名解析)和外部域名解析。
  • daemon.json 中的 dns 配置会作为上游服务器(ExtServers)被内置代理使用,并在容器 /etc/resolv.conf 的注释中显示。
  • 不会直接将公共 DNS 写入容器 resolv.conf(除非使用 --dns 参数或在 docker-compose 中显式指定 dns)。
  • 一旦出站网络通了(NAT 规则生效),127.0.0.11 就能正常向 8.8.8.8 等转发查询,DNS 解析自然恢复。

总结与建议

这个案例的难点在于问题从“DNS 解析失败”逐步演变为“容器无外部网络访问”,根源是 Docker 未自动创建 NAT 规则,而这又与 k8s 残留、配置历史密切相关。

预防建议

  • 退役 k8s 节点后执行 kubeadm reset 或彻底清理 /etc/cni/net.d//var/lib/cni/ 等。
  • daemon.json 配置前使用 JSON 校验工具验证格式。
  • 定期检查 iptables -t nat -L 是否有预期 MASQUERADE 规则。
  • 优先升级到最新 Docker 版本。
posted @ 2026-01-11 13:32  牛奔  阅读(56)  评论(0)    收藏  举报