Skip to content

1 背景

Docker是当前最常用的容器运行时引擎,自己用docker-compose搭建的开发环境,在新安装Docker后的使用过程中,发现通过docker run命令启动的容器,使用bridge网络的情况下,容器无法连接到外部网络,针对这个现象进行排查。

2 排查过程

使用 docker run -it --rm alpine /bin/sh 启动一个容器,采用新创建的1panel-network网络,在容器内ping外部网络的IP,我们发现是无法ping通,该命令会hang住。退出该容器,再尝试使用host网络启动容器,docker run -it --rm --network=bridge alpine /bin/sh,这次我们发现是可以ping通外部网络的,说明是docker的默认bridge网络无问题,新创建网络有问题。

2.1 创建bridge网络

为了验证是否有问题,我们新建一个网络

sh
docker network create \
  --driver bridge \
  --subnet 172.19.0.0/16 \
  --gateway 172.19.0.1 \
  --opt com.docker.network.bridge.name=1panel-network \
  --opt com.docker.network.driver.mtu=1450 \
  1panel-network

此网络保证了容器的mtu和宿主机保持一致。

容器使用bridge网络的情况下,在ping外部网络的情况下,如果发送的不是Docker启动创建的docker0的网桥,会进行SNAT,然后使用宿主机的网卡出去,那么怀疑是SNAT可能有问题,因此查看iptables中和Docker相关的规则。命令结果如下:

sh
# iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 845 packets, 57086 bytes)
 pkts bytes target     prot opt in     out     source               destination
 1414 96107 POSTROUTING_direct  all  --  *      *       0.0.0.0/0            0.0.0.0/0
 1414 96107 POSTROUTING_ZONES_SOURCE  all  --  *      *       0.0.0.0/0            0.0.0.0/0
 1414 96107 POSTROUTING_ZONES  all  --  *      *       0.0.0.0/0            0.0.0.0/0

根据上面结果,我们发现

  1. 缺少了一条-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE的iptables规则。
  2. 缺少一条10 592 MASQUERADE all -- * !1panel-network 192.168.100.0/24 0.0.0.0/0的网关转发信息 们怀疑是用户在配置Docker的时候,配置出错导致的该问题。

2.2 排查过程

根据上面的章节,我们发现是iptable的SNAT规则有问题,那么我们就要看Docker是如何来生成该规则的。

2.3 查看配置

首先查看官方的文档,我们看到官方文档有一段关于SNAT的相关配置:

--iptables=false prevents the Docker daemon from adding iptables rules. If multiple daemons manage iptables rules, they may overwrite rules set by another daemon. Be aware that disabling this option requires you to manually add iptables rules to expose container ports. If you prevent Docker from adding iptables rules, Docker will also not add IP masquerading rules, even if you set --ip-masq to true. Without IP masquerading rules, Docker containers will not be able to connect to external hosts or the internet when using network other than default bridge. --iptables=false 可阻止 Docker 守护进程添加 iptables 规则。如果多个守护进程管理 iptables 规则,它们可能会覆盖其他守护进程设置的规则。请注意,禁用此选项需要您手动添加 iptables 规则以公开容器端口。如果您阻止 Docker 添加 iptables 规则,即使您将 --ip-masq 设置为 true,Docker 也不会添加 IP 伪装规则。如果没有 IP 伪装规则,Docker 容器在使用默认网桥以外的网络时将无法连接到外部主机或互联网。

可以看到对于缺少的这条规则来说,需要dockerd在启动的时候有两项配置--iptables=true--ip-masq=true,否则Docker不会在创建默认网桥的时候生成该规则,从而容器无法访问外部网络,但是Docker默认以上两个配置项默认均为true。但是不知为何导致容器启动时没有生效,从而出现了容器内部无法访问外部网络的情况。 到这里我们的问题已经定位,下面看新增配置是否可以生效。我们找到/etc/docker/daemon.json文件,添加如下参数。保存重启docker

json
{
	"mtu": 1450,
	"iptables": true,
	"ip-masq": true
}

再次查看iptables的规则

shell
[root@test01 ~]# iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 6670 packets, 456K bytes)
 pkts bytes target     prot opt in     out     source               destination    
    2   114 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0
   10   592 MASQUERADE  all  --  *      !1panel-network  192.168.100.0/24     0.0.0.0/0
    0     0 MASQUERADE  tcp  --  *      *       172.18.0.2           172.18.0.2           tcp dpt:3001
    0     0 MASQUERADE  tcp  --  *      *       192.168.100.4        192.168.100.4        tcp dpt:5700

我们可以看到数据正常了。再次进容器ping,网络也恢复正常。 docker的源码,我让豆包帮我大概读了下,找到了具体的实现代码供参考

3 总结

  1. dockerd首先根据配置文件和命令行参数获取--iptables--ip-masq的值
  2. 进行参数校验,校验后决定ip-masq是否启用
  3. dockerd初始化bridge网络的时候,先清理旧的iptables规则,然后依次添加新的iptables规则
  4. 如果启用ip-masq,那么创建POSTROUTING的规则。 如果大家想要详细理解docker网络,可参考下属参考文章。

4 参考文章

https://zhuanlan.zhihu.com/p/199298498https://zhuanlan.zhihu.com/p/206512720