【kubernetes系列】flannel之vxlan模式分析

概述

在Kubernetes中要保证容器之间网络互通,网络至关重要。而Kubernetes本身并没有自己实现容器网络,而是而是借助CNI标准,通过插件化的方式自由接入进来。在容器网络接入进来需要满足如下基本原则:

  • Pod无论运行在任何节点都可以互相直接通信,而不需要借助NAT地址转换实现。
  • Node与Pod可以互相通信,在不限制的前提下,Pod可以访问任意网络。
  • Pod拥有独立的网络栈,Pod看到自己的地址和外部看见的地址应该是一样的,并且同个Pod内所有的容器共享同个网络栈。

kubernetes中的网络通信

kubernetes中的网络通信大致分为以下几种:

  • Pod内容器间的通信(lo)
  • Pod之间的通信 pod IP <—–> pod IP(flannel、calico)
  • Pod与Service之间的通信 podIP <—–> ClusterIP(iptables、ipvs)
  • Service与集群外部的通信 ClusterIP <—–> 集群外部

常见的CNI插件:

  • flannel
  • calico
  • canel
  • kube-router

Flannel

Flannel是CoreOS开源的,Overlay模式的CNI网络插件,Flannel在每个集群节点上运行一个flanneld的代理守护服务,为每个集群节点(host)分配一个子网(subnet),同时为节点上的容器组(pod)分配IP,在整个集群节点间构建一个虚拟的网络,实现集群内部跨节点通信。Flannel的数据包在集群节点间转发是由backend实现的,目前,已经支持核心官方推荐的模式有UDP、VXLAN、HOST-GW,以及扩展试用实验的模式有IPIP,AWS VPC、GCE、Ali VPC、Tencent VPC等路由,其中vxlan模式在实际的生产中使用最多,下面以vxlan模式为例。
在这里插入图片描述

从图里看每个宿主机都有一个flannel1(flannel.1)的设备,就是VXLAN所需的VTEP设备(flannel1“用于VXLAN报文的封装和解封装”),它既有IP地址也有MAC地址。如现在container1 访问 container2,当container1发出请求后,这个目的的地址是10.244.1.3的IP包,会先出现在cni0网桥,然后被路由到本机flanner1设备上处理,也就是说,来到了“隧道”的出口。既目的宿主机的VTEP设备(就是flannel1 设备)。
margu
当所有Node启动后,我们可以在k8s-m2上可以看到多个flannel1 网卡的路由信息,是因为flanneld启动后创建的。

