kuberberes CNI插件Flannel VXLAN模式详解
一:前言
Virtual Extensible LAN(虚拟可扩展局域网),是 Linux 内核本身就支持的一种网络虚似化技术。所以说,VXLAN 可以完全在内核态实现上述封装和解封装的工作。
VXLAN 的覆盖网络的设计思想是:在现有的三层网络之上,“覆盖”一层虚拟的、由内核 VXLAN 模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可以)之间,可以像在同一个局域网(LAN)里那样自由通信。当然,实际上,这些“主机”可能分布在不同的宿主机上,甚至是分布在不同的物理机房里。
而为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。这个设备就叫作 VTEP,即:VXLAN Tunnel End Point(虚拟隧道端点)。
而 VTEP 设备的作用,是进行二层数据帧(Ethernet frame)的封装和解封装,而且这个工作的执行流程,全部是在内核里完成的(因为 VXLAN 本身就是 Linux 内核中的一个模块)。
二:图解
基于 VTEP 设备进行“隧道”通信的流程,如下所示:
图中每台宿主机上名叫 flannel.1 的设备,就是 VXLAN 所需的 VTEP 设备,它既有 IP 地址,也有 MAC 地址。
container-1的IP地址为10.244.1.2,container-3的IP地址为10.244.2.2,现在让container-1访问container-3,container-1发出请求后,数据包来到cni0网桥上,cni0网桥上没有到10.244.2.0网段的路由,然后交给默认路由路由,就来到隧道入口(flannel.1)设备。为了方便叙述,接下来把这个IP包成为原始IP包。
为了能够将“原始 IP 包”封装并且发送到正确的宿主机,VXLAN 就需要找到这条“隧道”的出口,即:目的宿主机的 VTEP 设备。而这个设备的信息,正是每台宿主机上的 flanneld 进程负责维护的。比如,当 Node 2 启动并加入 Flannel 网络之后,在 Node 1(以及所有其他节点)上,flanneld 就会添加多条如下所示的路由规则:
主要看第三四五条路由规则,通往10.244.0.0/24(Master节点)或10.244.2.0/24(Node2节点)网段的数据包交给flannel.1设备处理。通往10.244.1.0/24(Node1本机)的数据包交给cni0设备。每添加一个节点,Flannel进程就会在其余节点上添加一条路由解析,这里注意Gateway地址是对应VTEP的IP地址。
到目前为止,只知道目的宿主机VTEP的IP地址,我们还需要知道宿主机VTEP的MAC地址,因为VXLAN是通过二层网络进行通信的,就是通过MAC地址进行通信,有目的IP地址,通过三层IP地址寻找对应二层MAC地址,是ARP表的功能,而ARP记录,也是由flannel进程启动时添加到其余节点上的,通过查询所得到Node2上VTEP设备flannel.1的MAC地址为:ea:b1:b9:90:cf:67
有了这个“目的 VTEP 设备”的 MAC 地址,Linux 内核就可以开始二层封包工作了。这个二层帧的格式,如下所示:
可以看到,Linux 内核会把“目的 VTEP 设备”的 MAC 地址,填写在图中的 Inner Ethernet Header 字段,得到一个二层数据帧。需要注意的是,上述封包过程只是加一个二层头,不会改变“原始 IP 包”的内容。所以图中的 Inner IP Header 字段,依然是 container-3 的 IP 地址,即 10.244.2.2。
但是,上面提到的这些 VTEP 设备的 MAC 地址,对于宿主机网络来说并没有什么实际意义。所以上面封装出来的这个数据帧,并不能在我们的宿主机二层网络里传输。为了方便叙述,我们把它称为“内部数据帧”(Inner Ethernet Frame)。所以接下来,Linux 内核还需要再把“内部数据帧”进一步封装成为宿主机网络里的一个普通的数据帧,好让它“载着”“内部数据帧”,通过宿主机的 eth0 网卡进行传输。
我们把这次要封装出来的、宿主机对应的数据帧称为“外部数据帧”(Outer Ethernet Frame)。为了实现这个“搭便车”的机制,Linux 内核会在“内部数据帧”前面,加上一个特殊的 VXLAN 头,用来表示这个“乘客”实际上是一个 VXLAN 要使用的数据帧。而这个 VXLAN 头里有一个重要的标志叫作 VNI,它是 VTEP 设备识别某个数据帧是不是应该归自己处理的重要标识。而在 Flannel 中,VNI 的默认值是 1,这也是为何,宿主机上的 VTEP 设备都叫作 flannel.1 的原因,这里的“1”,其实就是 VNI 的值。
然后,Linux 内核会把这个数据帧封装进一个 UDP 包里发出去。在宿主机看来,它会以为自己的 flannel.1 设备只是在向另外一台宿主机的 flannel.1 设备,发起了一次普通的 UDP 链接。
不过一个 flannel.1 设备只知道另一端的 flannel.1 设备的 MAC 地址,却不知道对应的宿主机地址是什么,也就是UDP应该发给哪台宿主机呢?
在这种场景下,flannel.1 设备实际上要扮演一个“网桥”的角色,在二层网络进行 UDP 包的转发。而在 Linux 内核里面,“网桥”设备进行转发的依据,来自于一个叫作 FDB(Forwarding Database)的转发数据库。flannel.1对应的FDB信息也是由Flannel进行维护的,使用“目的VTEP设备”的MAC地址进行查询:
上面的FDB记录说明了发往我们前面提到的“目的 VTEP 设备”(MAC 地址是 ea:b1:b9:90:cf:67)的二层数据帧,应该通过 flannel.1 设备,发往 IP 地址为 10.206.0.3 的主机。显然,这台主机正是 Node 2,UDP 包要发往的目的地就找到了,所以接下来的流程,就是一个正常的、宿主机网络上的封包工作。
我们知道,UDP 包是一个四层数据包,所以 Linux 内核会在它前面加上一个 IP 头,即原理图中的 Outer IP Header,组成一个 IP 包。并且,在这个 IP 头里,会填上前面通过 FDB 查询出来的目的主机的 IP 地址,即 Node 2 的 IP 地址 10.206.0.3。然后,Linux 内核再在这个 IP 包前面加上二层数据帧头,即原理图中的 Outer Ethernet Header,并把 Node 2 的 MAC 地址填进去。这个 MAC 地址本身,是 Node 1 的 ARP 表要学习的内容,无需 Flannel 维护。这时候,我们封装出来的“外部数据帧”的格式,如下所示:
这样,封包工作就宣告完成了。接下来,Node 1 上的 flannel.1 设备就可以把这个数据帧从 Node 1 的 eth0 网卡发出去。显然,这个帧会经过宿主机网络来到 Node 2 的 eth0 网卡。这时候,Node 2 的内核网络栈会发现这个数据帧里有 VXLAN Header,并且 VNI=1。所以 Linux 内核会对它进行拆包,拿到里面的内部数据帧,然后根据 VNI 的值,把它交给 Node 2 上的 flannel.1 设备。而 flannel.1 设备则会进一步拆包,取出“原始 IP 包”。最终,IP 包就进入到了 container-2 容器的 Network Namespace 里。
本文来自博客园,作者:鲜小橙,转载请注明原文链接:https://www.cnblogs.com/big-cousin/p/18185316