flannel 介绍(UTP、VXLAN、Host Gateway模式详解)

(本文章是学习《kubernetes 网络权威指南》 一书时做的笔记,由于本人是刚学习k8s,且自学方式就是把它敲一遍,然后加一些自己的理解。所以会发现很多内容是跟书中内容一模一样,如果本文是对本书的侵权,敬请谅解,告知后会删除。如果引用或转载,请注明出处——谢谢)


flannel 可以为容器提供跨节点网络服务,其模型为集群内所有的容器使用一个网络,然后为每个主机从该网络中划分一个子网。flannel为主机上的容器创建网络时,从子网中划分一个IP给容器。根据kubernetes的模型,为每个Pod提供一个IP,flannel的模型正好与之契合,并且flannel简单易用。
flannel几乎是最早的跨节点容器通信方案,很多网络插件都有它的影子,其他的网络方案可以看做是它的改进版。说到容器跨节点访问,主要有以下几方面的挑战:
容器IP地址的重复问题: 由于docker等容器工具只是利用Linux内核的network namespace实现了网络隔离,各个几点上的容器IP地址是所在所属节点上自动分配的,从全局来看,这种局部地址像是小区的门牌号,一旦拿到一个更大的范围来看,就有重复的可能。为了解决这个问题,flannel设计了一种全局的网络地址分配机制,及使用etcd存储网段和节点之间的关系,然后flannel配置各个节点上的docker(或其他容器工具),只在分配到当前节点的网段里选择容器IP地址。这样就确保了IP地址分配的全局唯一性。
容器IP地址路由问题: IP地址唯一并不能保证能够通信,还有一个问题,因为通常虚拟网络的IP和Mac地址在物理网络上是不认识的,所有数据包及时被发送到网络中,也会因为无法路由而被丢弃。flannel最早用的比较多的一种方式是overlay网络,其实就是个隧道网络,后来flannel也开发了另外几种处理方法,对应于flannel的几种网络模式。

在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,再交由其转发给目的容器。

使用kubernetes安装flannel

由于flannel没有master和slave之分,每个节点上都安装一个agent,即flanneld。我们可以使用kubernetes的DaemonSet部署flannel,以达到每个节点部署一个flanneld实例的目的。
我们在每一个flannel的Pod中都运行一个flanneld进程,且flanneld的配置文件以ConfigMap的形式挂载到容器内的/etc/kube-flannel/目录,供flanneld使用。其中比较关键的一个字段是Backend.Type 字段,表示flannel采用什么模式。我们后续详细介绍flannel的各种backend。

flannel backend 详解(flannel 各种模式详解)

flannel通过在每一个节点上启动一个叫flannel的进程,负责为每一个节点上的子网划分,并将相关配置信息(如各节点的子网网段、外部IP等)保存到etcd中,而具体的网络报文转发交给backend实现。
flanneld可以在启动时通过配置文件指定不同的backend进行网络通信,目前比较 成熟的backend有UDP、VXLAN和host-gateway三种。目前,VXLAN是官方比较推崇的一种backend实现方式;host-gateway一般用于对网络性能要求比较高的场景,但需要基础网络架构的支持;UDP则用于测试及一般比较老的不支持VXLAN的Linux内核。

