滴普技术荟-云原生基座OpenKube开放容器实践(七):flannel-udp模式原理分析

本来上一篇文章应该是介绍flannel的udp模式的了,但因为其中的内容会涉及到linux虚拟网络设备tun的原理,所以先介绍一下tun设备,然后在这一篇才转入正题。

 

flannel一共有UDP、VXLAN、HOST-GW三种工作模式,如果开启了Directrouting的话,会使用VXLAN和HOST-GW组合,不跨网段就使用HOST-GW,跨网段就使用VXLAN。对于host-gw和vxlan我们在前面的文章中有过简单地介绍,在这篇文章我们来分析一下flannel的UDP模式,虽然这种模式现在基本上已经不在生产上用了,但通过对UDP模式的了解,可以让我们更好地理解linux中的虚拟网络设备tun,以及为我们提供一种在用户态操作内核协议栈数据的思路。

 

当flannel以udp模式运行的时候,每个节点会有两个守护进程以及一个binary文件:

  1. 以k8s的daemonset运行的kube-flannel,负责与ETCD交互,获取最新的节点与子网信息,通过unix domain socket 的方式把信息同步给相同节点的flanneld进程
  2. 一个名叫flanneld守护进程,负责监听UDP 8285端口(默认端口,可以修改)并打开/dev/net/tun设备,不管哪一端来的数据,都往另一端转;同时会打开一个unix domain socket,接收来自kube-flannel的指令,更新路由。
  3. binary文件,存放目录为:/opt/cni/bin/flannel,在kubelet创建pod时会调用这个binary文件,负责创建同主机容器的相互通信,但它不具体做创建bridge或veth的工作,它只是生成了一个配置文件,然后调用本机的其它cni插件(例如:bridge和host-local,通常cni插件都在/opt/cni/bin目录下)来完成配置同主机容器的通信。

 

另外,在每个节点还会:

  1. 创建一个名为flannel.1的tun设备
  2. 创建了一条到POD-CIDR的直连路由,流量全部引到flannel.1的tun设备,注意是pod-cidr,不是某个node-cidr,这就意味着这台机出的流量只要是去容器的(当然除了本机的容器),就会走这张网卡。

 

下面一步步地解释上面的部件是怎么联动起来并完成跨主机的POD之间的通信的,在下面我们创建的tun设备名为tun0。

 

环境准备

首先介绍一下准备的环境,如下图:

(host1)

(host2)

 

两台主机,host1和host2:

---------------------------------------------------------------------------------

其中host1:

物理网卡eth0 IP:10.57.4.20;

linux veth设备veth1连着POD1,没有设置IP地址;

linux tun设备tun0,设置了IP为10.244.1.1(POD与POD通信时这个通常用不上,主机到POD时会用上)

主机路由三条,一条是默认网关;另外两条直连路由,分别到pod1和去往10.244.0.0/16

POD1的地址为10.244.1.3

---------------------------------------------------------------------------------

host2:

物理网卡eth0 IP:10.57.4.21;

linux veth设备veth1连着POD2,没有设置IP地址;

linux tun设备tun0,设置了IP为10.244.2.1(POD与POD通信时这个通常用不上,主机到POD时会用上)

主机路由三条,一条是默认网关;另外两条直连路由,分别到pod2和去往10.244.0.0/16

POD2的地址为10.244.2.3

---------------------------------------------------------------------------------

另外在两主机上都:

  1. 开启了路由转发(net.ipv4.ip_forward=1)。
  2. 分别已经运行了kube-flannel和flanneld进程,kube-flannel已经和etcd或api-server正常连接,并订阅到了全部节点和节点的子网信息,也已经传递给了主机的flanneld上的路由表。
  3. 主机的pod都用veth的方式与主机相互正常连接,pod上都像前面的文章提到的配置了默认网关169.254.2.2,并且对端veth1都开启了ARP代答,所以pod1此时已经能把出容器的流量送到主机端的veth1了。

 

下面我们将通过一个例子来详细说明数据包的发送和接收的全过程,这里binary文件flannel的工作就不再说明了,这并非本文的重点。

 

发送流程

我们假设pod2有一个web服务在运行,当我们从pod1发送一个http请求给pod2时,会经历以下步骤:

  1. 数据包从pod1的用户进程出来,进入pod1的协议栈,协议栈发现要去往目的地并非在相同网段,于是设置了下一跳为默认网关169.254.2.2,通过ARP查到默认网关的IP对应的MAC为主机veth1的MAC地址,于是把veth1的MAC地址填进目标MAC地址,完成MAC头的封装,把包发送到veth1,进入主机host1的网络协议栈。
  2. 数据包在host1的网络协议栈经过ROUTE判断,发现目标地址10.244.2.3并非本机地址,因为本机开启了路由转发,所以走FORWARD链。
  3. 主机协议栈在主机路由中为数据包寻找合适的路由,匹配到去往10.244.0.0/16网段的包应该走tun0,于是从tun0转发出去。
  4. tun0是一个linux tun设备,从协议栈收的包,会被另一端的用户进程收到,在这里tun0的另一端由flanneld进程打开(就是打开了/dev/net/tun这个文件),于是flanneld收到了数据包。
  5. flanneld查看数据包的IP头,发现目的地是10.244.2.3,于是从自己的路由中找去这个目的地下一跳,从kube-flannel传来的信息指示10.244.2.0/24这个子网在主机10.57.4.21上(这一步已经在kube-flannel和flanneld进程启动后就立刻完成了),于是flanneld进程经过一些必要的处理后,把从tun0来的数据包(包括TCP头、IP头)当成数据,通过打开的udp端口发往了10.57.4.21的UDP8285端口。

 

这时候由POD1的协议栈发出来的包,已经变成了另一个包的数据区的数据,从host1的eth0出来的数据包的结构如下:

接收流程

主机网段按正常的流程把数据包传输到了host2的eth0网卡上,来看看接收的全过程:

 

从10.244.2.3回包的流程也类似。

 

可以看到,flannel的udp模式在一次跨主机的pod与pod通信的过程中,需要两次切换用户态与内核态,所以性能其实远低于vxlan模式,虽然vxlan模式用的也是udp协议,但因为是在内核态完成数据包的处理,所以性能要远高于udp模式。

 

在flannel的源码中,flanneld是由c语言直接实现的,关键代码在/backend/udp/proxy_adm64.c,而kube-flannel则是由go语言实现的,关键部分在/backend/udp/cproxy_adm64.go。

proxy_adm64.c中最关键的就是tun_to_udp和udp_to_tun,光是看名字就已经明白了用途,而且代码还贼精简:

不知道为什么还要无端实现一个udp模式,据说是因为作者实现UDP模式时,linux的内核还不支持vxlan,但我去网上查了一下,linux内核从3.7开始支持vxlan,到3.12开始对vxlan已经完备

https://kernelnewbies.org/Linux_3.7#Virtual_extensible_LAN_tunneling_protocol

https://kernelnewbies.org/Linux_3.12#Networking

而3.7是2012年底发布的,3.12是2013年2月发布的,flannel代码的初次提交是2014年,应该不是这个原因,而且从实现上来说,明显是vxlan更容易实现,真是让人费解。

 

了解更多信息请登录:https://www.deepexi.com/bbs/developer-bbs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值