前言

本文主要会介绍笔者在学习Flannel时所总结的知识点,其中会涉及到Flannel的架构、各种Backend的实现原理等方面的相关内容。 笔者也会将自己的理解在文中进行阐述,这也算是在和大家交流心得的一个过程。若文中有错误的理解和概念,请大家及时纠正;吸纳大家的建议,对于我来说也是很重要的学习过程之一。


(目录)


1.概念

Flannel是CoreOS开发的容器网络解决方案。

在Flannel中,每一个host都会被分配一个子网subnet。之后在host上运行的容器会从该子网subnet中分配到一个IP。这些IP是可以在host之间路由通信。即Flannel无需NAT和端口映射就可以实现跨主机通信。Flannel本身只是一个框架,真正提供容器网络功能的是Flannel的Backend。


2.架构

Flannel会在每一个host运行一个flanneld,其职责是负责为host分配一个子网subnet。

在Flannel中实际上每个host上的子网都是从一个总的IP池划分出来的;而这个总IP池是在etcd中定义的。flannel还会将其他的网络配置、已分配的子网、host的IP等信息都会保存在etcd中。

Tips: Flannel使用etcd作为了其元数据的存储。

同时,随着不同的后端实现方式,Flannel中还会引入一些其他的网络设备/组件。


3.Backend

目前Flannel支持三种Backend:UDP、VXLAN和host-gw。其中,UDP与VXLAN属于Overlay类型的容器网络,而host-gw为Underlay类型的容器网络。

3.1 UDP Backend

UDP模式是Flannel最早支持的一种方式,但因为性能最差所以已被弃用。之所以会选择在Overlay层使用UDP协议来进行传输,是因为连接的稳定性是由UDP封装的内部网络栈来解决的,而外部的UDP一层只需要提供传输功能即可。所以最终选择了快速但不可靠的UDP协议。

8332564c0547bf46d1fbba2a1e0e166c.jpg.webp

Tips: 虽然UDP模式已经被弃用,但笔者仍然还是选择介绍一下的原因在于:笔者认为这种模式的实现中有一些设计理念/原则是很好的。这些理念/原则可以帮助我们开拓思路以及更加细致的了解Linux系统内的一些核心机制。

3.1.1 flannel0

flannel0设备是一个 TUN 设备(Tunnel 设备)。

Tips: 在 Linux 中,TUN 设备是一种工作在三层(Network Layer)的虚拟网络设备。TUN设备负责在操作系统内核和用户应用程序之间传递IP包。当操作系统将一个IP包发送给TUN设备之后,TUN就会把这个IP包交给创建这个设备的应用程序/进程。

container-1.webp

在该模式中,TUN设备负责flanneld进程和docker0之间的IP包传递。 该模式中有很多处位置都需要做用户态和系统态之间的IP包传递。用户态的容器进程发出的 IP 包经过 docker0 网桥进入内核态也是一次传递过程。仅在发出IP包的过程中就需要经过三次用户态与内核态之间的数据拷贝。大量用户态和内核态的切换会导致网络传输的性能会下降很多

Tips: 在进行系统级编程的时候,有一个非常重要的优化原则就是要减少用户态到内核态的切换次数,并且把核心的处理逻辑都放在内核态进行

3.1.2 路由

容器发送给docker0数据包会转发到flannel0上。这是因为Flannel在每台host上都创建出了一系列的路由规则。这些路由描述的是“去往Flannel子网的包都会转发到flannel0”

Tips: 在内核网络栈中处理IP包的方法一般是利用系统路由或是iptables规则。

3.1.3 flanneld

3.1.3.1 接收数据包

flanneld收到的数据包都是经过flannel0传递过来的

3.1.3.2 封装数据包

flanneld在收到数据包之后会把这个IP包直接封装在一个UDP包里并发送出去。

该UDP包的源地址是flanneld所在的host地址,而目的地址则是目标容器所在的host地址。目标容器所在的host地址是通过查询etcd中所保存的数据而得知的。由于每台v上的flanneld都会监听一个端口,因此UDP包所发送和接收端口也都是固定的。即在用户态网络栈中处理IP包往往都是封装、解析和提取相关数据内容,而转发依旧是交给了内核态的网络栈来处理

Tips: Calico的IPIP模式中也是使用的这种“将一个网络域的IP包作为数据部分封装到另一个网络域的IP包中从而可以在其他网络域中传递”的做法。

3.1.3.3 解析数据包

