上一篇文章中,我们介绍了pod与宿主机通信,并且通过network namespace模拟了通信过程。回顾整个流程,无非就涉及到两个东西,通信设备和路由规则。
本文要讲的,也离不开这两个东西,只不过需要对容器IP进行额外处理。
pod访问外网
在展开讲述之前,先明确一个问题,外网的范围是什么?
这个外网范围可大可小,可能是同一个局域网的另外一台主机,也可能是局域网外的一台网路设备或主机。
讲到这里,可能会混淆,是不是扯到了集群容器间通信,毕竟集群的容器也是分散在不同主机上的。
因此为了避免混淆,我们这里先对这些主机做两个约定
- 两个主机间可以通过物理网络通信。
- 仅能修改源主机配置
试想,当我们访问百度时,你肯定只是配置自己的主机,你会去配置百度的网关吗?
好,基于两个约定,展开本文要讨论的话题。
同样,我们还是先建模,网络拓扑图如下:
注:这里用network泛指中间的网路设备
如上图,host A和host B可以通过物理网络连接,并且为host A和host B分配了IP 196.128.1.2和196.128.2.2,并且通过CIDR地址可以看到,他们分别归宿与两个不同的网段。
同时当主机A和主机B通信时,host A先将报文将给默认网关196.128.1.1,默认网关将报文转发至network,最终该报文会被交给196.128.2.1,即host B的默认网关,然后转交给host B。
试想一下,如果主机A上有一个pod-1,其IP为10.10.1.2/24,它的报文可以发送给主机B吗?
当然可以!!!因为pod-1发送的报文的目标IP是host B的IP,这个IP是可以在network中路由的。
那为什么我们说他们之间不能通信呢?
那是因为pod-1能发送报文,但是却收不到响应报文。
那这个响应报文被谁丢了呢?
如果回答是host B的网关,那证明你的基础网络知识还是比较过关,没错这里就是被host B的默认网关把报文丢了。
我们来看看这个过程发生了什么
- pod-1发起IP报文,源IP为10.10.1.2,目标IP为196.128.2.2
- host A具备转发功能,将报文转发给自己的默认网关196.128.1.1
- host A默认网关将报文转发至network
- network最终将报文交付给196.128.2.1,即host B的默认网关
- host B默认网关将报文转交给host B
- host B协议栈处理完报文准备响应
- host B将IP调换,发送响应报文,此时,源IP为196.128.2.2,目标IP为10.10.1.2
- host B上无10.10.1.2的直接路由,于是将报文交给它的默认网关
- host B默认网关收到报文,查找路由信息,发现无任何匹配,丢弃该报文。
最终pod-1无法收到响应报文。
因此在整个过程中,无法发送响应报文的核心的问题是:
host B的网关以及整个network中的网络设备都不认识10.10.1.2
讲到这里,可能你已经想到办法了。
既然不认识,我们让它认识不就完了吗?
确实可以这样做,但是却存在以下两个问题
- 你需要配置整个网络中参与路由的所有设备
- 10.10.1.2是我们虚拟的ip,它在我们虚拟的子网10.10.1.0/24这个域内不会产生冲突,但是保不齐其他域中也存在10.10.1.2这个IP。
因为存在这个问题,所以这个方案非常复杂并且几乎不可能实现。
既然中间网络设备不让修改,那我们就只能从IP入手了.
值得庆幸的是,pod-1所在的host A的IP在整个网络中是可以路由的。
即上面流程中第8条,如果host B收到的报文的目标IP是host A的IP不就可以了吗?
那要如何修改IP呢?答案就是NAT
NAT
顾名思义,NAT就是网络地址转换,简单理解就是一种将私网地址转成公网地址的技术。
关于NAT技术的详情,不在这篇文章的讨论范围,感兴趣的可以自行查阅
既然有了NAT,我们将刚在的网络图改造以下,可以得到:
有了上一篇文章的基础,这张图看起来应该没有压力。
整个传输过程如下:
- host A中pod协议栈发起报文,源IP为10.10.1.2,目的IP为196.128.2.2
- 报文通过veth pair到达主机协议栈
- host A协议栈iptables中存在规则:源地址为10.10.1.2/24的报文需要做nat
- host A协议栈将原始报文的源IP替换为主机的IP地址,并记录该报文被snat过
- host A将报文从默认网关发出(图中已忽略)
- 报文经过网络传输,到达host B的默认网关(图中已忽略)
- host B的默认网关将报文转交给host B
- host B协议栈处理报文
- host B协议栈处理报文完毕将响应报文发送给默认网关
- host B默认网关根据规则将报文传输给下一跳
- 响应报文通过网络到达host A
- host A协议栈发现该报文应该做dnat(过程4中存有记录)
- host A将目标IP还原为10.10.1.2,并转发给veth 1
- host A中pod协议栈收到响应报文。
其它的流程都很熟悉,我们重点看看第3,4,12。
先看4和12,这里有snat和dnat,即将源地址转换和目标地址转换(上图IP首部)。至于内核转换细节,这里不讨论。
我们再看第3条,iptable规则,是一条什么样的规则完成了这个转换呢?
要回答这个问题,必须要了解iptable的工作原理,因为在后续的文章中,例如分析K8S service时我们会用到这个iptable,所以这里也不展开详细的讲述。
我们通过一个小实验来看看是不是和我们说的一样
实验
在这个实验中,我们仍然以network namespace代指pod
因为实验环境的主机IP已经配置了,所以下面实验中的IP可能会和上图中分配的IP对不上,不过不影响我们做整个实验
在实验中,我们用192.167.11.126代指上图中host A,用192.167.11.127代指上图中host B
首先我们在host A中创建一个network namespace,并完成相关配置工作,因为这部分在第一篇文章中已经有讲述,所以这里就不展开讲每一条命令的作用
# ip netns add pod-1# ip link add eth0 type veth peer name veth1# ip link set eth0 netns pod-1# ip netns exec pod-1 ip addr add 10.10.1.10 dev eth0# ip netns exec pod-1 ip link set dev eth0 up# ip netns exec pod-1 ip route add 169.254.1.1 dev eth0# ip netns exec pod-1 ip route add default via 169.254.1.1 dev eth0# ip link set dev veth1 up # echo 1 > /proc/sys/net/ipv4/conf/veth1/proxy_arp# echo 1 > /proc/sys/net/ipv4/ip_forward
此时我们尝试在pod-1中ping host B
# ip netns exec pod-1 ping -c 1 192.168.11.127
PING 192.168.11.127 (192.168.11.127) 56(84) bytes of data.--- 192.168.11.127 ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
不出意外,ping 不通。
我们在host B上抓包看看。
# tcpdump -n -i ens33 -p icmp
05:46:42.231478 IP 10.10.1.10 > 192.168.11.127: ICMP echo request, id 37196, seq 1, length 64
05:46:42.231505 IP 192.168.11.127 > 10.10.1.10: ICMP echo reply, id 37196, seq 1, length 64
可以看到host B是正确响应了报文的,和我们刚才的分析一致
此时,我们在主机上添加iptables规则
# iptables -A POSTROUTING -t nat -s 10.10.1.0/24 -j MASQUERADE
这个规则的意思是所有来自10.10.1.0/24这个网段的IP做一次snat
配置好规则之后,这时候就可以ping通了,我们再次尝试在host B抓包
# tcpdump -n -i ens33 -p icmp
07:08:51.945389 IP 192.168.11.126 > 192.168.11.127: ICMP echo request, id 59199, seq 1, length 64
07:08:51.945405 IP 192.168.11.127 > 192.168.11.126: ICMP echo reply, id 59199, seq 1, length 64
和刚才抓包的结果对比,你会发现,源IP不再是容器IP,而是host A的IP
如果你的宿主机能访问百度,你这时候在pod-1内访问百度,也是可以的
# ip netns exec pod-1 ping -c 1 www.baidu.com
PING www.a.shifen.com (183.2.172.185) 56(84) bytes of data.
64 bytes from 183.2.172.185 (183.2.172.185): icmp_seq=1 ttl=127 time=36.2 ms--- www.a.shifen.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 36.291/36.291/36.291/0.000 ms
总结
本文介绍了宿主内的容器,如何访问外网,严格来说,他不是容器组网的关键。因为它解决的不是两个容器相互通信,而是pod访问其他主机这个单点问题。
关于容器组网的内容,将在下一篇文章中分析。