1、UDP

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的图片。所以后面的流程图也上传不了。读者谅解)

  1. 容器A发出的ICMP请求报文,通过IP封装后的形式为10.244.1.96(源)->10.244.2.194(目的)。此时通过容器A内部的路由表匹配到应该讲IP发送到网关10.244.1.1(cni0网桥)。此时的ICMP报文以太网帧格式为:
    Mac头:src:容器A的Mac,dst:节点A cni0MAC
    IP头:src:容器A的IP10.244.1.96;dst:容器B的IP10.244.2.194 后面是ICMP报文
  2. 到达cni0的IP包目的地址为10.244.2.194,匹配到节点A上第一条路由规则(10.244.0.0),内核通过查本机路由表知道应该讲RAWIP包发送给flannel0接口
  3. flannel0接口为tun设备,发送给flannel0接口的RAW
    IP包(无MAC)信息将被flanneld进程接收,flanneld进程接收RAW IP包后再原有
    的基础上进行UDP封包,UDP封包的形式为172.16.130.140:{系统管理的随机端口}->172.16.130.164:8285。
    注:172.16.130.164是10.244.2.194这个目的容器所在宿主机的IP地址,flanneld通过查询etcd很容器就能得到,8285是flanneld监听端口。
  4. flanneld将封装好的UDP报文经过eth0转发出去,从这里可以看出网络包通过eth0发出前,先是加上了UDP头(8个字节),再加上IP头(20字节)进行封装,这也是flannel0的MTU要比eth0的MTU小28个字节的原因,即防止封包后的以太网帧超过eth0的MTU,而在经过eth0时被丢弃。
    此时完整封包后的ICMP以太网帧格式为:
    MAC头:源 节点A MAC;目的 节点B Mac
    IP头:源 节点A IP172.16.130.140; 目的 节点B IP 172.16.130.164
    UDP头:源端口 系统随机的端口;目的端口 8285
    后面跟着的是payload
  5. 网络包经过主机网络从节点A到达节点B
  6. 主机B收到UDP报文后,Linux内核通过端口号8285将包交给正在监听的flanneld。
  7. 运行在节点B上的flanneld将UDP包解包后得到RAW IP包:10.244.1.96 ->10.244.2.194
  8. 解包后的RAW IP包匹配到主机B上的路由规则(10.244.2.0),内核通过查询本机路由表知道将该RAW
    IP转发给cni0网桥。此时解包后的以太网帧格式:
    Mac头:源 节点B cni0 的Mac;目的 容器B 的Mac
    IP头:源 容器AIP 10.244.1.96; 目的 容器B IP 10.244.2.196
    后面是ICMP报文
  9. cni0 网桥将IP包转发给连接在网桥上的容器B。至此整个流程结束。回程报文将按照上面的数据流原路返回

纵观这个过程,flannel的作用是:

  • UDP包封包解包
  • 节点上路由表的动态更新

flannel的UDP封包是指原数据由节点的flanneld进行UDP封装,经过主机网络投递到目的节点后就被另一端的flanneld还原成了原始数据包,两边的docker daemon 都感觉不到这个过程的存在。flannel根据etcd的数据刷新本节点路由表,通过路由表信息在寻址时找到应该投递的目的节点。
可以看到容器A和容器B虽然在屋里网络上并没有直接相连,但在逻辑上就好像处于同一个三层网络中,这种基于底层网络设备通过flannel等软件定义网络技术构建的上层网络称之为overlay网络。
通过UDP这种backend实现的网络传输过程最明显的问题就是,网络数据包先通过tun设备从内核中复制到用户态,再由用户态的应用复制到内核,仅一次网络传输就进行了两次用户态和内核态的切换,显然效率不高。

2、VXLAN

在开始对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得知通知,并以此执行以下流程:

  1. 在节点中创建一条该节点所属网段的路由表,主要是让Pod中的流量路由到flannel.1 接口。route -n
    可以查看节点上相关的路有信息。
  2. 在节点中添加一条爱节点的IP及VTEP设备的静态ARP缓存,arp-n查看当前节点中已经缓存的其他节点上容器的ARP信息。通过bridge fdb命令查看节点上的VXLAN转发表fdb,可以看到转发表中,Mac为对端容器的Mac,IP为对端VTEP的对外IP。(flannel VTEP的对外IP地址可通过flanneld的启动参数 -iface=eth0指定,若不指定则按照默认网关查找网络接口对应的IP)

