本文详解之flannel-udp模式

本文详解之flannel-udp模式

flanneld可以在启动的时候通过配置文件来指定不同的Backend来进行网络通信,目前比较成熟的Backend有VXLAN、host-gw以及UDP三种方式,

UDP- 工作在内核态和用户态之间

UDP模式相对容易理解

采用UDP模式时需要在flanneld的配置文件当中指定Backend type为UDP,可以通过直接修改flanneld的ConfigMap的方式实现,配置修改完成之后如下:

[root@10-10-88-192 ~]# kubectl get cm -n kube-system -o yaml kube-flannel-cfg
apiVersion: v1
data:
 cni-conf.json: |
 {
 "name": "cbr0",
 "type": "flannel",
 "delegate": {
 "isDefaultGateway": true
 }
 }
 net-conf.json: |
 {
 "Network": "10.244.0.0/16",
 "Backend": {
 "Type": "udp" #修改为udp模式
 }
 }
kind: ConfigMap
metadata:
 creationTimestamp: 2018-10-30T08:34:01Z
 labels:
 app: flannel
 tier: node
 name: kube-flannel-cfg
 namespace: kube-system
 resourceVersion: "33718154"
 selfLink: /api/v1/namespaces/kube-system/configmaps/kube-flannel-cfg
 uid: 8d981eff-dc1e-11e8-8103-fa900126bc00
[root@10-10-88-192 ~]#

关键字段为Backend当中的Type字段,采用UDP模式时Backend Port默认为8285,即flanneld的监听端口。

当采用UDP模式时,flanneld进程在启动时会通过打开/dev/net/tun的方式生成一个TUN设备,TUN设备可以简单理解为Linux当中提供的一种内核网络与用户空间(应用程序)通信的一种机制,即应用可以通过直接读写tun设备的方式收发RAW IP包。

通过可以ip -d link show flannel0可以看到这是一个tun设备:

[root@10-10-88-192 ~]# ip -d link show flannel0
5: flannel0:  mtu 1472 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 500
 link/none promiscuity 0
 tun
[root@10-10-88-192 ~]#
[root@10-10-88-192 ~]# netstat -ulnp | grep flanneld
udp 0 0 172.16.130.140:8285 0.0.0.0:* 2373/flanneld
[root@10-10-88-192 ~]#

我们发现这里的mtu是1472,相比Kubernetes集群网络接口eth1小了28个字节,为什么呢?

容器跨节点通信实现流程:

假设在节点A上有容器A(10.244.1.96),在节点B上有容器B(10.244.2.194),此时容器A向容器发送一个ICMP请求报文(ping),我们来逐步分析一下ICMP报文是如何从容器A到达容器B的。

本文详解之flannel-udp模式

步骤详解:

1、容器A当中发出ICMP请求报文,通过IP封装后形式为:10.244.1.96 -> 10.244.2.194,此时通过容器A内的路由表匹配到应该将IP包发送到网关10.244.1.1(cni0网桥)。下图为完整的帧格式:

本文详解之flannel-udp模式

2、此时到达cni0的IP包目的地IP 10.244.2.194匹配到节点A上第一条路由规则(10.244.0.0),内核将RAW IP包发送给flannel0接口。

3、flannel0为tun设备,发送给flannel0接口的RAW IP包(无MAC信息)将被flanneld进程接收到,flanneld进程接收到RAW IP包后在原有的基础上进行UDP封包,UDP封包的形式为:172.16.130.140:src port -> 172.16.130.164:8285。

这里有一个问题就是flanneld怎么知道10.244.2.194这个容器到底是在哪个节点上呢?

flanneld在启动时会将该节点的网络信息通过api-server保存到etcd当中,故在发送报文时可以通过查询etcd得到10.244.2.194这个容器的IP属于host B,且host B的IP为172.16.130.164。

我们发现这里的mtu是1472,相比Kubernetes集群网络接口eth1小了28个字节,为什么呢?

4、flanneld将封装好的UDP报文经eth1发出,从这里可以看出网络包在通过eth1发出前先是加上了UDP头(8个字节),再然后加上了IP头(20个字节)进行封装,这也是为什么flannel0的MTU要比eth1的MTU小28个字节的原因(防止封装后的以太网帧超过eth1的MTU而在经过eth1时被丢弃)。 下面是完整的以太网帧格式图:


本文详解之flannel-udp模式


5、网络包经节点A和节点B之间的网络连接到达host B。

6、host B收到UDP报文后经Linux内核通过UDP端口号8285将包交给正在监听的应用flanneld。

7、运行在host B当中的flanneld将UDP包解包后得到RAW IP包:10.244.1.96 -> 10.244.2.194。

8、解封后的RAW IP包匹配到host B上的路由规则(10.244.2.0),内核将RAW IP包发送到cni0。下面是完整的以太网帧格式图:


本文详解之flannel-udp模式


9、cni0将IP包转发给连接在cni0网桥上的container B,而flanneld在整个过程中主要主要负责两个工作:

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

总结:

从上面虚线部分就可以看到container A和container B虽然在物理网络上并没有直接相连,但在逻辑上就好像是处于同一个三层网络当中,这种基于底下的物理网络设备通过Flannel等软件定义网络技术实现的网络我们称之为Overlay网络。

那么上面通过UDP这种Backend实现的网络传输过程有没有问题呢?

最明显的问题就是,网络数据包先是通过tun设备从内核当中复制到用户态的应用,然后再由用户态的应用复制到内核,仅一次网络传输就进行了两次用户态和内核态的切换,显然这种效率是不会很高的。那么有没有高效一点的办法呢?当然,最简单的方式就是把封包解包这些事情都交给内核去干好了,事实上Linux内核本身也提供了比较成熟的网络封包解包(隧道传输)实现方案VXLAN,下面我们就来看看通过内核的VXLAN跟flanneld自己通过UDP封装网络包在实现上有什么差别


分享到:


相關文章: