docker宿主机iptables配置

4,322 阅读3分钟

背景

以前服务器都是直接配置LNMP环境,最近手头正好有一台需要重新配置,想尝试使用docker来配置多PHP版本环境。docker配置十分顺利,感谢有明大佬的小册,和DNMP项目

待解决

服务器配置好后,在几天的试用过程中,发现如下两个问题:

  • NGINX PHP容器中无法获取request的真实IP
  • PHP容器中无法访问公网

过程

无法访问公网,无法获取真实IP,首先想到了防火墙的问题;关闭iptables,问题解决。可是iptables不能关呀,虽说现在云服务器都有安全组过滤,但防火墙是最后一道防线,不能在云厂商的怀抱里裸奔啊……

查看了docker的网络部分,docker暴露容器端口是由docker-proxy来实现的,至于docker-proxy是什么略过不表。肯定是iptables影响了docker-proxy导致数据包的源ip发生了改变,无法获取真实IP;无法访问公网,则是数据包找不到出口,被iptables拦截掉了,在内网转圈圈

  • 既然docker的各容器处于内网中,于是我想到了iptables的转发功能。

解决

  • 假设我的公网IP为 117.25.140.71

  • NGINX容器内网IP为 172.18.0.2

  • PHP容器内网IP为 172.18.0.3 172.19.0.3

把公网来的数据包直接转发给NGINX容器,跳过docker-proxy

添加如下规则

-A PREROUTING -d 117.25.140.71 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.18.0.2:80
-A PREROUTING -d 117.25.140.71 -p tcp -m tcp --dport 443 -j DNAT --to-destination 172.18.0.2:443

重启iptables 无法获取真实IP问题解决

将内网的数据包转发到公网

我的PHP容器访问公网,需求大抵是发送短信之类(无邮件发送),用的都是http协议,所以这里我没有选择全部转发,而是只转发目的端口为80和443的数据包

添加如下规则

-A POSTROUTING -s 172.19.0.3 -p tcp -m tcp --dport 80 -j SNAT --to 117.25.140.71
-A POSTROUTING -s 172.19.0.3 -p tcp -m tcp --dport 443 -j SNAT --to 117.25.140.71

重启iptables发现仍然无法访问公网,是不是落下了什么。。 对了,既然http协议访问其他域名提供的api服务,怎么能少了DNS解析呢?添加DNS的支持(注意:DNS解析使用的是udp协议)

补充如下规则

-A POSTROUTING -s 172.19.0.3 -p udp -m udp --dport 53 -j SNAT --to 117.25.140.71

重启iptables,大功告成!

最后总结几点:

  • 之所以能用宿主机iptables实现转发,是因为宿主机是内网的网关
  • 我限制了PHP容器访问公网的目的端口,如果需要发送邮件或者访问8080等其他端口,仍需单独添加规则或者不限制具体端口号
  • 转发内网数据包到公网,不要使用地址伪装(MASQUERADE),否则会影响本文中添加的PREROUTING规则,仍然无法获取request的真实IP(使用其他方法除外)
  • 如果直接service iptables stop,这篇文章就不用看了。。。

##2020.3.31更新 docker19.03版本解决了这个问题,不再需要自己手动配置啦