目标容器所在host上运行的flanneld接收到UDP包后会解析出封装在里面原IP包,并将原IP包转发给flannel0设备。

3.1.4 子网

在由Flannel管理的容器网络里,一台host上的所有容器都属于该host被分配的一个“子网”。子网与host的对应关系都保存在 Etcd 当中。flanneld进程在处理由 flannel0 传入的 IP 包时,就可以根据目的 IP 的地址从 Etcd 中找到这个子网对应的host的 IP 地址。

3.2 VXLAN Backend

Virtual eXtensible Local Area Network(虚拟可扩展局域网)是Linux内核本身就支持的一种网络虚似化技术。VXLAN可以完全在内核态实现上述封装和解封装的工作,因此VXLAN Backend是一种Overlay类型的容器网络解决方案

Tips: VXLAN是基于二层的通信技术,因此对于mac地址的获取是需要解决的核心问题。

3.2.1 设计思想

在现有的三层网络之上“覆盖”一层虚拟的、由Linux内核VXLAN模块负责维护的二层网络,使得连接在这个 VXLAN 二层网络上的“主机”(虚拟机或者容器都可以)之间可以像在同一个局域网(LAN)里那样自由通信。这些“主机”可能分布在不同的宿主机上,甚至是分布在不同的物理机房里。

Tips: 相当于是Flannel利用VXLAN在每一个host之间建立隧道,使不同host中的容器都连接在一个大的子网中。

与UDP模式不同的是,该方案数据包流转过程中没有引入属于用户态的flanneld进程。因此所有的数据包封装解封都交给了内核态的vxlan模块

Tips: 数据包流转过程中没有引入flanneld,但并不代表该方案中无需使用flanneld。flanneld进程还是会协助创建一些路由规则以及管理的功能。

03185fab251a833fef7ed6665d5049f5.jpg.webp

数据包会在flannel.1处被封装成VXLAN并进行转发

Tips: 如果读者对VXLAN了解的不够充分,则可能在阅读后续几个小章节时会有困难。笔者这里给出一个阅读建议:事先将VXLAN数据报文结构理解透彻。即上图里中间部分的数据报文结构图。当搞清楚报文结构中每一部分的含义后,自然而然的就会想到如何去获得这些结构中对应的数据,而这也就是Flannel后续要做的事情了。

3.2.2 路由维护

flanneld会新增一条路由,其内容是使得去往flannel创建的网络的数据包都通过flannel.1进行转发。容器发出的数据包会根据一条路由条目被转发到docker0,之后数据包会按照flanneld所添加的路由条目内容,即被转发到本机的flannel.1 设备进行处理。

其次,flanneld还会在所有node上添加上到所有flannel子网的路由条目。其中,路由条目中的下一跳是目标所在node上的flannel.1设备的IP,而出口设备为当前node的flannel.1设备。

3.2.3 flannel.1

为了能够在二层网络上打通“隧道”,VXLAN 会在宿主机上设置一个特殊的网络设备作为“隧道”的两端。即:VTEP(VXLAN Tunnel End Point),虚拟隧道端点。VTEP 设备的作用是进行封装/解封数据包,封装和解封装的对象是二层数据帧(Ethernet frame)。这个工作的执行流程全部是在内核里完成的,因为VXLAN本身就是 Linux 内核中的一个模块。

在VXLAN Backend中,flannel.1实际上就是一个VTEP设备

3.2.3.1 接收数据包

flannel.1 接收来自docker0的数据包。 即同一host内的容器通过docker0连通,跨主机流量通过flannel.1来转发

3.2.3.2 获取目的VTEP设备的mac地址

在VXLAN中,隧道两端的VTEP的mac地址会用于封装数据帧,即作为最内层数据帧的目的mac地址和源mac地址。因此需要先获取本机flannel.1设备的mac地址以及目的flannel.1设备的mac地址。

老版本的flannel中,是基于ARP协议获取到目的VTEP设备的mac地址。新版本的flannel中的ARP的记录是由flanneld进程在每台node启动时自动添加到各个node上的

基于上述原理,发送端使用目的VTEP设备的IP地址在ARP记录中就可查找到目的VTEP设备的mac地址了。

3.2.3.3 获取目的宿主机的IP地址

在VXLAN中,隧道两端对应的宿主机IP地址会用于封装传输在真实物理网络中的数据帧中;即作为最外层数据帧中的目的IP地址和源IP地址。

