技术干货|深刻理解flannel

根据官网的描述,flannel是一个专为kubernetes定制的三层网络解决方案,主要用于解决容器的跨主机通讯问题。正则表达式

1.概况



首先,flannel利用Kubernetes API或者etcd用于存储整个集群的网络配置,其中最主要的内容为设置集群的网络地址空间。例如,设定整个集群内全部容器的IP都取自网段“10.1.0.0/16”。docker

接着,flannel在每一个主机中运行flanneld做为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内全部容器的IP地址都将从中分配。后端

而后,flanneld再将本主机获取的subnet以及用于主机间通讯的Public IP,一样经过kubernetes API或者etcd存储起来。数组

最后,flannel利用各类backend mechanism,例如udp,vxlan等等,跨主机转发容器间的网络流量,完成容器间的跨主机通讯。网络

 
1.1容器间的跨主机通讯如何运行

以下图所示,集群范围内的网络地址空间为10.1.0.0/16,Machine A获取的subnet为10.1.15.0/24,且其中的两个容器IP分别为10.1.15.2/24和10.1.15.3/24,二者都在10.1.15.0/24这一子网范围内,对于下方的Machine B同理。若是上方Machine A中IP地址为10.1.15.2/24的容器要与下方Machine B中IP地址为10.1.16.2/24的容器进行通讯,封包是如何进行转发的。从上文可知,每一个主机的flanneld会将本身与所获取subnet的关联信息存入etcd中,例如,subnet 10.1.15.0/24所在主机可经过IP 192.168.0.100访问,subnet 10.1.16.0/24可经过IP 192.168.0.200访问。反之,每台主机上的flanneld经过监听etcd,也可以知道其余的subnet与哪些主机相关联。如上图,Machine A上的flanneld经过监听etcd已经知道subnet 10.1.16.0/24所在的主机能够经过Public 192.168.0.200访问,并且熟悉docker桥接模式的同窗确定知道,目的地址为10.1.16.2/24的封包一旦到达Machine B,就能经过cni0网桥转发到相应的pod,从而达到跨宿主机通讯的目的。socket

所以,flanneld只要想办法将封包从Machine A转发到Machine B就OK了,而上文中的backend就是用于完成这一任务。不过,达到这个目的的方法是多种多样的,因此咱们也就有了不少种backend. 在这里咱们举例介绍的是最简单的一种方式hostgw : 由于Machine A和Machine B处于同一个子网内,它们本来就能直接互相访问。所以最简单的方法是:在Machine A中的容器要访问Machine B的容器时,咱们能够将Machine B当作是网关,当有封包的目的地址在subnet 10.1.16.0/24范围内时,就将其直接转发至B便可。而这经过图中那条红色标记的路由就能完成,对于Machine B同理可得。由此,在知足仍有subnet能够分配的条件下,咱们能够将上述方法扩展到任意数目位于同一子网内的主机。而任意主机若是想要访问主机X中subnet为S的容器,只要在本主机上添加一条目的地址为R,网关为X的路由便可。ide

下面,咱们以问题驱动的方式来详细分析flannel是如何运做的。函数

 
2.节点初始化



首先,咱们最感兴趣的是,当一个新的节点加入集群时,它是如何初始化的。对此,咱们可能会有如下几个疑问:ui

若主机有多张网卡和多个IP,如何选择其中的一张网卡和一个IP用于集群主机间的通讯?主机如何获取属于本身的subnet并维护?spa

咱们如何在集群中有新的节点加入时,获取对应的subnet和Public IP,并经过配置backend进行访问?

 
2.1网卡及对外IP选择

对于第一个问题,事实上咱们能够在flanneld的启动参数中经过”–iface”或者”–iface-regex”进行指定。其中”–iface”的内容能够是完整的网卡名或IP地址,而”–iface-regex”则是用正则表达式表示的网卡名或IP地址,而且两个参数都能指定多个实例。flannel将以以下的优先级顺序来选取:

1) 若是”–iface”和”—-iface-regex”都未指定时,则直接选取默认路由所使用的输出网卡

2) 若是”–iface”参数不为空,则依次遍历其中的各个实例,直到找到和该网卡名或IP匹配的实例为止

3) 若是”–iface-regex”参数不为空,操做方式和2)相同,惟一不一样的是使用正则表达式去匹配

最后,对于集群间交互的Public IP,咱们一样能够经过启动参数”–public-ip”进行指定。不然,将使用上文中获取的网卡的IP做为Public IP。

 
2.2获取subnet

在获取subnet以前,咱们首先要建立一个SubnetManager,它在具体的代码实现中,表现为一个接口,以下所示:

从接口中各个函数的名字,咱们大概就能猜出SubnetManager的做用是什么了。可是,为何获取subnet的函数叫AcquireLease,而不叫AcquireSubnet呢?实际上,每台主机都是租借了一个subnet,若是到了必定时间不进行更新,那么该subnet就会过时从而从新分配给其余的主机,即主机和subnet的关联信息会从etcd中消失(在本文中咱们将默认选择etcd做为SubnetManager的后端存储)。所以,lease就是一条subnet和所属主机的关联信息,而且具备时效性,须要按期更新。

下面咱们来看看,每台主机都是如何获取lease的:

1) 首先,咱们调用GetNetworkConfig(),它会访问etcd获取集群网络配置并封装在结构Config中返回,Config结构以下所示。其中的Network字段对应的集群网络地址空间是在flannel启动前,必须写入etcd中的,例如”10.1.0.0/16″。

对于其余字段的含义及默认值以下:

①SubnetLen表示每一个主机分配的subnet大小,咱们能够在初始化时对其指定,不然使用默认配置。在默认配置的状况下,若是集群的网络地址空间大于/24,则SubnetLen配置为24,不然它比集群网络地址空间小1,例如集群的大小为/25,则SubnetLen的大小为/26

②SubnetMin是集群网络地址空间中最小的可分配的subnet,能够手动指定,不然默认配置为集群网络地址空间中第一个可分配的subnet。例如对于”10.1.0.0/16″,当SubnetLen为24时,第一个可分配的subnet为”10.1.1.0/24″。

③ SubnetMax表示最大可分配的subnet,对于”10.1.0.0/16″,当subnetLen为24时,SubnetMax为”10.1.255.0/24″

④BackendType为使用的backend的类型,如未指定,则默认为“udp”

⑤ Backend中会包含backend的附加信息,例如backend为vxlan时,其中会存储vtep设备的mac地址

2) 在获取了集群的网络配置以后,接下来咱们就调用SubnetManager中的AcquireLease()获取本主机的subnet。其中的参数类型LeaseAttrs以下所示:

显然,其中最重要的字段就是Public IP,它实质上是标识了一台主机。在获取subnet以前,咱们先要从etcd中获取当前全部已经存在的lease信息—-leases,以备后用。下面咱们将对不一样状况下lease的获取进行讨论:

① 事实上,这可能并非咱们第一次在这台机器上启动flannel,所以,颇有可能,在此以前,这台机器已经获取了lease。已知一台主机实际上是由它的Public IP标识的,因此咱们能够用Public IP做为关键字匹配leases中全部lease的Public IP。若匹配成功,则检查相应的lease是否和当前的集群网络配置兼容:检查的内容包括IP是否落在SubnetMin和SubnetMax内,以及subnet大小是否和SubnetLen相等。若兼容,则用新的LeaseAttrs和ttl更新该lease,表示成功获取本机的lease,不然只能将该lease删除。

② 当初始化SubnetManager时,会先试图解析以前flannel获取了lease后留下的配置文件(该文件的建立,会在下文描述),从中读取出以前获取的subnet。若是读取到的subnet不为空,则一样利用该subnet去匹配leases中全部lease的subnet。若匹配成功,则一样检查lease是否和当前的集群网络配置兼容。若兼容则更新lease,表示成功获取本机的lease,不然将其删除。若是该subnet并不能从leases中找到,可是它和当前的集群网络配置兼容的话,能够直接将它和LeaseAttrs封装为lease,写入etcd。

③ 若此时还未获取到lease,那么咱们有必要本身建立一个新的了。建立的方法很简单,从SubnetMin遍历到SubnetMax,将其中和leases中已有的subnet都不重合者加入一个集合中。再从该集合随机选择一个,做为本主机的subnet便可。最后,将subnet和LeaseAttrs封装为一个lease写入etcd。由此,该主机获取了本身的subnet。

最后,咱们将有关的集群网络和subnet的配置信息写入文件/run/flannel/subnet.env(可经过命令行参数”–subnet-file”手动指定)中,写入的信息以下所示,包括:集群网络地址空间FLANNEL_NETWORK,获取的子网信息FLANNEL_SUBNET等等

 

2.3维护subnet

当SubnetManager的后端存储使用的是etcd时,各个主机还须要对本身的lease进行维护,在租期即将到期时,须要对etcd中的lease进行更新,调用SubnetManager中的RenewLease()方法,防止它到期后被自动删除。另外,咱们能够在flanneld的命令行启动参数中用”–subnet-lease-renew-margin”指定在租期到期前多久进行更新。默认值为1小时,即每23小时更新一次lease,从新获取一次24小时的租期。

 
2.4发现新节点

如今,初始化已经完成了,咱们须要面对以下两个问题:

一、当本主机的flanneld启动时,若是集群中已经存在了其余主机,咱们如何经过backend进行配置,使得封包可以到达它们

二、若是以后集群中又添加了新的主机,咱们如何获取这一事件,并经过backend对配置进行调整,对于删除主机这类事件同理

固然上述两个问题,都是经过etcd解决的。backend会一边经过上文中的WatchLeases()方法对etcd进行监听,从中获取各种事件,另外一边会启动一个事件处理引擎,不断地对监听到的事件进行处理。

对于问题1,咱们首先要从etcd中获取当前全部的lease信息,并将其转化为一系列的event,将它交于事件处理引擎进行处理,从而让封包可以到达这些主机。

对于问题2,直接对etcd中的事件进行监听,将获取的事件转换为事件处理引擎可以处理的形式,并进行处理便可。

事件的类型也很简单,总共就只有EventAdded和EventRemoved两种,分别表示新增了lease以及一个lease过时。由于不一样backend的配置方式是彻底不一样的,下面咱们就对各类backend的基本原理进行解析,并说明它们如何处理EventAdded和EventRemoved这两类事件。

 
3. backend原理解析



在本节中,咱们将对hostgw,udp和vxlan三种backend进行解析。

 
3.1 hostgw

hostgw是最简单的backend,它的原理很是简单,直接添加路由,将目的主机当作网关,直接路由原始封包。

例如,咱们从etcd中监听到一个EventAdded事件subnet为10.1.15.0/24被分配给主机Public IP 192.168.0.100,hostgw要作的工做就是在本主机上添加一条目的地址为10.1.15.0/24,网关地址为192.168.0.100,输出设备为上文中选择的集群间交互的网卡便可。对于EventRemoved事件,只需删除对应的路由。

 
3.2 udp

咱们知道当backend为hostgw时,主机之间传输的就是原始的容器网络封包,封包中的源IP地址和目的IP地址都为容器全部。这种方法有必定的限制,就是要求全部的主机都在一个子网内,即二层可达,不然就没法将目的主机当作网关,直接路由。

而udp类型backend的基本思想是:既然主机之间是能够相互通讯的(并不要求主机在一个子网中),那么咱们为何不能将容器的网络封包做为负载数据在集群的主机之间进行传输呢?这就是所谓的overlay。具体过程以下所示:

