在overlay网络下,所有被发送到网络中的报文都会被添加上额外的包头封装,这些包头里通常包含了主机本身的IP地址,这些包头里通常包含了主机本身的IP地址,因为只有主机的IP地址是原本就可以在网络里路由转播的。根据不同的封包方式,flannel提供了UDP和VXLAN两种传播方法。UDP封包使用了flannel自定的一种包头协议,数据是在Linux的用户态进行封包和解包的,因此当数据进入主机后,需要经历两次内核态到用户态的转换。VXLAN封包采用的是内置在Linux内核里的标准协议,因此虽然它的封包结构比UDP复杂,但是所有的数据封装和解包都是在内核中完成的,实际传播速率要比UDP模式快许多。但是flannel早期流行的时候,2014年左右,那时主流的Linux内核版本比较低,没有VXLAN内核模块,使flannel落得了一个 速度慢的名声。overlay是一种解决容器网络地址路由的方法。
路由是另一种解决容器网络地址路由方式,flannel的host-gateway模式采用的就是该方法。从上面我们知道,容器网络无法进行路由是因为宿主机之间没有路由信息,但flannel是知道这个消息的,因此一个直观的方法能不能把这个信息告诉网络上的节点?在host-gateway模式下,flannel通过在各个节点上运行的agent将容器网络的路由信息刷到主机的路由表上,这样一来,所有的主机就都有整个容器网络的路由信息了。host-gateway的方式没有引入overlay额外的封包和解包,完全是普通网络路由机制,通信效率与裸机直连相差无几。事实上flannel的host-gateway模式性能甚至比calico要好。然而,由于flannel只能修改节点上的路由表,一旦节点之间有其他路由设备,比如三层路由器,这个包就会被路由设备丢弃。这样一来,host-gateway模式只能用于二层直接可达的网络,由于网络风暴问题,这种网络通常是比较小规模的。近年来,也出现了一些专门的设备能够构建出大规模的二层网络(我们经常听到的“大二层”网络)。
flannel的设计目的就是为集群中的所有节点重新规划IP地址的使用规则,从而使得集群中的不同节点主机创建的容器都具有唯一且可路由的IP地址,并让属于不同节点上的容器能够直接通过内网IP通信。那么节点是如何知道哪些IP可用,哪些不可用呢?flannel用到了etcd的分布式协同功能。
flannel在架构上分为管理面和数据面。管理面主要包含了一个etcd,用于协调各个节点上容器分配的网段,数据面即在每个节点上运行一个flanneld进程。与其他网络方案不同的是,flannel采用的是no server架构,即没有控制节点,简化了flannel的部署与运维。
集群内所有flannel节点共享一个大的容器地址段,flanneld一启动便会观察etcd,从etcd得知其他节点上容器已占用的网段信息,然后向etcd申请该节点可用的IP地址段,并把该网段和主机IP记录在etcd中。
flannel通过etcd分配了每个节点可用的IP地址段后,修改docker的启动参数,例如–bip=172.17.18.1/24限制了所在节点容器获得IP范围,以确保每个节点上的docker会使用不同的IP段。需要注意的是,这个IP范围是flannel自动分配的,由flannel通过保存在etcd服务中的记录确保它们不会重复,无需用户手动干预。
flannel底层实现实质上是一种overlay网络(除了host-gateway模式),即把某一协议的数据包封装在另一种网络协议中进行路由转发。flannel目前支持的底层实现有:UDP、VXLAN、Alloc、host-gateway、AWS VPC、GCE路由。
其中性能最好的是host-gateway。AWS VPC和GCE路由都需要L2网络支持,如果没有接入云服务,通常维护成本也挺高。Alloc只为本机创建子网,多个主机上的子网之间不能直接通信。
最后,flannel在封包的时候是怎么知道目的容器所在主机的IP地址?flannel会观察etcd的数据,因此在其他节点向etcd更新网段和IP信息时,etcd就感知到了,在向其他主机上的容器转发网络包时,用对方容器所在的主机的IP进行封包,然后将数据发往对应主机的flanneld,再交由其转发给目的容器。
由于flannel没有master和slave之分,每个节点上都安装一个agent,即flanneld。我们可以使用kubernetes的DaemonSet部署flannel,以达到每个节点部署一个flanneld实例的目的。
我们在每一个flannel的Pod中都运行一个flanneld进程,且flanneld的配置文件以ConfigMap的形式挂载到容器内的/etc/kube-flannel/目录,供flanneld使用。其中比较关键的一个字段是Backend.Type 字段,表示flannel采用什么模式。我们后续详细介绍flannel的各种backend。
flannel通过在每一个节点上启动一个叫flannel的进程,负责为每一个节点上的子网划分,并将相关配置信息(如各节点的子网网段、外部IP等)保存到etcd中,而具体的网络报文转发交给backend实现。
flanneld可以在启动时通过配置文件指定不同的backend进行网络通信,目前比较 成熟的backend有UDP、VXLAN和host-gateway三种。目前,VXLAN是官方比较推崇的一种backend实现方式;host-gateway一般用于对网络性能要求比较高的场景,但需要基础网络架构的支持;UDP则用于测试及一般比较老的不支持VXLAN的Linux内核。
UDP模式相对容易理解,顾先采用UDP这种backend模式进行说明,然后延伸到其他模式。采用UDP模式时,需要在flanneld的配置文件中指定Backend.Type为UDP,可通过直接修改flanneld的ConfigMap的方式实现。
当采用UDP模式时,flanneld进程在启动时会通过打开/dev/net/tun的方式生产一个tun设备(需了解可看写的tun设备详情介绍文章)。tun设备可简单理解为Linux提供的一种内核网络与用户空间(应用程序)通信的机制,及应用可以通过直接读写tun设备的方式收发RAW IP包。
flanneld进程启动后,通过ip addr 命令可以发现节点上会多出一个flannel0的网络接口。通过 ip -d link show flannel0 可以查看到这是一个tun设备;并且可以看到flannel0 网络接口的MTU为1472,相比宿主机的网络接口eth0 少了28个字节。通过 netstate -ulnp | grep flanneld 得知flanneld监听的是8285端口。
flannel UDP模式本机通信实践:
现在有三个容器A、B,它们IP地址分别为4.0.100.3、4,0 100,4。查看宿主机上的路由信息,可以发现有下面一条路由信息
route -n
4.0.100.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0
当容器发送到同一个subnet的容器B时,因为二者处于同一个子网,所以容器A和B处于同一个宿主机上,而容器A和B也均桥接在docker0上,借助网桥docker0,即可实现同一主机上的容器A和容器B的直接通信。
注:较早版本的flannel是直接服用Docker创建的docker0网桥,后面版本的flannel将使用CNI创建自己的cni0网桥。不管是docker0还是cni0,本质都是Linux网桥,功能也一样。
flannel UDP模式跨主机通信实践:
容器跨主机通信实现流程:假设节点A上有容器A10.244.1.96,在节点B上有容器B 10.244.2.194。此时,容器A向容器B发送一个ICMP请求报文,我们来逐步分析ICMP报文从容器A到容器B的整个过程
(本来是有图片的,但是CSDN只能上传不超过5M的图片。所以后面的流程图也上传不了。读者谅解)
纵观这个过程,flannel的作用是:
flannel的UDP封包是指原数据由节点的flanneld进行UDP封装,经过主机网络投递到目的节点后就被另一端的flanneld还原成了原始数据包,两边的docker daemon 都感觉不到这个过程的存在。flannel根据etcd的数据刷新本节点路由表,通过路由表信息在寻址时找到应该投递的目的节点。
可以看到容器A和容器B虽然在屋里网络上并没有直接相连,但在逻辑上就好像处于同一个三层网络中,这种基于底层网络设备通过flannel等软件定义网络技术构建的上层网络称之为overlay网络。
通过UDP这种backend实现的网络传输过程最明显的问题就是,网络数据包先通过tun设备从内核中复制到用户态,再由用户态的应用复制到内核,仅一次网络传输就进行了两次用户态和内核态的切换,显然效率不高。
在开始对flannel VXLAN模式讨论之前,建立先了解一下VXLAN技术,可在前面的文章了解到。
flannel对VXLAN的使用比较简单,因为目前kubernetes只支持单网络,故在三层网络上只有一个VXLAN网络。因此,你会发现flannel会在集群的节点上创建一个名为flannel.1(明明规则为flannel.[VNI,默认VNI为1])的VXLAN网卡。VTEP的Mac地址不是通过多播学习到的,而是通过API server处的watch Node发现的。反观UDP模式下创建的flannel0是tun设备。
可以通过 ip -d link 查看VTEP设备flannel.1的配置信息,可以看到,flannel.1配置的local IP为容器网段,flanneld为VXLAN外部UDP包目的端口配置的是Linux VXLAN默认端口8472,而不是IANA分配的4789端口。
可能会有疑惑,在UDP模式下flanneld进行网络的封包和解包工作,而VXLAN模式下封包解包的工作由内核完成,那么此时的flanneld的作用是什么?带着这个疑问我们先来简单介绍flannel的VXLAN模式时如何工作的。
flanneld启动时先确保VXLAN设备已存在,如果不存在则创建,存在则跳过。并将VTEP设备的信息上报到etcd中,当flannel网络有新节点加入集群时并向etcd注册时,各节点上的flanneld从etcd得知通知,并以此执行以下流程:
VXLAN 模式数据路径:在VXLAN模式下,flannel集群跨节点通信的数据流程图如下
(不好意思,图片过大不能插入)
在VXLAN模式下,数据由内核转发的,flannel不转发数据,仅动态设置ARP和FDB表项。
VXLAN 模式的实现
flannel VXLAN模式的实现经历三个版本的迭代
flannel的第一个版本,L3Miss学习,是通过查找ARP表Mac完成的。L2Miss学习,通过获取VTEP上的对外IP地址实现
flannel的第二个版本,移除了L3Miss学习。当主机上线时,直接添加对应的ARP表项即可,不用查找学习。
最新版的flannel移除了L2Miss学习和L3Miss学习,它的工作模式如下:
1)创建VXLAN设备,不再监听L2Miss和L3Miss事件
2)为远端主机创建静态ARP表项
3)创建FDB转发表项,包含VTEP Mac和远端flannel的对外IP
最新版的flannel完全去掉了L2Miss和L3Miss方式,改成主动给子网添加远端主机路由的方式。同时,VTEP和网桥各自分配三层IP地址。当数据包到达目的主机后,在内部进行三层寻址,路由数目与主机数(而不是容器数)线性相关。官方声称同一个VXLAN子网下每个主机对应一个路由表项,一个ARP表项和一个FDB表项。
Host Gateway简称 host-gw,从名字就可以想到这种方式是通过把主机当做网关实现跨节点网络通信。与UDP和VXLAN模式相似,要是用host-gw模式,就需要将flannel的backend中的.Type参数设置成“host-gw”。
使用host-gw Backend 的flannel网络额网络包传输过程如图
(同样原因,图片不能插入,请谅解)
host-gw模式下,各节点之间的跨节点网络通信要通过节点上的路由表实现,因此必须要通信双方所在的宿主机能够直接路由。这就要求flannel host-gw模式下集群中的所有节点必须同处于一个网络内,这限制使得host-gw模式无法适用于集群规模较大且需要对节点进行网段划分的场景。host-gw另外一个限制则是随着集群中节点规模的增大,flanneld维护主机上成千上万条路由表的动态更新也是一个不小的压力,因此在路由方式下,路由表规则的数量是限制网络规模的一个重要因素,我们在Calico时也会讨论这个话题。
采用host-gw模式后,flanneld的唯一作用就是负责主机上路由表的动态更新。
flannel需要使用etcd保存网络元数据,可能读者会关心flannel在etcd中保存的数据是什么,保存在哪个key中?在flannel版本演进过程中,etcd保存路径发生了多次变化,因此在etcd数据库中全文搜索flannel的关键字是比较靠谱的一种方式,如下所示
etcd get "" --prefix --key-only | grep -Ev "^$" | grep "flannel"
除了flannel,Calico和Canal在etcd中配置数据的key都可以用以上的方式查询
flannel配置L3 overlay网络,它会创建一个大型内部内网,跨越集群中每个节点。在此overlay网络中,每个节点都有一个子网,用于在内部分配IP地址。在配置Pod时,每个节点上的Docker桥接口都会为每一个新创建的容器分配一个地址。同一主机中的Pod可以使用Docker桥接通信,而不同主机上的Pod会使用flanneld将其流量封装在UDP数据包中,以便路由到适当的目标。flannel有几种不同类型的后端可用于封装和路由,经历了UDP、VXLAN和Host-Gateway等技术演进,效率也逐渐提高。这一切都离不开Linux的设计哲学: Less is More。
总体来说,flannel是大多数用户的不错选择。从管理角度来看,它提供了一个简单的网络模型,用户只要一些基础的知识,就可以设置适合大多数用例的环境。与其他方案想相比,flannel相对容易安装和配置,许多常见的kubernetes集群部署工具和许多kubernetes发行版都可以默认安装flannel。在学习kubernetes初期,使用flannel是一个稳妥且明智的选择,直到你开始需要一些它无法提供的东西(比如 kubernetes 中 network policy。
(本文章是学习《kubernetes 网络权威指南》 一书时做的笔记,由于本人是刚学习k8s,且自学方式就是把它敲一遍,然后加一些自己的理解。所以会发现很多内容是跟书中内容一模一样,如果本文是对本书的侵权,敬请谅解,告知后会删除。如果引用或转载,请注明出处——谢谢)