★★★K8S中的Flannel三种网络分析---udp(性能非常差)、vxlan(默认模式)、host-gw(性能最强)

olivee 4年前 ⋅ 1737 阅读

参考

k8s<=1.16版本,则安装:flannel/v0.11.0

yml下载地址:https://raw.githubusercontent.com/coreos/flannel/v0.11.0/Documentation/kube-flannel.yml

k8s>=1.17版本,则安装:flannel/v0.12.0

yml下载地址:https://raw.githubusercontent.com/coreos/flannel/v0.12.0/Documentation/kube-flannel.yml

支持3种实现:UDP、VXLAN、host-gw

  • udp模式:使用设备flannel.0进行封包解包,不是内核原生支持,上下文切换较大,性能非常差

  • vxlan模式:使用flannel.1进行封包解包,内核原生支持,性能较强

  • vxlan(Dirextrouting)模式:经过测试发现感觉和host-gw模式差不多。不知道它们的区别是啥

  • host-gw模式:无需flannel.1这样的中间设备,直接宿主机当作子网的下一跳地址,性能最强

  • host-gw的性能损失大约在10%左右,而其他所有基于VXLAN隧道机制的网络方案,性能损失在20%~30%左右

使用kubernetes安装flannel

由于flannel没有master和slave之分,每个节点上都安装一个agent,即flanneld。我们可以使用kubernetes的DaemonSet部署flannel,以达到每个节点部署一个flanneld实例的目的。

1. 安装vxlan模式的flannel

1.1 安装

修改下载的kube-flannel.yml中的"Network": "10.244.0.0/16",与你k8s集群配置的Pod CIDR一致即可(注意不要和物理网卡的IP同网段,这样可能导致路由问题)。

可以看到kube-flannel.yml中配置的Backend默认为vxlan。因此不用修改。

      "Backend": {
        "Type": "vxlan"
      }

kubectl create -f kube-flannel.yml

flannel采用VxLan模式,使用8472端口。在每个k8s集群中的节点都会监听8472端口,其它节点会用一个随机端口连接到目标主机的8472端口,采用UDP协议进行发布。如下面一条抓包数据所示:我们在192.168.19.166的pod(10.244.1.7)中去ping 192.168.19.167的pod ip(10.244.2.6),这时我们抓取192.168.19.166 的enp0s3设备的udp包,192.168.19.166.39338 > 192.168.19.167.otv表示192.168.19.166发包的时候会的用39338(随机端口)连接到192.168.19.167的8472(otv)端口---这相当于隧道的包头(新的目的和源地址);后面的fe:0e:f2:75:aa:44 > 92:e5:0e:57:bd:72, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 3072, seq 0, length 64表示隧道传输的原始数据。

1.1.1 安装好之后的网络架构图如下

flannel-vxlan1.png

1.1.2 以Node1为例详细说明

flannel-vxlan2.png

1.2 测试

1.2.1 创建测试pod

kubectl run busybox --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox1 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox2 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox3 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox4 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m

这里我创建好之后的拓扑架构如下图: flannel-vxlan3.png

1.2.2 在node1的busybox3(10.244.1.7)pod中去ping node2的busybox(10.244.2.6)

# kubectl exec -ti busybox3 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
# 这里看到eth0配对的网卡是if8(即veth1093090e)
3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue 
    link/ether d2:64:45:f9:14:d3 brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.7/24 scope global eth0
       valid_lft forever preferred_lft forever
# 看到路由的网关是10.244.1.1(即网关是cni0设备)
/ # ip r
default via 10.244.1.1 dev eth0 
10.244.0.0/16 via 10.244.1.1 dev eth0 
10.244.1.0/24 dev eth0 scope link  src 10.244.1.7 
# 发起ping包
/ # ping 10.244.2.6 -c 1

1.2.2.1 node1的抓包结果

在ping的同时,对node1的veth1093090e、cni0、flannel.1、enp0s3设备抓包:

# tcpdump -i veth1093090e -ne icmp
15:00:44.493616 d2:64:45:f9:14:d3 > 42:e1:b2:c8:b5:24, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.494266 42:e1:b2:c8:b5:24 > d2:64:45:f9:14:d3, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

#tcpdump -i cni0 -ne icmp
15:00:44.493616 d2:64:45:f9:14:d3 > 42:e1:b2:c8:b5:24, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.494257 42:e1:b2:c8:b5:24 > d2:64:45:f9:14:d3, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

#tcpdump -i flannel.1 -ne icmp
15:00:44.493648 fe:0e:f2:75:aa:44 > 92:e5:0e:57:bd:72, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.494245 92:e5:0e:57:bd:72 > fe:0e:f2:75:aa:44, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

