Docker针对iptables的filter表的FORWARD链的默认设置

一、环境版本信息

Docker Community 18.09.9

  • Client Version: 18.09.9
  • Server Docker Engine Version: 18.09.9

二、问题现象

1. 在一台Linux服务器上,首先安装并启动了docker,接着设置了如下内核参数:

1
2
3
4
cat <<EOF > /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

这时发现了个有趣的现象,docker容器之间网络不通了。甚至当你使用了linux原生的两个network namespace通过veth pair设备连接在同一网桥上,然后为两个linux原生的两个network namespace中的设备配置同一网段的ip地址后,这两个linux原生的两个network namespace的网络也是不通的。

2. 在kubernetes环境中,发现当把service改为nodePort形式对外暴露时,无法通过nodeIP+nodePort的方式访问Pod内的服务。很有意思,这个问题有人装kubernetes就会遇到,有人就不会遇到。

三、排查过程

1. 如果开着 net.bridge.bridge-nf-call-iptables 这个参数,用tcmdump抓包,只能看到arp有请求和回复,icmp就只有请求没有回复,关掉就一切正常了;

注意:net.bridge.bridge-nf-call-iptables = 1 表示开启,net.bridge.bridge-nf-call-iptables = 0 表示关闭。

2. 查资料确定 net.bridge.bridge-nf-call-iptables 这个内核参数的含义,发现其含义用比较直白的话概括是这样的:网桥做流量转发时是否先转到iptables进行过滤;

关闭 net.bridge.bridge-nf-call-iptables 这个内核参数就意味着网桥把流量流量直接转发,不经过iptables,反之则先要经过iptables。

3. 既然是在iptables中出了问题,那么就查看iptables中filter表中的FORWARD链的Policy设置,发现其为DROP;

4. 这个不是我们要的设置,只能想办法手动覆盖了;

于是想到了改docker的systemd脚本docker.service,加入ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT。

5. 目前只是在外围临时解决了,但是并未探知到问题的本质,到底官方为什么要这么改?

一头雾水,于是继续通过github上的CHANGELOG查找线索,发现了如下线索:

1
2
3
4
5
6
## 1.13.0 (2017-01-18)
。。。
### Networking
。。。
* Change the default `FORWARD` policy to `DROP` [#28257](https://github.com/docker/docker/pull/28257)
。。。

通过阅读该CHANGELOG相关的PR和issues可以看到这个修改的来龙去脉。

6. 现在只知道默认会把 FORWARD 设置为 DROP,开始查找如何才能避免docker daemon做这个配置;

7. 阅读了与实验环境版本一致的docker源码,终于找到了问题的原因:net.ipv4.ip_forward 只要在docker daemon 启动之前手动把这个参数设置为1,docker daemon 默认就认为有人用iptables的filter表的forward链了,就不会更改forward链的默认策略了。

源码详见 “五、根本原因” 部分的截图。

四、解决方法

1. 外围的解决方法

我们自己可以在外围这么修改systemd的service脚本

1
2
3
4
5
6
vi /lib/systemd/system/docker.service
。。。
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT
。。。

其实主要是加最后那句 ExecStartPost=/usr/sbin/iptables -P FORWARD ACCEPT

2. 推荐的解决方法

先设置如下两个关键的内核参数并让其生效:

1
2
3
4
5
6
cat <<EOF > /etc/sysctl.d/docker.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

sysctl --system

之后再安装docker,然后启动即可。这里的关键是,安装或者首次启动docker前,linux操作系统需要设置内核参数 net.ipv4.ip_forward = 1 才可以。

五、根本原因(可以从对应版本的docker源码中找到线索)

docker_ipv4_forward
通过源码可以看出来,当内核参数 net.ipv4.ip_forward 的值不为1时,docker daemon会默认把iptables的filter表的FORWARD链默认设置为DROP。

六、参考资料

这个是为什么要这么改?因为从纯docker角度看,是个漏洞,详见链接。不过这个漏洞对我们来说影响意义不大。

https://github.com/moby/moby/issues/14041

这个是问题的PR描述:

https://github.com/moby/moby/pull/28257

Systemd的相关配置:

https://www.centosdoc.com/system/201.html