flannel.1设备实际上是在二层网络进行UDP包的转发。在Linux内核中,二层网络转发设备(例如网桥)进行转发的依据是来自于FDB(Forwarding Database),即转发数据库。 在FDB中能够依据转发目标的mac地址查找到要转发目标的IP地址。

Tips: flannel.1对应的FDB信息是由flanneld进程负责维护的。

因此利用章节3.2.3.2中获取到的目的VTEP设备的mac地址在Bridge FDB list中就可以查到了目的宿主机IP地址了。

3.2.3.4 封装/解封数据帧

Linux 内核会把目的VTEP设备的mac地址填写在Inner Ethernet Header 字段得到一个二层数据帧。

目的VTEP设备的MAC地址.webp

注意: 这一步封装出的数据帧还不能够在真实的网络/宿主机网络中进行传输。

为了能够让数据帧能够在真实的二层网络中传输,Linux内核还需要再把内部数据帧进一步封装成为能够在宿主机网络里的一个普通的数据帧

8cede8f74a57617494027ba137383f85.jpg.webp

这里的进一步封装细节如下:

  1. 添加VXLAN Header Linux内核会加上一个特殊的VXLAN Header用来表示这是一个 VXLAN 要使用的数据帧。VXLAN Header里有一个重要的标志叫作VNI,它是VTEP设备识别某个数据帧是不是应该归自己处理的重要标识。

    Tips: 在Flannel中,VNI的默认值是 1。所以宿主机上的 VTEP 设备都叫作 flannel.1 。

  2. 封装UDP数据帧 Linux内核会把这个数据帧封装进一个UDP数据帧里发出去。在VXLAN网络中,两个flannel.1设备之间是使用UDP协议通信的,即最外层网络包中使用的4层协议使用的是UDP协议。

  3. 封装最外层数据帧 根据章节3.2.3.3中所获取到的目的宿主机的IP地址,在宿主机的ARP表中查找到目的宿主机的mac地址。最后将这些数据按照网络报文的结构添加到相应位置即可组成可在真实网络上传输的数据帧。

同理,在目的宿主机上接收到该数据包后,目的宿主机的内核网络栈会发现这个数据帧里有 VXLAN Header且 VNI=1;因此Linux内核会对它进行拆包并获得里面的内部数据帧。然后根据 VNI 的值,把它交给目的宿主机上的 flannel.1 设备。而flannel.1设备则会进一步拆包,取出内部数据帧中的容器网络中的IP包。最终,IP包就通过docker0进入到了目标容器的Network Namespace中了。

3.3 host-gw Backend

host-gw Backend中不会封装数据包,而是在host的路由表中创建到其他host上Flannel子网的路由从而实现跨主机通信。host-gw模式的工作原理就是将每个 Flannel 子网(Flannel Subnet)的“下一跳”设置成了该子网对应的宿主机的 IP 地址

Tips: 相当于是将每一个host充当这条容器通信路径里的“网关”(Gateway)。这也正是“host-gw”的含义。

因此host-gw是一种三层容器网络方案,即是一种Underlay类型的容器网络解决方案

1024402.webp

3.3.1 路由维护

Flannel子网和主机的信息都是保存在Etcd中的。flanneld只需要WACTH这些数据的变化,然后实时更新路由表即可

Tips: 在 Kubernetes v1.7 之后,类似 Flannel、Calico的CNI网络插件都是可以直接连接 Kubernetes 的 APIServer 来访问 Etcd 的,无需额外部署 Etcd 给它们使用了

3.3.2 环境要求

host-gw Backend能够正常工作的核心就在于IP包在封装成帧发送出去的时会使用路由表里的“下一跳”来设置目的 MAC地址,之后就会经过二层网络到达目的宿主机。 因此,Flannel host-gw Backend必须要求集群宿主机之间是二层连通的

3.3.3 性能

在这种模式下,容器通信的过程就免除了额外的封包和解包带来的性能损耗。根据实际的测试,host-gw 的性能损失大约在 10% 左右,而其他所有基于 VXLAN“隧道”机制的网络方案,性能损失都在 20%~30% 左右。


4.网络隔离

因为使用了flannel后,所有的容器实际上都是在一个大的子网中,因此flannel提供不了相关的网络隔离功能。


5.外网连通

因为flannel使用了docker0,因此所有容器访问外网的方式与docker相同。即通过docker0的NAT来访问外网;通过host端口映射可使的外网访问容器内部。


6.域名解析

flannel不提供DNS服务,因此需要自行解决。