Docker一台服务器部署多容器,容器A无法通过宿主机外网ip访问另外一个容器B分析
首先出现这个问题,都是iptables ---input chain 设置了默认Policy 为 Drop导致的(如果默认全都是 accept或者没有其他拒绝策略,肯定是没问题的。)
这是我服务器上的INPUT规则如下:
3306 ,6379那几个, 分别是mysql容器和 redis容器对应的端口号,第一行表示,input默认策略是 Drop。
这台服务器的结构很简单,就是一张外网网卡, 直接接外网,eth0 , ip=101.*。*。*, 然后docker0网桥, 172.17.0.1
此时,我新建了一个nginx容器, 做了端口映射, 9999--》 容器里的80,
容器启动后, docker会自动在 Iptable添加相应的转发规则的,如果从别的服务器上, 访问 该容器服务器外网ip 101 : 9999, 其实是可以访问成功的。
我们可以拿iptabels 分析一下。
从外面进来的数据包, 会首先走iptables的prerouting链, 但是在这里, nat表里是有一条规则的,
可以看到第一个粗红框中 ,in out src dest 不是*就是0, 后面跟了一个 addrtype的模块,match dest = local,
首先前面四个,肯定命中,剩下的,就是匹配目标ip是不是本机了, 既然是直接访问宿主机的Ip,那就肯定 = local了,所以这条规则会命中, 从 prerouting链,jump到 docker链,即第二个粗红框
这里,我们依然要挨个检查,是否能命中规则(这里不太清楚,应该是执行完了,依然要返回调用方,即 prerouting)
第一条,in = docker0,肯定不对, 我们只有一个外网网卡接口 eth0, 所以忽略,第二条, in 命中, 不是docker0, src和 dest不管了, 命中, dport就不对了, 是6379, 我们访问的是9999,
一看, 最后一条命中了, targe是 dnat,即目标地址转换, 转换成 172.17.0.6,端口由 9999转成80.
然后返回到 prerouting里, 这里贴一张图,就很清晰的看到iptables都干了什么了。
刚才 ,从docker链判断完了, 做了目标地址转换后, 返回了 上面的那个紫框, nat-prerouting, 然后要继续往下执行, 判断是否是本机IP(这里本机ip有连个,1.外网ip101****, 第二个是 docker0网桥上的ip 172.17.0。1,由于刚才把ip换成了172.17.0.6了,所以判断是fale, 只能走forward链了, 不能走input了。(这也是为什么,最早的时候, 安装docker,一定要提前把Ip_foward打开 = 1),foward链中也有几个规则, 不过不影响大局,就不分析了。
这是一个正常的使用流程。
问题是, 我们再使用过程中 ,偶尔会出现,一个容器要访问另一个容器,如果直接使用容器ip,会不太方便,而公网ip大家都知道,一般人肯定会用公网ip去访问另一个容器, 这时候,如果Iptables不做更改,我们这里就出现了一个异常,
容器a访问 101.*。*。*:9999, 居然连接超时,无响应。
我们仔细想一下,容器a的包,正常出去, 通过容器a的路由表, 肯定只能发送给网桥那个172.17.0.1的ip, 通过tcpdump抓包,我们可以看到 宿主机 docker0网卡有数据进来(废话,容器的出包,也只能走这个网卡了)
这就很简单了,既然知道了是从docker0进来的,那就继续走一边iptables,就知道包到底被谁干掉了。
1. prerouting链, 是发给LOCAL的数据,命中,跳转到 docker链, 这个时候, 跟从外面来的包就不一样了,因为会命中 docker链 nat表的第一个规则 ,In 的确是 docker0, target = return,什么都没干,就返回到了prerouting, 然后判断host,发现是本机,不能欧foward链了,要走input,这个时候, input里的各种规则就起作用了, 咱们根本就没有添加相应的rule,(只有原来的那些22啊, 3306之类的,9999是没有的,所以就被默认drop了,导致无响应), 这么一分析,其实就清晰了, 所以如果要想让9999对本机的容器(记住一定是本机的容器(因为容器的src ip无法匹配local的,不然 本机给本机发包, 感觉input没拦截,这里没太弄懂,不知道到底本机ip1to本机ip2到底忽略了什么))也能联通, 只需要在input里增加一条 --dport 9999 -j accept的规则就好了 (而且source ip 可以为 172.17.0.0/24)这个段,不一定非要开全网的,因为 tcpdump抓的包明显能看出来,不可能是非容器段的Ip的。