[root@k8s-m2 ~]# ifconfig  flannel.1
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 10.244.0.0  netmask 255.255.255.255  broadcast 0.0.0.0
        ether 96:69:e2:7d:bd:32  txqueuelen 0  (Ethernet)
        RX packets 10177  bytes 977133 (954.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 10177  bytes 1787417 (1.7 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@k8s-m2 ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.2.254   0.0.0.0         UG    0      0        0 ens32
10.244.0.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.1.0      10.244.1.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 ens32
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
192.168.2.0     0.0.0.0         255.255.255.0   U     0      0        0 ens32

从上图看到10.244.1.0就是k8s-m3的VTEP设备(flannel1)的IP地址,而这些VTEP设备之间通讯就需要想办法组成一个虚拟的二层网络,既:通过二层数据帧进行通信,而k8s-m2上的VTEP设备在收到原始报文后,就要想办法把原始报文加一个目的MAC地址,封装成二层数据帧,然后发送给目的VTEP设备。这里需要解决一个问题目的VTEP设备的MAC地址是什么?

根据路由表信息我们知道了目的VTEP设备的IP地址,而根据三层IP地址查询二层MAC地址正是ARP表的功能。而这里用ARP表的记录,也就是flanneld进程在Node2节点启动时,自动添加到Node1上的。如下:

$ ip neigh show dev flannel.1
10.244.1.0 lladdr e6:05:f1:5f:d7:13 PERMANENT
10.244.2.0 lladdr 46:1b:96:8c:b0:cf PERMANENT

有了这个MAC地址linux内核就可以开始二层封装了,上面提到的MAC地址,对宿主机的二层网络没有任何意义,所以上述封装的数据帧不能在宿主机的二层网络里传输,为了方便概述,我们把上述数据帧称为内部数据帧。所以Linux内核还要把内部数据帧进一步封装成宿主机网络的一个普通数据帧,好让它载着内部数据帧,通过外网网卡(如eth0 、ens33等)进行传输。这次封装我们称为外部数据帧,为了实现这个搭便车的机制,Linux内核在封装内部数据帧前面,加上特殊的VXLAN头,用来表示这个乘客实际上是VXLAN使用的数据帧。而这个VXLAN头里有一个重要的标志VNI,它是识别某个数据帧是不是应该归属自己处理的标志。而flannel中,VNI的值是1,这也是为什么宿主机的VTEP设备都叫做flannel1的原因。这个时候linux内核会把这数据帧封装一个UDP报文在转发出去。虽然k8s-m2的flannel1知道k8s-m3的flannel1的MAC地址,但是不知道k8s-m3对外网卡的MAC的地址,也就是UDP该发往那台主机,实际上flannel1还要扮演一个网桥的角色,在二层网络进行UDP转发,而在Linux内核里面,网桥设备进行转发的依据来自FDB的转发数据库。这个flannel网桥对应的FDB信息,就是flannel进程维护的,他的内容如下:

$  bridge fdb show flannel.1|grep  46:1b:96:8c:b0:cf
46:1b:96:8c:b0:cf dev flannel.1 dst 192.168.2.140 self permanent

我们可以看到发往的IP地址是192.168.2.142的主机,显然这台主机就是 k8s-m3,UDP要转发的目的也找到了。接下来就是宿主机网络封包的过程了。
在这里插入图片描述

下面让我们来看看,当有一个EventAdded到来时,flanneld如何进行配置,以及封包是如何在flannel网络中流动的。
在这里插入图片描述

借用上图所示,当主机B启动了一个flanneld的服务后,将从etcd中读取配置信息,并请求获取子网的租约。所有Node上的flanneld都依赖etcd cluster来做集中配置服务,etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化。flanneld一旦获取子网租约、配置后端后,会将一些信息写入/run/flannel/subnet.env文件。它会将自己的subnet 10.1.16.0/24和Public IP 192.168.0.101写入etcd中,它还会将vtep设备flannel.1的mac地址也写入etcd中。

之后,主机A会得到EventAdded事件,并从中获取主机B添加至etcd的各种信息。这个时候,它会在本机上添加三条信息:

  • 路由信息:所有通往目的地址10.1.16.0/24的封包都通过vtep设备flannel.1设备发出,发往的网关地址为10.1.16.0,即主机B中的flannel.1设备。
$ ip route list
...
10.1.16.0/24 via 10.1.16.0 dev flannel.1 onlink
...
  • fdb信息:MAC地址为flannel的mac地址,发往10.1.16.0的数据包都将通过vxlan首先发往目的地址192.168.0.101,即主机B
$ ip neigh show dev flannel.1
10.1.16.0 lladdr 46:1b:96:8c:b0:cf PERMANENT

$  bridge fdb show flannel.1  | grep46:1b:96:8c:b0:cf
46:1b:96:8c:b0:cf dev flannel.1 dst 192.168.0.101 self permanent
  • arp信息:网关地址10.1.16.0的MAC地址为flannel的mac地址
[root@k8s-m2 ~]# arp -v
Address                  HWtype  HWaddress           Flags Mask            Iface
....
10.1.16.0               ether   46:1b:96:8c:b0:cf   CM                    flannel.1
....

参数说明:

  • Network flannel使用CIDR格式(10.244.0.0/16)的网络地址,用于为pod的配置网络功能
  • SubnetLen表示每个主机分配的subnet大小,我们可以在初始化时对其指定,否则使用默认配置。在默认配置的情况下SubnetLen配置为24(表示24位子网掩码)。
  • SubnetMin是集群网络地址空间中最小的可分配的subnet,可以手动指定,否则默认配置为集群网络地址空间中第一个可分配的subnet。例如对于”10.244.0.0/16″,当SubnetLen为24时,第一个可分配的subnet为”10.244.1.0/24″
  • SubnetMax表示最大可分配的subnet,对于”10.244.0.0/16″,当subnetLen为24时,SubnetMax为”10.244.255.0/24″
  • Backend.Type 为flannel指定使用的backend的类型,类型分三种:vxlan、host-gw、udp,如未指定,则默认为“vxlan”
  • –ip-masq=true 为网桥上的IP地址开启IP伪装(代表需要为其配置SNAT)
    注意:Backend为vxlan时,其中会存储vtep设备的mac地址至etcd中

抓包分析

以flannel常用的vxlan模式发送icmp包为例,ping不在同一节点上的pod。如下,default命名空间下的两个nginx pod分别位于不同的节点上。

[root@k8s-m1 k8s-total]# kubectl get po -o wide
NAME                        READY   STATUS    RESTARTS   AGE     IP            NODE     NOMINATED NODE   READINESS GATES
liveness-exec-pod           1/1     Running   1          4d18h   10.244.0.10   k8s-m2   <none>           <none>
my-nginx-5b8555d6b8-cdqbm   1/1     Running   1          4d18h   10.244.0.13   k8s-m2   <none>           <none>
my-nginx-5b8555d6b8-vxcsj   1/1     Running   1          7d1h    10.244.2.7    k8s-m1   <none>           <none>

由于只有liveness-exec-pod 这个容器自带有ping命名,我就用10.244.0.10 这个pod去ping另一个节点上的 10.244.2.7 这个ip的pod,如下:

#查看对应节点上生成的veth网卡,在如上的k8s-m2上
[root@k8s-m1 k8s-total]# kubectl exec -it liveness-exec-pod -- /bin/sh
/ # cat /sys/class/net/eth0/iflink
20

/ # route  -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.244.0.1      0.0.0.0         UG    0      0        0 eth0
10.244.0.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0
10.244.0.0      10.244.0.1      255.255.0.0     UG    0      0        0 eth0
#注意此次的eth0是容器内部的eth0

/ # ping 10.244.2.7
PING 10.244.2.7 (10.244.2.7): 56 data bytes
64 bytes from 10.244.2.7: seq=0 ttl=62 time=0.355 ms
64 bytes from 10.244.2.7: seq=1 ttl=62 time=0.436 ms
64 bytes from 10.244.2.7: seq=2 ttl=62 time=0.309 ms
#一直ping着

到k8s-m2进行抓包,抓取对应的网卡

#先查看路由,到10.244.2.0开始是到网卡flannel.1
[root@k8s-m2 ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.2.254   0.0.0.0         UG    0      0        0 ens32
10.244.0.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.1.0      10.244.1.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 ens32
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker_gwbridge
192.168.2.0     0.0.0.0         255.255.255.0   U     0      0        0 ens32

[root@k8s-m2 ~]# ip a
17: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default 
    link/ether da:fa:5e:60:ad:7c brd ff:ff:ff:ff:ff:ff
    inet 10.244.0.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
.....
20: vethc1f65864@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 66:55:12:cd:be:bb brd ff:ff:ff:ff:ff:ff link-netnsid 5
21: vethe06e914a@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 1e:d7:3e:c5:e1:e8 brd ff:ff:ff:ff:ff:ff link-netnsid 6
22: veth0e3aa9d1@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 1a:16:64:f3:d5:8c brd ff:ff:ff:ff:ff:ff link-netnsid 7
23: veth3c3c8697@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 3e:5b:82:c3:c4:44 brd ff:ff:ff:ff:ff:ff link-netnsid 8
24: veth131f9e56@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default 
    link/ether 06:af:26:b3:fa:17 brd ff:ff:ff:ff:ff:ff link-netnsid 9

[root@k8s-m2 ~]# tcpdump -i vethc1f65864 -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vethc1f65864, link-type EN10MB (Ethernet), capture size 262144 bytes
14:38:32.734140 IP 10.244.0.10 > 10.244.0.13: ICMP echo request, id 32555, seq 265, length 64
14:38:32.734242 IP 10.244.0.13 > 10.244.0.10: ICMP echo reply, id 32555, seq 265, length 64
14:38:33.734364 IP 10.244.0.10 > 10.244.0.13: ICMP echo request, id 32555, seq 266, length 64
14:38:33.734431 IP 10.244.0.13 > 10.244.0.10: ICMP echo reply, id 32555, seq 266, length 64
14:38:34.734583 IP 10.244.0.10 > 10.244.0.13: ICMP echo request, id 32555, seq 267, length 64
14:38:34.734681 IP 10.244.0.13 > 10.244.0.10: ICMP echo reply, id 32555, seq 267, length 64
^C

[root@k8s-m2 ~]# tcpdump -i cni0 -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on cni0, link-type EN10MB (Ethernet), capture size 262144 bytes
15:06:25.449188 IP 10.244.0.10 > 10.244.2.7: ICMP echo request, id 3358, seq 15, length 64
15:06:25.449531 IP 10.244.2.7 > 10.244.0.10: ICMP echo reply, id 3358, seq 15, length 64
15:06:26.449342 IP 10.244.0.10 > 10.244.2.7: ICMP echo request, id 3358, seq 16, length 64
15:06:26.449604 IP 10.244.2.7 > 10.244.0.10: ICMP echo reply, id 3358, seq 16, length 64

[root@k8s-m2 ~]# tcpdump -i flannel.1  -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes
15:06:43.451706 IP 10.244.0.10 > 10.244.2.7: ICMP echo request, id 3358, seq 33, length 64
15:06:43.451957 IP 10.244.2.7 > 10.244.0.10: ICMP echo reply, id 3358, seq 33, length 64

[root@k8s-m2 ~]# tcpdump -i ens32  -p icmp -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens32, link-type EN10MB (Ethernet), capture size 262144 bytes
15:06:54.411530 IP 192.168.2.141 > 14.119.104.189: ICMP echo request, id 1, seq 21189, length 64
15:06:54.448813 IP 14.119.104.189 > 192.168.2.141: ICMP echo reply, id 1, seq 21189, length 64

在flannel的vxlan模式下不在同一节点上的pod,通过抓包分析结果如下:

  • 容器内部的流量通过内部网卡eth0网桥先到达cni0网卡(上面是 cni0网卡地址是10.244.0.1)
  • cni0上的流量再根据相应宿主机上的路由表,报文将送到flannel.1设备(10.244.0.0)
  • flannel.1是一个tun设备,工作在三层,它接收到的是一个RAW IP包(无MAC信息)。它会查看数据包的目的ip,从flanneld获取对端隧道设备的必要信息,封装数据包并转发至eth0网卡。
  • 随后eth0网卡会将发送至另一个pod所在的宿主机,该宿主机的网卡接收到数据包,发现数据包为overlay数据包,解开外层封装,并发送内层封装到flannel.1设备
  • flannel.1 设备查看数据包,根据路由表匹配,将数据发送给cni0设备,然后转发给相应的pod。

处在同一节点上的pod其实是不会经过cni0和flannel的,直接通过docker0网桥进行转发。

关于flannel的更多模式,可以前往https://blog.csdn.net/margu_168/article/details/131828605查看。

更多关于kubernetes的知识分享,请前往博客主页。编写过程中,难免出现差错,敬请指出

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

margu_168

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值