当容器10.1.15.2/24要和容器10.1.20.2/24通讯时,由于该封包的目的地不在本主机subnet内,所以封包会首先经过网桥转发到主机中。最终在主机上通过路由匹配,进入网卡flannel0。须要注意的是flannel0是一个tun设备,它是一种工做在三层的虚拟网络设备,而flanneld是一个proxy,它会监听flannel0并转发流量。当封包进入flannel0时,flanneld就能够从flannel0中将封包读出,因为flannel0是三层设备,因此读出的封包仅仅包含IP层的报头及其负载。最后flanneld会将获取的封包做为负载数据,经过udp socket发往目的主机。同时,在目的主机的flanneld会监听Public IP所在的设备,从中读取udp封包的负载,并将其放入flannel0设备内。由此,容器网络封包到达目的主机,以后就能够经过网桥转发到目的容器了。

最后和hostgw不一样的是,udp backend并不会将从etcd中监听到的事件里所包含的lease信息做为路由写入主机中。每当收到一个EventAdded事件,flanneld都会将其中的subnet和Public IP保存在一个数组中,用于转发封包时进行查询,找到目的主机的Public IP做为udp封包的目的地址。

 
3.3 vxlan

首先,咱们对vxlan的基本原理进行简单的叙述。从下图所示的封包结构来看,vxlan和上文提到的udp backend的封包结构是很是相似的,不一样之处是多了一个vxlan header,以及原始报文中多了个二层的报头。

下面让咱们来看看,当有一个EventAdded到来时,flanneld如何进行配置,以及封包是如何在flannel网络中流动的。

如上图所示,当主机B加入flannel网络时,和其余全部backend同样,它会将本身的subnet 10.1.16.0/24和Public IP 192.168.0.101写入etcd中,和其余backend不同的是,它还会将vtep设备flannel.1的mac地址也写入etcd中。

以后,主机A会获得EventAdded事件,并从中获取上文中B添加至etcd的各类信息。这个时候,它会在本机上添加三条信息:

1) 路由信息:全部通往目的地址10.1.16.0/24的封包都经过vtep设备flannel.1设备发出,发往的网关地址为10.1.16.0,即主机B中的flannel.1设备。

2) fdb信息:MAC地址为MAC B的封包,都将经过vxlan首先发往目的地址192.168.0.101,即主机B

3)arp信息:网关地址10.1.16.0的地址为MAC B

如今有一个容器网络封包要从A发往容器B,和其余backend中的场景同样,封包首先经过网桥转发到主机A中。此时经过,查找路由表,该封包应当经过设备flannel.1发往网关10.1.16.0。经过进一步查找arp表,咱们知道目的地址10.1.16.0的mac地址为MAC B。到如今为止,vxlan负载部分的数据已经封装完成。因为flannel.1是vtep设备,会对经过它发出的数据进行vxlan封装(这一步是由内核完成的,至关于udp backend中的proxy),那么该vxlan封包外层的目的地址IP地址该如何获取呢?事实上,对于目的mac地址为MAC B的封包,经过查询fdb,咱们就能知道目的主机的IP地址为192.168.0.101。

最后,封包到达主机B的eth0,经过内核的vxlan模块解包,容器数据封包将到达vxlan设备flannel.1,封包的目的以太网地址和flannel.1的以太网地址相等,三层封包最终将进入主机B并经过路由转发达到目的容器。

事实上,flannel只使用了vxlan的部分功能,因为VNI被固定为1,本质上工做方式和udp backend是相似的,区别无非是将udp的proxy换成了内核中的vxlan处理模块。而原始负载由三层扩展到了二层,可是这对三层网络方案flannel是没有意义的,这么作也仅仅只是为了适配vxlan的模型。vxlan详细的原理参见文后的参考文献,其中的分析更为具体,也更易理解。

 
4. 总结



总的来讲,flannel更像是经典的桥接模式的扩展。咱们知道,在桥接模式中,每台主机的容器都将使用一个默认的网段,容器与容器之间,主机与容器之间都能互相通讯。要是,咱们能手动配置每台主机的网段,使它们互不冲突。接着再想点办法,将目的地址为非本机容器的流量送到相应主机:若是集群的主机都在一个子网内,就搞一条路由转发过去;如果不在一个子网内,就搞一条隧道转发过去。这样以来,容器的跨网络通讯问题就解决了。而flannel作的,其实就是将这些工做自动化了而已。

相关文章
相关标签/搜索