阅读提示|本文大概12440字 阅读需要20分钟
写在前面:
Flannel 项目是 CoreOS 公司(2018年被Red Hat收购)主推的容器网络方案。事实上,Flannel 项目本身只是一个框架,真正为我们提供容器网络功能的,是 Flannel 的后端实现。目前,Flannel 支持三种后端实现,分别是:
vxlan
host-gw
udp
在容器的overlay网络解决方案中,Flannel作为最早的容器解决方案,其中又以vxlan模式应用最为广泛。本篇文章将通过demo环境演示并且在数据包传输的各个阶段,通tcpdump抓包分析,还原k8s集群中一个pod当中的容器是如何通过Flannel vxlan网络访问到另一个节点上的pod中的容器。
什么是vxlan
VXLAN(Virtual eXtensible Local Area Network,虚拟扩展局域网),是由IETF定义的NVO3(Network Virtualization over Layer 3)标准技术之一,是对传统VLAN协议的一种扩展。VXLAN的特点是将L2的以太帧封装到UDP报文(即L2 over L4)中,并在L3网络中传输。VXLAN本质上是一种隧道技术,在源网络设备与目的网络设备之间的IP网络上,建立一条逻辑隧道,将用户侧报文经过特定的封装后通过这条隧道转发。从用户的角度来看,接入网络的服务器就像是连接到了一个虚拟的二层交换机的不同端口上,可以方便地通信。
vxlan报文格式:
VXLAN Header
增加VXLAN头(8字节),其中包含24比特的VNI字段,用来定义VXLAN网络中不同的租户。此外,还包含VXLAN Flags(8比特,取值为00001000)和两个保留字段(分别为24比特和8比特)。
UDP Header
VXLAN头和原始以太帧一起作为UDP的数据。UDP头中,目的端口号(VXLAN Port)固定为4789,源端口号(UDP Src. Port)是原始以太帧通过哈希算法计算后的值。
Outer IP Header
封装外层IP头。其中,源IP地址(Outer Src. IP)为源VM所属VTEP的IP地址,目的IP地址(Outer Dst. IP)为目的VM所属VTEP的IP地址。
Outer MAC Header
封装外层以太头。其中,源MAC地址(Src. MAC Addr.)为源VM所属VTEP的MAC地址,目的MAC地址(Dst. MAC Addr.)为到达目的VTEP的路径中下一跳设备的MAC地址。
这里重点说一下VTEP设备,这个是是本篇文章的重点。VTEP(VXLAN Tunnel Endpoints,VXLAN隧道端点)是VXLAN网络的边缘设备,是VXLAN隧道的起点和终点,VXLAN对用户原始数据帧的封装和解封装均在VTEP上进行。VTEP既可以是一台独立的网络设备,也可以是在服务器中的虚拟交换机。容器发出的原始数据帧,在VTEP上被封装成VXLAN格式的报文,并在IP网络中传递到另外一个VTEP上,并经过解封转还原出原始的数据帧,最后转发给目标容器。
综合上面的内容,我们大概可以了解flannel的vxlan网络方案的大概数据传输流程,接下来我们将在各个流程进行tcpdump抓包分析,对整个过程进行拆解(这对k8s中通信故障定位至关重要),为了方便理解,我根据我的demo环境画了个简图,后续demo环境也如图所示:
演示方式:
(1)在node1 coffee-1(10.244.1.32)容器内部访问node2 coffee-2(10.244.2.40)节点上的web服务,访问方式:curl http://10.244.2.40:8080/coffee。
(2)触发访问流量的同时会在coffee-1容器的eth0网卡、node1宿主机cni0网卡、flannel.1网卡、eth0网卡4个位置通过tcpdump抓包,拆解数据包在传输过程中源目IP及Mac变化,加深flannel vxlan工作模式的理解。
demo环境介绍
一、node节点及Flannel VTEP信息
[root@k8s-master01 ~]# kubectl get node -o yaml | grep -A3 Vtep
flannel.alpha.coreos.com/backend-data: '{"VtepMAC":"00:50:56:ab:9e:f8"}'
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: "true"
flannel.alpha.coreos.com/public-ip: 10.1.11.89
--
{"apiVersion":"v1","kind":"Node","metadata":{"annotations":{"flannel.alpha.coreos.com/backend-data":"{\"VtepMAC\":\"00:50:56:ab:9e:f8\"}","flannel.alpha.coreos.com/backend-type":"vxlan","flannel.alpha.coreos.com/kube-subnet-manager":"true","flannel.alpha.coreos.com/public-ip":"10.1.11.89"},"name":"bigip"},"spec":{"podCIDR":"10.244.3.0/24"}}
node.alpha.kubernetes.io/ttl: "0"
creationTimestamp: "2021-01-04T10:38:11Z"
name: bigip
--
flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"5a:1c:17:98:e6:eb"}'
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: "true"
flannel.alpha.coreos.com/public-ip: 10.1.11.90
--
flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"1e:0f:7e:bb:26:2b"}'
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: "true"
flannel.alpha.coreos.com/public-ip: 10.1.11.91
--
flannel.alpha.coreos.com/backend-data: '{"VNI":1,"VtepMAC":"ce:b8:fc:a6:06:d2"}'
flannel.alpha.coreos.com/backend-type: vxlan
flannel.alpha.coreos.com/kube-subnet-manager: "true"
flannel.alpha.coreos.com/public-ip: 10.1.11.92
二、node节各网卡IP地址及MAC地址信息
#node01
[root@k8s-node01 tmp]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:ab:81:34 brd ff:ff:ff:ff:ff:ff
inet 10.1.11.91/24 brd 10.1.11.255 scope global noprefixroute ens192
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:ae:ed:49:a1 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether 1e:0f:7e:bb:26:2b brd ff:ff:ff:ff:ff:ff
inet 10.244.1.0/32 brd 10.244.1.0 scope global flannel.1
valid_lft forever preferred_lft forever
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 8e:62:4b:7f:4b:ab brd ff:ff:ff:ff:ff:ff
inet 10.244.1.1/24 brd 10.244.1.255 scope global cni0
valid_lft forever preferred_lft forever
33: veth33136931@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 4a:28:d3:7d:dd:3f brd ff:ff:ff:ff:ff:ff link-netnsid 11
#node02
[root@k8s-node02 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
2: eno16780032: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether 00:50:56:ab:00:3b brd ff:ff:ff:ff:ff:ff
inet 10.1.11.92/24 brd 10.1.11.255 scope global noprefixroute eno16780032
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:44:44:ad:a8 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
4: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether ce:b8:fc:a6:06:d2 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.0/32 brd 10.244.2.0 scope global flannel.1
valid_lft forever preferred_lft forever
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 4a:ab:da:9d:27:34 brd ff:ff:ff:ff:ff:ff
inet 10.244.2.1/24 brd 10.244.2.255 scope global cni0
valid_lft forever preferred_lft forever
三、coffee container配置信息
#coffee-01(10.244.1.32)和coffee-02(10.244.2.40) pod信息
[root@k8s-master01 ~]# kubectl get pod -o wide -l app=coffee
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coffee-67c6f7c5fd-2pfr4 1/1 Running 0 5h45m 10.244.1.32 k8s-node01 <none> <none>
coffee-67c6f7c5fd-c2lpg 1/1 Running 0 5h45m 10.244.2.40 k8s-node02 <none> <none>
#查看coffee-01(10.244.1.32)pod信息
[root@k8s-master01 ~]# kubectl describe pod/coffee-67c6f7c5fd-2pfr4
Name: coffee-67c6f7c5fd-2pfr4
Namespace: default
Priority: 0
Node: k8s-node01/10.1.11.91
Start Time: Fri, 15 Jan 2021 12:58:40 +0800
Labels: app=coffee
pod-template-hash=67c6f7c5fd
Annotations: <none>
Status: Running
IP: 10.244.1.32
Controlled By: ReplicaSet/coffee-67c6f7c5fd
Containers:
coffee:
Container ID: docker://dd9a0cdaf775d4bedc072b84d90cf99c7f08e303d75db12ac1e71b4baddf791d
Image: nginxdemos/nginx-hello:plain-text
Image ID: docker-pullable://nginxdemos/nginx-hello@sha256:71b4a63b6d31ae846c431d0dc7134bc0ae490fe4698eebf3ed297668db3b2939
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 15 Jan 2021 12:59:34 +0800
Ready: True
#查看coffee-01(10.244.1.32)容器进程ID,后续将会进去该容器network namespace进行curl http://10.244.2.40:8080/coffee 发起对coffee-2的访问
[root@k8s-node01 ~]# docker inspect -f {{.State.Pid}} dd9a0cdaf775d4bedc0
30510
网卡IP及MAC对应关系汇总
为了方便后续对照各个数据包传输过程中源目IP地址及MAC地址的改变,对以上信息进行整理,如下所示:
node | 网卡 | ip | mac |
k8s-node01 | ens192 | 10.1.11.91/24 | 00:50:56:ab:81:34 |
flannel.1 | 10.244.1.0/32 | 1e:0f:7e:bb:26:2b | |
cni0 | 10.244.1.1/24 | 8e:62:4b:7f:4b:ab | |
k8s-node02 | eno16780032 | 10.1.11.92/24 | 00:50:56:ab:00:3b |
flannel.1 | 10.244.2.0/32 | ce:b8:fc:a6:06:d2 | |
cni0 | 10.244.2.1/24 | 4a:ab:da:9d:27:34 |
访问流程及抓包点
根据数据包的流向:
1.数据包从容器coffee-1的eth0接口发出
2.cni接口收到从容器coffee-1的eth0接口发出的数据包,然后根据主机路由将数据包发送给flannel.1
3.flannel.1将收到的数据包交给flanneld进程对数据包进行vxlan封包
4.对封装完毕的vxlan数据包添加IP包头和链路层mac信息后从主机接口eth0(ens192)发出
tcpdump抓包点:
1.node01节点上容器coffee-1的eth0接口抓包(进入容器network namespace抓包)
#查看coffee-01(10.244.1.32)pod信息
[root@k8s-master01 ~]# kubectl describe pod/coffee-67c6f7c5fd-2pfr4
Name: coffee-67c6f7c5fd-2pfr4
Namespace: default
Priority: 0
Node: k8s-node01/10.1.11.91
Start Time: Fri, 15 Jan 2021 12:58:40 +0800
Labels: app=coffee
pod-template-hash=67c6f7c5fd
Annotations: <none>
Status: Running
IP: 10.244.1.32
Controlled By: ReplicaSet/coffee-67c6f7c5fd
Containers:
coffee:
Container ID: docker://dd9a0cdaf775d4bedc072b84d90cf99c7f08e303d75db12ac1e71b4baddf791d
Image: nginxdemos/nginx-hello:plain-text
Image ID: docker-pullable://nginxdemos/nginx-hello@sha256:71b4a63b6d31ae846c431d0dc7134bc0ae490fe4698eebf3ed297668db3b2939
Port: 8080/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 15 Jan 2021 12:59:34 +0800
Ready: True
#查看coffee-01(10.244.1.32)容器进程ID,后续将会进去该容器network namespace进行curl http://10.244.2.40:8080/coffee 发起对coffee-2的访问
[root@k8s-node01 ~]# docker inspect -f {{.State.Pid}} dd9a0cdaf775d4bedc0
30510
[root@k8s-node01 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
3: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 4a:9e:70:55:74:ac brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.32/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
#使用宿主机tcpdump对coffee-1容器eth0抓包
[root@k8s-node01 ~]# tcpdump -nni eth0 -s0 -w var/tmp/containernode1coffee.pcap
2.node01宿主机cni0接口抓包
[root@k8s-node01 ~]# tcpdump -nni cni0 -s0 -w var/tmp/node1cin.pcap -v
3.node01宿主机flannel.1接口抓包
[root@k8s-node01 ~]# tcpdump -nni flannel.1 -s0 -w /var/tmp/node1flannel.pcap -v
4.node01宿主机eth0(ens192)接口抓包
[root@k8s-node01 ~]# tcpdump -nni ens192 -s0 -w var/tmp/hostens192.pcap -v
在coffee-1执行curl命令
由于coffee-1容器中没有curl命令,将使用宿主机curl命令测试,和直接在容器中curl一样,在容器中执行ip a可以看到coffee-1容器eth0口的ip地址(10.244.1.32/24)和Mac(4a:9e:70:55:74:ac)
#30510为coffee-1容器进程ID
[root@k8s-node01 ~]# nsenter -t 30510 -n
[root@k8s-node01 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 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
3: eth0@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 4a:9e:70:55:74:ac brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.244.1.32/24 brd 10.244.1.255 scope global eth0
valid_lft forever preferred_lft forever
#执行完curl命令后,可以看到10.2.44.2.40已经返回来结果
[root@k8s-node01 ~]# curl http://10.244.2.40:8080/coffee
Server address: 10.244.2.40:8080
Server name: coffee-67c6f7c5fd-c2lpg
Date: 15/Jan/2021:07:28:27 +0000
URI: /coffee
Request ID: 72702fed00222cfa73034f13446ef045
抓包展示
执行完上述curl http://10.244.2.40:8080/coffee命令后,此时已经获取到了数据包。
1.node01节点上容器coffee-1的eth0接口抓包
从容器eth0接口发出来的原始数据包,可以看到目的Mac地址为cni0网卡mac
源IP地址/端口:10.244.1.32:46012 目的IP地址/端口:10.244.2.40:8080
源Mac:4a:9e:70:55:74:ac 目的Mac:8e:62:4b:7f:4b:ab
2.node01宿主机cni0接口抓包
通过在cni0网卡上的抓包对比容器coffee-1网卡eth0的数据包,源目IP地址及源目Mac相同。
3.node01宿主机flannel.1接口抓包
从宿主机flannel.1网卡抓包看到了一些不同,很有意思,可以看到源目IP地址和端口并未发生变化,但此时的源目Mac变成了node1和node2 flannel.1网卡的Mac(隧道两端Vtep的Mac地址)
源IP地址/端口:10.244.1.32:46012 目的IP地址/端口:10.244.2.40:8080
源Mac:1e:0f:7e:bb:26:2b 目的Mac:ce:b8:fc:a6:06:d2
4.node01宿主机eth0(ens192)接口抓包
从宿主机ens192网卡抓包又看到了一下变化,此时的数据包已经变得“面目全非”了。可以看到源目IP地址和端口发生变化,目的端口变成UDP 8472端口,并且源目Mac变成了node1和node2 eth0网卡的Mac(物理网卡mac)
最外层IP包头及mac信息:
源IP地址/端口:10.1.11.91:52543 目的IP地址/端口:10.1.11.92:8472
源Mac:00:50:56:ab:81:34目的Mac:00:50:56:ab:00:3b
Vxlan数据包IP包头及Mac信息:
源IP地址/端口:10.244.1.32:46012 目的IP地址/端口:10.244.2.40:8080
源Mac:1e:0f:7e:bb:26:2b 目的Mac:ce:b8:fc:a6:06:d2
数据包如下:
总结
从上述各个阶段抓包分析可以看出flannel vxlan工作模式下容器跨主机通信下的整个访问流程(对照数据包食用,效果更佳):
(1) node1容器coffee首先查询本容器的默认路由,将数据包发送给了网关cni0,此时数据包进入宿主机
[root@k8s-master01 ~]# kubectl exec coffee-67c6f7c5fd-2pfr4 -- route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.244.1.1 0.0.0.0 UG 0 0 0 eth0
10.244.0.0 10.244.1.1 255.255.0.0 UG 0 0 0 eth0
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
[root@k8s-master01 ~]# kubectl exec coffee-67c6f7c5fd-2pfr4 -- arp -v
? (10.244.1.1) at 8e:62:4b:7f:4b:ab [ether] on eth0
(2) node1 cni0收到收据包后根据宿主机路由,将数据包转发给flannel.1
[root@k8s-node01 ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.1.11.1 0.0.0.0 UG 100 0 0 ens192
10.1.11.0 0.0.0.0 255.255.255.0 U 100 0 0 ens192
10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1
10.244.1.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0
10.244.2.0 10.244.2.0 255.255.255.0 UG 0 0 0 flannel.1
10.244.3.0 10.244.3.0 255.255.255.0 UG 0 0 0 flannel.1
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
(3) node1 flannel.1收到数据包后,通过宿主机主机路由配置、ARP信息,查询到node2 vtep设备的IP地址及Mac信息,由linux内核进行封包(第一次封包:此数据包又称原始数据包或内部数据包)
[root@k8s-node01 ~]# ip neigh show dev flannel.1
10.244.0.0 lladdr 5a:1c:17:98:e6:eb PERMANENT
10.244.2.0 lladdr ce:b8:fc:a6:06:d2 PERMANENT
10.244.3.0 lladdr 00:50:56:ab:9e:f8 PERMANENT
(4) 为了实现vxlan跨主机通信,linux内核会在步骤三的原始数据包头封装vxlan标示VNI,然后封装到一个UDP数据包中,然后后查询FDB(转发数据库),查询到去往vtep设备ce:b8:fc:a6:06:d2的数据包,需要将此包送给10.1.11.92,然后进行第二次封包。最终数据包从node1物理接口eth0发出。
[root@k8s-node01 ~]# bridge fdb show dev flannel.1
ce:b8:fc:a6:06:d2 dst 10.1.11.92 self permanent
00:50:56:ab:9e:f8 dst 10.1.11.89 self permanent
5a:1c:17:98:e6:eb dst 10.1.11.90 self permanent
06:ee:53:fd:1a:a9 dst 10.1.11.92 self permanent
36:36:40:76:1c:9f dst 10.1.11.90 self permanent
demo数据包下载
整个demo演示中tcpdump抓包已经上传到百度网盘,后台回复【flannel】即可下载。
参考链接:
https://support.huawei.com/enterprise/zh/doc/EDOC1100087027#ZH-CN_TOPIC_0254803582
- EOF -
觉得本文对你有帮助,请分享给更多人
扫码关注「小咩社长」