# tcpdump -i enp0s3 -ne udp and 'host 192.168.19.167'
15:00:44.493668 08:00:27:69:22:80 > 08:00:27:f3:59:5b, ethertype IPv4 (0x0800), length 148: 192.168.19.166.39338 > 192.168.19.167.otv: OTV, flags [I] (0x08), overlay 0, instance 1
fe:0e:f2:75:aa:44 > 92:e5:0e:57:bd:72, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.494191 08:00:27:f3:59:5b > 08:00:27:69:22:80, ethertype IPv4 (0x0800), length 148: 192.168.19.167.59883 > 192.168.19.166.otv: OTV, flags [I] (0x08), overlay 0, instance 1
92:e5:0e:57:bd:72 > fe:0e:f2:75:aa:44, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

经测试,数据包确实由 busybox3 pod 的eth0 发往 veth1093090e 再发往 cni0 再发往flannel.1 再发往enp0s3,再发到node2。

且从enp0s3的抓包发现,enp0s3发送的是udp的包,协议是OTV协议。以隧道的方式发送,真实的数据包在隧道内部。

1.2.2.2 node2的抓包结果

在ping的同时,对node2的enp0s3、flannel.1、cni0、vethd7bb83ed设备抓包:

# tcpdump -i enp0s3 -ne udp and 'host 192.168.19.166'
15:00:44.492688 08:00:27:69:22:80 > 08:00:27:f3:59:5b, ethertype IPv4 (0x0800), length 148: 192.168.19.166.39338 > 192.168.19.167.otv: OTV, flags [I] (0x08), overlay 0, instance 1
fe:0e:f2:75:aa:44 > 92:e5:0e:57:bd:72, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.492926 08:00:27:f3:59:5b > 08:00:27:69:22:80, ethertype IPv4 (0x0800), length 148: 192.168.19.167.59883 > 192.168.19.166.otv: OTV, flags [I] (0x08), overlay 0, instance 1
92:e5:0e:57:bd:72 > fe:0e:f2:75:aa:44, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

# tcpdump -i flannel.1 -ne icmp
15:00:44.492779 fe:0e:f2:75:aa:44 > 92:e5:0e:57:bd:72, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.492908 92:e5:0e:57:bd:72 > fe:0e:f2:75:aa:44, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

# tcpdump -i cni0 -ne icmp
15:00:44.492812 3a:be:21:07:67:52 > 66:e9:bd:ae:22:49, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.492895 66:e9:bd:ae:22:49 > 3a:be:21:07:67:52, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

# tcpdump -i vethd7bb83ed -ne icmp
15:00:44.492825 3a:be:21:07:67:52 > 66:e9:bd:ae:22:49, ethertype IPv4 (0x0800), length 98: 10.244.1.7 > 10.244.2.6: ICMP echo request, id 9216, seq 0, length 64
15:00:44.492895 66:e9:bd:ae:22:49 > 3a:be:21:07:67:52, ethertype IPv4 (0x0800), length 98: 10.244.2.6 > 10.244.1.7: ICMP echo reply, id 9216, seq 0, length 64

# busybox中eth0网卡的由于所在的namespace不好抓包,所以就没抓

经测试,node2收到ping包后,数据包的转发过程确实是由 enp0s3 发往 cni0 再发往 flannel.1 再发往vethd7bb83ed ,最好在发送到 busybox pod 的eth0

1.2.2.3 数据包的回路

数据包的原路返回。原理与前面类似。

2. 安装vxlan(Dirextrouting)模式flannel

2.1 安装

修改kube-flannel.yml中的"Network": "10.244.0.0/16",与你k8s集群配置的Pod CIDR一直即可。 修改kube-flannel.yml中配置的Backend,增加配置"Directrouting": true:

      "Backend": {
        "Type": "vxlan",
        "Directrouting": true
      }

执行安装

kubectl create -f kube-flannel.yml

2.2 测试

创建测试pod

kubectl run busybox --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox1 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox2 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox3 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox4 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m

测试过程略....

2.3 测试结果

发现数据包的流向如下所示: flannel-vxlan4.png

抓包发现,数据包直接由cni0转发给了enp0s3设备,少了flannel.1封版隧道包的过程。

flannel-vxlan5.png

通过抓包数据发现,该模式有点像Calico的BGP模式。

vxlan(Dirextrouting)模式的效率要比vxlan模式的效率高

3. 安装host-gw模式flannel

3.1 安装

修改kube-flannel.yml中的"Network": "10.244.0.0/16",与你k8s集群配置的Pod CIDR一直即可。 修改kube-flannel.yml中配置的Backend,配置Type的值为host-gw:

      "Backend": {
        "Type": "host-gw"
      }

执行安装

kubectl create -f kube-flannel.yml

3.1.1 安装好之后的架构图

hostgw1.png

这里发现没有flannel.1网络设备了。

数据包通过cni0网桥,通过路由协议由enp0s3网卡设备把数据包转发到其它节点pod。

3.2 测试

创建测试pod

kubectl run busybox --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox1 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox2 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox3 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m
kubectl run busybox4 --image=busybox --generator=run-pod/v1 --command -- sleep 36000m

测试过程略....

3.3 测试结果

3.3.1 测试发现网络的流向如下图

hostgw2.png

3.3.2 Node1 中的busybox3 pod eth0 发包过程

hostgw3.png

3.3.3 Node2 中的busybox pod 收包过程

hostgw4.png