理解 Docker 网络(番外) – 《Docker 源码分析》勘误
前言
本来打算这篇文章是分析 Docker Overlay 网络是如何建立以及如何手动实现 Docker 的跨主机通信的。但是在完成了上一篇文章之后,打算找一些文章或者书籍印证我的文章是否正确。这时看了一下案头的《Docker 源码分析》,翻到 Docker 容器网络部分,然后快速过了一遍,发现了有一段和我的观点不太一致。
有问题的部分是 95 页的一段话,摘录如下[1]:
5)宿主机将经过 SNAT 处理后的报文通过请求的目的IP地址(宿主机以外世界的IP地址)发送至外界。
在这里,很多人肯定要问:对于 Docker 容器内部主动发起的网络请求,请求到达宿主机进行 SNAT 处理发给外界之后,当外界响应请求时,响应报文中的目的IP地址肯定是 Docker Daemon 所在宿主机的 IP 地址,那响应报文回到宿主机的时候,宿主机又是如何转给 Docker 容器的呢?关于这样的响应,由于没有做相应的 DNAT 转换,原则上不会被发送到容器内部。为什么说对于这样的响应,不会做 DNAT 转换呢。原因很简单,DNAT 转换是针对特定容器内部服务监听的特定端口做的,该端口是供服务监听使用,而容器内部发起的请求报文中,源端口肯定不会占用服务监听的端口,故容器内部发起请求的响应不会在宿主机上经过 DNAT 处理。
其实,这一环节的关键在于 iptables,具体的 iptables 规则如下:
iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
…
所以这篇文章将会从逻辑,理论,实验来分析这段文字是否正确。
逻辑性和理论性分析
逻辑性分析
按照这段文字,如果我没有理解错的话,作者的意思是 Docker 容器通过 SNAT 向外发送请求,但是此时宿主机的 iptables 没有配置 DNAT 那么在不配置 DNAT 的情况下必须要通过 iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
来保证响应能够正确返回到 Docker 容器中。首先分析 iptables -I FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
,
此 iptables 规则的意思是:在宿主机发往 docker0 网桥的网络数据报文,如果该数据报文所处的连接已经建立,则无条件接受,并且由 Linux 内核转发到原来的连接上,即回到 Docker 容器内部。
这里的意思有些偏颇,但不影响逻辑分析。这里要知道关于路由转发的一些知识:数据包会发往 docker0 网桥的充分必要条件是路由表上由能将数据包正确转发到 docker0 网桥的项。有这些作为基础就可以开始逻辑性分析了:
假设条件:
- Docker 容器发往外网的请求响应地址是本机 ip 地址
- 本机没有配置 DNAT ,数据包目标地址没有被改写为容器 IP
- 通过设置 iptables 的 FORWARD 链,放行发往 docker0 网桥的数据包
结论:
- Docker 容器收到了数据包
如果按照没有 DNAT 就不会将目标地址改写为容器 IP 这个逻辑的话,数据包会直接进入到 INPUT 链而不是进入到 FORWARD 链[5],就算进入到 FORWARD 链数据包目标地址不是目标容器的 IP ,数据包也不会经 docker0 输出,此时 -o docker0
的 iptables 也不会生效,所以作者认为的关键 iptables 规则并没有生效,因而在这里作者的逻辑是不通的。
理论性分析
作者在这里对 SNAT, DNAT 和 iptables 的理论基础还不够扎实,至少在写作的时候还不