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的。
步骤详解:
1、容器A当中发出ICMP请求报文,通过IP封装后形式为:10.244.1.96 -> 10.244.2.194,此时通过容器A内的路由表匹配到应该将IP包发送到网关10.244.1.1(cni0网桥)。下图为完整的帧格式:
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时被丢弃)。 下面是完整的以太网帧格式图:
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。下面是完整的以太网帧格式图:
9、cni0将IP包转发给连接在cni0网桥上的container B,而flanneld在整个过程中主要主要负责两个工作:
- UDP封包解包
- 节点上的路由表的动态更新
总结:
从上面虚线部分就可以看到container A和container B虽然在物理网络上并没有直接相连,但在逻辑上就好像是处于同一个三层网络当中,这种基于底下的物理网络设备通过Flannel等软件定义网络技术实现的网络我们称之为Overlay网络。
那么上面通过UDP这种Backend实现的网络传输过程有没有问题呢?
最明显的问题就是,网络数据包先是通过tun设备从内核当中复制到用户态的应用,然后再由用户态的应用复制到内核,仅一次网络传输就进行了两次用户态和内核态的切换,显然这种效率是不会很高的。那么有没有高效一点的办法呢?当然,最简单的方式就是把封包解包这些事情都交给内核去干好了,事实上Linux内核本身也提供了比较成熟的网络封包解包(隧道传输)实现方案VXLAN,下面我们就来看看通过内核的VXLAN跟flanneld自己通过UDP封装网络包在实现上有什么差别
關鍵字: 配置文件 ICMP 10-10-88-192