Docker 容器网络原理与常见问题

Docker 的网络模型是很多人初学时最容易感到困惑的部分之一:为什么容器之间可以互相 ping 通?为什么容器访问宿主机是 172.17.0.1?为什么有时候容器可以访问外网,有时候却突然不行了?排错时到底应该从哪里下手?

这篇文章将从原理层面系统梳理 Docker 的网络实现机制,并结合生产环境中最常见的网络问题,给出清晰的排查思路和解决方案。希望看完之后,你能对 Docker 网络有一个相对完整的认知,并在遇到问题时知道“应该先看什么”。

一、Docker 网络基础架构

Docker 默认提供了几种网络驱动(Network Driver),每种驱动决定了容器如何联网、如何与其他容器/宿主机/外部通信。

Docker 默认支持的网络模式

网络模式驱动名称容器是否有独立 IP是否有端口映射是否隔离典型使用场景
bridgebridge是(默认172.17.0.0/16)需要网络隔离生产环境最常用模式
hosthost无(共享宿主机网络)无需无隔离需要极高网络性能的场景
nonenone无网络自定义网络栈(如 sidecar)
overlayoverlay是(跨主机)需要隔离Swarm 集群或 Kubernetes(flannel/calico)
macvlanmacvlan是(与宿主机同网段)无需物理隔离传统物理网络环境、需要独立 MAC
ipvlanipvlan是(与宿主机同网段)无需逻辑隔离类似 macvlan 但共享 MAC

最重要的一句话生产环境中 90% 以上的单机 Docker 部署都使用默认的 bridge 模式

二、Docker bridge 网络原理(最核心内容)

当你执行 docker run 且不指定 --network 时,默认使用 bridge 模式。

核心组件

  1. docker0 网桥
    • 默认创建的虚拟网桥,地址通常是 172.17.0.1/16
    • 作用相当于一台虚拟交换机,所有容器都挂在这个“交换机”上
  2. veth pair(虚拟网卡对)
    • 每启动一个容器,Docker 都会在宿主机和容器内部各创建一端 veth
    • 宿主机端:vethxxxx(通常随机命名)
    • 容器端:eth0(容器内部看到的网卡)
  3. 容器网络命名空间
    • 每个容器都有独立的网络命名空间(Network Namespace)
    • 容器内部的 eth0、lo、路由表、iptables 都是独立的
  4. iptables NAT 规则
    • Docker 会自动在 POSTROUTING 链添加 MASQUERADE 规则,让容器访问外网时源地址被 NAT 成宿主机的外网 IP

数据流向示例

容器 A → 容器 B(同主机)

  1. 容器 A 发出 IP 包,目的 IP 为容器 B 的 172.17.0.2
  2. 包到达容器 A 的 eth0 → veth pair 另一端(宿主机)
  3. 宿主机上的 docker0 网桥收到包
  4. docker0 根据目的 MAC 地址转发到容器 B 的 veth 端
  5. 进入容器 B 的网络命名空间 → 到达容器 B 的 eth0

容器 → 外网

  1. 容器发出目的为 8.8.8.8 的包
  2. 容器路由表默认走 172.17.0.1(docker0)
  3. 到达 docker0
  4. docker0 将包转发到宿主机的默认网卡(eth0)
  5. POSTROUTING 链执行 SNAT,把源 IP 改为宿主机的公网 IP
  6. 外网返回的包再通过 DNAT 回到容器

三、Docker 网络常见问题与排查思路

1. 容器无法访问外网

最常见现象:容器内 ping 8.8.8.8 超时,但 ping 172.17.0.1 正常

排查顺序(按概率从高到低)

  1. 宿主机本身能否访问外网?
    Bash
    ping 8.8.8.8
    curl -I https://www.google.com
  2. 检查 iptables NAT 规则是否存在
    Bash
    iptables -t nat -L -n -v | grep MASQUERADE
    # 应该看到类似:
    # MASQUERADE  all  --  172.17.0.0/16   !172.17.0.0/16
  3. 检查 FORWARD 链是否 ACCEPT
    Bash
    iptables -L FORWARD -n -v
    # Docker 默认会把 FORWARD 策略设为 DROP,然后靠具体规则放行
  4. 是否误删了 docker0 网桥或 iptables 规则?
    • 重启 docker 服务通常能恢复:systemctl restart docker
  5. 是否使用了自定义 iptables 规则把 FORWARD 链清空了?
  6. 宿主机是否开启了 ip_forward?
    Bash
    sysctl net.ipv4.ip_forward   # 应该为 1

2. 容器之间无法互相通信

现象:同一个 bridge 网络下容器 ping 不通

快速检查

Bash
# 查看容器网络
docker network inspect bridge

# 确认容器是否在同一个网络
docker inspect 容器名 | grep NetworkMode

# 查看容器 IP
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 容器名

常见原因

  • 容器不在同一个网络(默认 bridge 除外)
  • 容器内部防火墙(iptables / ufw / firewalld)阻断了流量
  • 自定义网络驱动配置错误

3. 宿主机访问容器端口失败(宿主机 ping 容器 IP 正常)

原因:Docker 默认不会在宿主机上暴露端口,除非做了端口映射

Bash
# 正确做法
docker run -p 8080:80 nginx

# 错误做法
docker run -p 8080 nginx   # 这样不会映射

宿主机直接访问容器 IP:理论上是可以的,但不推荐(因为容器 IP 是动态的)

4. 容器启动失败:network not found / failed to create NAT chain

常见于:

  • Docker 服务异常重启
  • iptables 被清空或被其他工具(如 firewalld、nftables)接管
  • Docker 版本与内核不兼容(尤其是 4.x 内核上的新版 Docker)

快速修复

Bash
systemctl restart docker
# 或
docker network prune
docker network create bridge

5. 使用 host 网络模式后的问题

host 模式下容器与宿主机共享网络命名空间,常见坑

  • 端口冲突(容器监听的端口宿主机已经在用了)
  • 多个容器监听同一端口会失败
  • 无法使用 -p 做端口映射(因为没有独立网络栈)

6. DNS 解析异常(容器内 curl 域名失败)

常见表现

  • 容器内 ping 8.8.8.8 成功,但 ping www.google.com 失败

原因及解决

  1. 检查 /etc/docker/daemon.json 是否配置了 dns
    JSON
    {
      "dns": ["8.8.8.8", "1.1.1.1"]
    }
  2. 容器是否被设置了无效的 DNS(比如 127.0.0.1 但没有 dnsmasq)
  3. 宿主机本身的 /etc/resolv.conf 被污染
  4. 使用 --dns 参数启动容器

四、生产环境推荐配置

JSON
// /etc/docker/daemon.json
{
  "bip": "172.18.0.1/16",               // 更改默认网段,避免冲突
  "fixed-cidr": "172.18.0.0/24",
  "default-address-pools": [
    {"base":"172.19.0.0/16","size":24},
    {"base":"172.20.0.0/16","size":24}
  ],
  "dns": ["8.8.8.8", "1.1.1.1", "114.114.114.114"],
  "icc": true,                          // 是否允许容器间通信
  "ip6tables": false,                   // 通常关闭 IPv6
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  }
}

五、快速排查清单

当容器网络出问题时,按顺序执行:

  1. 宿主机网络是否正常?
  2. docker network ls 和 docker network inspect 查看网络信息
  3. docker inspect 容器ID 查看 NetworkSettings
  4. iptables -t nat -L -n -v 检查 NAT
  5. iptables -L -n -v 检查 FORWARD
  6. sysctl net.ipv4.ip_forward
  7. 重启 docker 前先 docker network prune

结语

Docker 的网络本质上就是Linux 网络命名空间 + 虚拟网桥 + iptables NAT + veth pair 的组合。理解了这个底层原理,再遇到网络问题时,你就不再是盲目试各种参数,而是有逻辑地去定位“到底是哪一层断了”。

生产环境中建议:

  • 能用 bridge 就尽量用 bridge
  • 提前规划好网段,避免冲突
  • 把 DNS 写死到 daemon.json
  • 不要随意清空 iptables
  • 重要业务建议使用 overlay 或 macvlan + 外部负载均衡

欢迎在留言区分享你遇到过的最坑的 Docker 网络问题,或者你当前生产环境的网络模型,我们可以一起讨论更优的方案。

THE END