VXLAN 模式数据路径:在VXLAN模式下,flannel集群跨节点通信的数据流程图如下
(不好意思,图片过大不能插入)

  1. 同UDP模式,容器A中的IP包通过容器A的路由表被发送到cni0.
  2. 到达存cni0中的包通过匹配主机A中的路由表发送通往 10.244.2.194的IP包应该交给flannel.1 接口
  3. flannel.1作为一个VTEP设备,收到报文后按照VTEP的配置进行封包。首先通过etcd得知10.244.2.169属于节点B,并知道节点B的IP地址。然后通过节点A中的转发表得知到节点B对应的VTEP的Mac,根据flannel.1设备创建时设置的参数(VNI、local IP、Port)进行VXLAN封包。
  4. 通过主机A与主机B之间的网络连接,VXLAN包到达节点B的网卡
  5. 通过 8472端口,VXLAN包被转发给VTEP设备 flannel.1进行解包
  6. 解包后的IP包匹配节点B中的路由表(10.244.2.0),内核将IP包转发给cni0。
  7. cni0 将IP包转发给连接在cni0 上的容器B

在VXLAN模式下,数据由内核转发的,flannel不转发数据,仅动态设置ARP和FDB表项。

VXLAN 模式的实现
flannel VXLAN模式的实现经历三个版本的迭代

  1. flannel的第一个版本,L3Miss学习,是通过查找ARP表Mac完成的。L2Miss学习,通过获取VTEP上的对外IP地址实现

  2. flannel的第二个版本,移除了L3Miss学习。当主机上线时,直接添加对应的ARP表项即可,不用查找学习。

  3. 最新版的flannel移除了L2Miss学习和L3Miss学习,它的工作模式如下:

    1)创建VXLAN设备,不再监听L2Miss和L3Miss事件
    2)为远端主机创建静态ARP表项
    3)创建FDB转发表项,包含VTEP Mac和远端flannel的对外IP

最新版的flannel完全去掉了L2Miss和L3Miss方式,改成主动给子网添加远端主机路由的方式。同时,VTEP和网桥各自分配三层IP地址。当数据包到达目的主机后,在内部进行三层寻址,路由数目与主机数(而不是容器数)线性相关。官方声称同一个VXLAN子网下每个主机对应一个路由表项,一个ARP表项和一个FDB表项。

3、Host Gateway

Host Gateway简称 host-gw,从名字就可以想到这种方式是通过把主机当做网关实现跨节点网络通信。与UDP和VXLAN模式相似,要是用host-gw模式,就需要将flannel的backend中的.Type参数设置成“host-gw”。
使用host-gw Backend 的flannel网络额网络包传输过程如图
(同样原因,图片不能插入,请谅解)

  1. 同UDP、VXLAN模式一致,通过容器A的路由表IP包到达cni0
  2. 到达cni0 的IP包匹配到主机A中的路由规则(10.244.2.0),且网关为172.16.130.164,即主机B,所以内核将IP包发送给主机B
  3. IP包通过物理网络到达主机B的eth1
  4. 到达主机B的eth1 的IP包匹配到主机B上的路由表(10.244.2.0),IP包转发给cni0
  5. cni0 将IP包转发给连接在cni0 上的容器B

host-gw模式下,各节点之间的跨节点网络通信要通过节点上的路由表实现,因此必须要通信双方所在的宿主机能够直接路由。这就要求flannel host-gw模式下集群中的所有节点必须同处于一个网络内,这限制使得host-gw模式无法适用于集群规模较大且需要对节点进行网段划分的场景。host-gw另外一个限制则是随着集群中节点规模的增大,flanneld维护主机上成千上万条路由表的动态更新也是一个不小的压力,因此在路由方式下,路由表规则的数量是限制网络规模的一个重要因素,我们在Calico时也会讨论这个话题。
采用host-gw模式后,flanneld的唯一作用就是负责主机上路由表的动态更新。

4、flannel与etcd

flannel需要使用etcd保存网络元数据,可能读者会关心flannel在etcd中保存的数据是什么,保存在哪个key中?在flannel版本演进过程中,etcd保存路径发生了多次变化,因此在etcd数据库中全文搜索flannel的关键字是比较靠谱的一种方式,如下所示

etcd get "" --prefix  --key-only | grep -Ev "^$" | grep "flannel"

除了flannel,Calico和Canal在etcd中配置数据的key都可以用以上的方式查询

flannel小结

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。

发布了13 篇原创文章 · 获赞 6 · 访问量 350

猜你喜欢

转载自blog.csdn.net/WuYuChen20/article/details/104515974