当从Pod访问百度时会用到VTEP吗
编辑搜图
大家好,我是二哥。
一个公众号关注者私信问我一个问题:从 Pod 内发起的,向外网的访问过程会涉及到 VTEP 吗?涉及到的 NAT 细节是什么?
我大概整理了一下,写成此文。
写公众号大概有半年左右的时间了,写一段小感想:于我,每篇文章的选题和文章所涉及知识点的汇编、学习、整理、成文都是一个历练过程;于你,是一个恰好数分钟的的短暂停留;于我们,是一个交流的纽带。利是断然没有的,虚名会有一丁点,但那绝非我初衷。
相关话题
在文章《tun设备的妙用-Flannel UDP模式篇》中,二哥借助 tun 设备,画了几张高清大图和老铁们详细聊了下 Pod 间通信时,数据流向是什么样子的,以及在这个数据流淌过程中涉及到了哪些网络设备,这些设备又是如何在各自的位置上尽心尽责地处理、搬移每一份网络包的。
那篇文章涉及到的是 Pod 间网络通信的场景。在 K8s Overlay 网络模型下,这个过程不需要对进、出 Pod 的 traffic 进行任何的 NAT。在 VXLAN 这个神器加持下,宿主机网络根本不关心、也看不到 Pod traffic 的细节,因为 traffic 都被封装起来了。
但当 Pod 想访问外网,比如 www.baidu.com 的时候,就是另外一个场景了。一是 VXLAN 在这里帮不上忙,VXLAN 是在通信双方所涉及到的 work node 上利用各自的 VTEP 分别进行封包和解包,很显然外网 remote service 不可能有这个对等的 VTEP。二是也没有必要使用 VXLAN 。
其实大家多多少少能大概猜得出来或隐约觉得这里面涉及到了 NAT 。可架不住细问,比如:
-
一定需要NAT吗?
-
假如要NAT的话,从 Pod a 看来,既然 NAT 涉及到对去程的网络包进行 IP 或 Port 的修改,那它势必需要记得这些连接在被修改过之前的信息,这样才能在网络包回来的时候再修改一番。那这些连接信息记录在那里呢?
-
NAT 这个过程到底发生在哪里呢?Pod 里还是宿主机上?具体是网络包流经到哪个位置发生了 NAT 呢?
先回答第一个问题:当 traffic 离开宿主机的时候不一定需要NAT。这取决于 K8s 所使用的网络模型。比如在 Underlay 模型下,就完全没有必要用到NAT, 具体内容详见二哥在《多图汇总梳理VPC与三种K8s网络模型》中的总结。但在 Overlay 模型下,NAT就很有必要了,所以本文的场景限定于 Overlay 模型。
另外,本文所讨论的 NAT 只会到 work node 这一级。我们知道从网络包离开 work node 到最终达到百度服务器,中间可能会经过若干次 NAT ,那些我们就管不着了。不过二哥在《广角-聊聊Underlay》这篇文章里,画出了数据中心网络拓扑高清大图,如果你感兴趣的话,可以打开看看。
来吧,进入正题。
Netfilter conntrack
照例,我们先从 NAT 所涉及到的基础知识开始。
Netfilter conntrack 又名 CT ,译作连接跟踪。它是一个内核模块(nf_conntrack),用于维护可跟踪协议(trackable protocols)的连接状态。目前只支持以下六种协议:TCP、UDP、ICMP、DCCP、SCTP、GRE。
既然说跟踪,得有一个或一组标记可供记录、查找、回溯。在这里 CT 利用到了每个网络包里面的源 IP ,目的 IP ,源 Port ,目的 Port 和 Protocol 这5个参数所组成的一组标记。更简单地说,CT 认为这5个参数可用来唯一标识一个单向连接。注意,这里说的是单向连接。网络通信涉及到去和回两个单向连接。
你可以用命令 conntrack -L 查看本机的 CT 记录。如下表所示,第一条记录了去、回两个方向的记录。每一个这样的记录叫做:connection tracking entry (conntrack entry)。
# conntrack -Ludp 17 172 src=127.0.0.1 dst=127.0.0.53 sport=59837 dport=53 src=127.0.0.53 dst=127.0.0.1 sport=53 dport=59837 [ASSURED] mark=0 use=1tcp 6 5 TIME_WAIT src=127.0.0.1 dst=127.0.0.1 sport=35074 dport=2379 src=127.0.0.1 dst=127.0.0.1 sport=2379 dport=35074 [ASSURED] mark=0 use=11.2.3.
看完了一条 conntrack entry 长什么样子,我们再来看下记录是在哪里发生的。下图中 routing + iptables of root namespace 里面,你会看到一共两处有椭圆形所标记的 conntrack 。一处是在 PREROUTING 链附近,一处是在 OUTPUT 链附近。
为什么是这两个 hook 点创建连接跟踪记录呢?因为它们都是新连接的第一个包最先达到的地方:
-
PRE_ROUTING 是外部包或者源自本机其它 network namepsace 的包最先到达的地方。从 eth0 进来的包为外部包,而从 1.5 处 cni0 bridge 进来的则为本机上其它 network namepsace 的包。
-
LOCAL_OUT 是本机通过 root network namepsace 主动与对方通信时,网络包最先到达的地方。这里的“对方”既可以是外部服务,也可以是位于本机上的,但使用的是其它 network namepsace 的进程。
当然,在LOCAL_IN和POSTROUTING处还会执行一次 confirm 操作,以确保这样一件事情:刚才所说的这个新创建的 conntrack entry ,它对应的是一条新的单向连接,这个连接的第一个包经过各种处理,一直到这里仍然健在、没有被丢弃。这并非本文重点,略过不聊。
没有特殊情况,我们可以粗略地认为前文所提 conntrack entry 在 PREROUTING 和 OUTPUT 链被成功地记录下来了,以备后用。
编辑搜图
图 1:从 Pod a 访问外网时的数据流
NAT
连接跟踪是许多网络应用的基础,例如 Kubernetes Service、ServiceMesh sidecar、 软件四层负载均衡器 LVS/IPVS、Docker network、OVS、iptables 主机防火墙等等都依赖连接跟踪功能。NAT更是离不开 CT 。
我们结合上图来看看当 Pod a 访问百度的时候,网络包流经协议栈以及在 routing table 和 iptables 共同作用下,具体发生了什么。
www.baidu.com IP 地址是 180.101.49.11 , 我们的 Pod a IP 地址为 10.244.0.2 。
1.1:从Pod a发起的请求会从 1.1 一路来到 1.5 的位置。二哥在《tun设备的妙用-Flannel UDP模式篇》里面已细聊过细节,这里就跳过去了。
1.6:当bridge将网络包递交到网络层后,网络包在 1.6 处便开始了它在 root network namespace 的新旅程。与之对应的是从 1.1 ~ 1.4 网络包还依旧只是在 Pod a 自己的 network namespace 里面转悠。在 1.5 处,网络包从一个 network namespace 跳转到了另外一个。
1.7:如前一节所述,这里的 conntrack 会记录新的连接。
1.8:经过路由选择,网络包需要从宿主机的 eth0 离开。于是走 FORWARD 链,来到 POSTROUTING 链。
1.9:现在来到了 NAT 现场。结合下面的 iptables dump,我们可以清楚地看到当源IP位于 CIDR 10.244.0.0/16 ,且不是去 docker0 这个interface ,则会进行 NAT 。将Pod a的源 IP 地址修改成了 Node 1 的 IP 地址。
1.10:开始将网络包送到链路层。
1.11:网络包从 eth0 离开宿主机。
-A POSTROUTING -s 10.244.0.0/16 ! -o docker0 -j MASQUERADE1.
再看问题
看完 Pod 访问百度时,CT 和 NAT 的介入细节,我们再来梳理一下这位同学的问题:为什么 Pod a 访问百度时 VTEP 不会介入?
图2是文章《tun设备的妙用-Flannel UDP模式篇》的配图。它详细地标出了从 Node 1 上的 Pod a 访问 Node X 的 Pod b 时的数据流。为了方便理解,我们将 Flannel VXLAN 模式所用的 VTEP 换成了 UDP 模式所用的 tun 设备 + flannel daemon 组合。这样的更换只是把原本位于内核态的数据解、封装动作挪动到用户态的 daemon 里面来完成了,换汤不换药。
当请求从 Pod a 发起,无论是访问外网还是访问另一个 Pod,图1和图2中 1.1 ~ 1.5 这个流程是不变的。
当网络包从 1.5 处进入网络层后,网络包的去向发生了变化:
-
如果是 Pod 间通信,网络包会经由图2中的 1.7 流动到 1.9,再完成后续的数据封包过程。
-
与之相比,当 Pod 访问百度时,网络包在图1中 1.9 处经过 NAT 处理后,直接从 1.11 处离开了宿主机,不需要任何的封包过程,也就不需要VTEP的介入。
编辑搜图
图 2:Pod间通信时的数据流
我有话说: