之前的文章讲了k8s ipip模式的使用以及流量路径,本篇文章主要是来讲解一下vxlan 模式下pod 流量是如何通信的。
一、ipip模式转vxlan
- 修改calico backend参数
将calico_backend参数由bird
设置为vxlan
,因为vxlan部署不使用bgp
修改calico controllers的configmap配置
[root@node1 ~]# kubectl edit cm/calico-config -n kube-system
calico_backend: vxlan ###修改为vxlan
重启calico controllers
[root@node1 ~]# kubectl rollout restart deploy/calico-kube-controllers -n kube-system
- 修改calico daemonset
编辑calico daemonset
[root@node1 ~]# kubectl edit ds/calico-node -n kube-system
- name: CALICO_IPV4POOL_IPIP
value: Never ####修改为Never,禁用ipip
- name: CALICO_IPV4POOL_VXLAN
value: Always ####修改为vxlan
2:禁用bird探针检测,因为vxlan不需要bgp,所以需要禁用
livenessProbe:
exec:
command:
- /bin/calico-node
- -felix-live
#- -bird-live ###禁用此处探针检查
periodSeconds: 10
initialDelaySeconds: 10
failureThreshold: 6
timeoutSeconds: 10
readinessProbe:
exec:
command:
- /bin/calico-node
- -felix-ready
#- -bird-live ######禁用此处探针检查
periodSeconds: 10
timeoutSeconds: 10
3:重启ds
[root@node1 ~]# kubectl rollout restart ds/calico-node -n kube-system
- 修改ippool
1:查看当前使用的ippool
[root@node1 ~]# kubectl get ippool
NAME AGE
default-ipv4-ippool 374d
new-ipv4-ippool 168d
2:修改ippool的模式
[root@node1 ~]# kubectl edit ippool/new-ipv4-ippool
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
annotations:
projectcalico.org/metadata: '{"uid":"f8ba255e-198a-4d7b-86b7-d47dc7066960","creationTimestamp":"2023-11-01T06:45:12Z"}'
creationTimestamp: "2023-11-01T06:45:12Z"
generation: 5
name: new-ipv4-ippool
resourceVersion: "62183182"
uid: f8ba255e-198a-4d7b-86b7-d47dc7066960
spec:
allowedUses:
- Workload
- Tunnel
blockSize: 24
cidr: 172.16.0.0/16
ipipMode: Never ####禁用ipip
natOutgoing: true
nodeSelector: all()
vxlanMode: Always ###修改为vxlan
当vxlanMode参数设置为 Always 的时候,三层和二层的通信都通过vxlan的方式进行通信,当值为CrossSubnet的时候只有三层才进行vxlan的方式进行通信。
- 确认没有bgp运行
[root@node1 ~]# calicoctl --allow-version-mismatch node status
Calico process is running.
The BGP backend process (BIRD) is not running.
[root@node1 ~]#
二、vxlan模式讲解
通过上面的操作已经将ipip模式转换成了vxlan模式,环境中多了一个vxlan.calico的设备,这就是平时所说的vtep口,vxlan的封装以及解封装都在这个设备上进行。
[root@node1 ~]# ip -d link show vxlan.calico
12: vxlan.calico: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/ether 66:f9:37:c3:7e:94 brd ff:ff:ff:ff:ff:ff promiscuity 0
vxlan id 4096 local 192.168.5.79 dev eth0 srcport 0 0 dstport 4789 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
可以看到vxlan.calico设备使用的驱动为vxlan
[root@node1 ~]# ethtool -i vxlan.calico
driver: vxlan ###驱动为vxlan
version: 0.1
firmware-version:
expansion-rom-version:
bus-info:
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no
[root@node1 ~]#
#####参数解析如下:
vxlan: 指定要创建或配置的 VXLAN 隧道。
id 4096: 指定 VXLAN 的标识符(VNI,Virtual Network Identifier),这是一个用于区分不同 VXLAN 网络的唯一标识符。
local 192.168.5.59: 指定本地端点vtep的 IP 地址,即 VXLAN 隧道所在主机的 IP 地址。
dev eth0: 指定 VXLAN 隧道所使用的底层网络设备,即用于发送和接收 VXLAN 封装数据包的物理网络接口。
srcport 0 0: 指定 VXLAN 封装数据包的源端口范围,这里的0 0表示源端口范围是从0到0,即随机选择源端口。
dstport 4789: 指定 VXLAN 封装数据包的目标端口,即用于发送和接收 VXLAN 数据包的目标端口号。
nolearning: 禁用学习模式,即不允许 VXLAN 设备自动学习 MAC 地址。
ageing 300: 设置 MAC 地址表的老化时间为 300 秒,即在 300 秒内没有收到关于某个 MAC 地址的数据包时,将该 MAC 地址从表中删除。
noudpcsum: 禁用 UDP 校验和,即不对 VXLAN 封装的 UDP 数据包进行校验和计算。
noudp6zerocsumtx: 禁用 UDPv6 发送时的零检验和,即不对发送的 UDPv6 数据包的校验和字段进行填充。
noudp6zerocsumrx: 禁用 UDPv6 接收时的零检验和,即不对接收的 UDPv6 数据包的校验和字段进行验证。
addrgenmode eui64: 设置地址生成模式为 EUI-64,即使用 EUI-64 地址生成算法生成接口标识符。
numtxqueues 1: 设置发送队列的数量为 1。
numrxqueues 1: 设置接收队列的数量为 1。
gso_max_size 65536: 设置每个数据包的最大 GSO(Generic Segmentation Offload,通用分段卸载)大小为 65536 字节。
gso_max_segs 65535: 设置每个数据包的最大 GSO 段数为 65535
如下为vxlan模式下pod 跨节点通信数据流向图
三、实验模拟
- 启动pod,位于不同节点
[root@node1 ~]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-5977dc5756-4nx25 1/1 Running 0 142m 172.16.154.16 node1 <none> <none>
test-5977dc5756-8xrr9 1/1 Running 0 3h55m 172.16.28.33 node3 <none> <none>
test-5977dc5756-zhg56 1/1 Running 0 97m 172.16.28.34 node3 <none> <none>
本次测试用nod1e 的172.16.154.16 和node3 的172.16.28.34地址
- 进入网络ns
[root@node1 ~]# crictl ps | grep test
dba4c621b262e 12766a6745eea 2 hours ago Running nginx 0 3e0ae3c3fad42 test-5977dc5756-4nx25
[root@node1 ~]# crictl inspect dba4c621b262e | grep -i pid
"pid": 44939,
"pid": 1
"type": "pid"
[root@node1 ~]# nsenter -t 44939 -n bash
[root@node1 ~]# 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
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if17: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether 76:32:89:65:0d:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.16.154.16/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::7432:89ff:fe65:dee/64 scope link
valid_lft forever preferred_lft forever
查看pod的默认路由
[root@node1 ~]# ip r
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
[root@node1 ~]#
############################
############################
可以看到pod的默认路由下一条地址是169.254.1.1,一个不存在于主机上的地址。这里主要是calico 使用了网卡的proxy arp 功能。在Kubernetes Calico网络中,当一个数据包的目的地址不是本网络时,会先发起ARP广播,网关即169.254.1.1收到会将自己的mac地址返回给发送端,后续的请求由这个veth对 进行完成,使用代理arp做了arp欺骗。这样做抑制了arp广播攻击,并且通过代理arp也可以进行跨网络的访问。
###############
根据pod内部eth0@if7 可以得到位于物理机上的veth pair 名称为cali3c99e896108
17: cali3c99e896108@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default
link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::ecee:eeff:feee:eeee/64 scope link
valid_lft forever preferred_lft forever
- 在node1上的pod网卡eth0抓包
[root@node1 ~]# ping 172.16.28.34
PING 172.16.28.34 (172.16.28.34) 56(84) bytes of data.
64 bytes from 172.16.28.34: icmp_seq=1 ttl=62 time=1.53 ms
64 bytes from 172.16.28.34: icmp_seq=2 ttl=62 time=0.715 ms
64 bytes from 172.16.28.34: icmp_seq=3 ttl=62 time=1.10 ms
64 bytes from 172.16.28.34: icmp_seq=4 ttl=62 time=1.09 ms
64 bytes from 172.16.28.34: icmp_seq=5 ttl=62 time=0.724 ms
64 bytes from 172.16.28.34: icmp_seq=6 ttl=62 time=0.849 ms
64 bytes from 172.16.28.34: icmp_seq=7 ttl=62 time=0.568 ms
64 bytes from 172.16.28.34: icmp_seq=8 ttl=62 time=0.893 ms
64 bytes from 172.16.28.34: icmp_seq=9 ttl=62 time=1.03 ms
^C
--- 172.16.28.34 ping statistics ---
9 packets transmitted, 9 received, 0% packet loss, time 8007ms
rtt min/avg/max/mdev = 0.568/0.945/1.532/0.269 ms
[root@node1 ~]#
网卡抓包
[root@node1 ~]# tcpdump -enp -i eth0 -w node1-pod-eth0.pcap
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C22 packets captured
22 packets received by filter
0 packets dropped by kernel
通过wireshark 分析包的内部结构
1:首先根据前面讲到的由于目的ip和podip不在一个网络内以及不知道目的ip的mac地址,pod首先发起广播,默认网关169.254.1.1将自己的mac返回给发起者,即对端veth pair的mac(ee:ee:ee:ee:ee:ee),后面的网络请求由这个veth pai设备进行网络应答。
2:icmp 报文
以下可知,icmp报文中src ip为172.16.154.16,dest ip为172.16.28.34 。src mac为76:32:89:65:0d:ee,dest mac 为ee:ee:ee:ee:ee:ee
- 在node1 pod对端网卡cali抓包
[root@node1 ~]# tcpdump -enp -i cali3c99e896108 -w node-pod-cali3c99e896108.pcap
tcpdump: listening on cali3c99e896108, link-type EN10MB (Ethernet), capture size 262144 bytes
^C38 packets captured
40 packets received by filter
0 packets dropped by kernel
[root@node1 ~]#
由于此veth pair网卡和pod内部网卡eth0报文机几乎一致,此处不做分析
- 在node1 vxlan.calico抓包
[root@node1 ~]# tcpdump -enp -i vxlan.calico -w node-pod-vxlan-calico.pcap
tcpdump: listening on vxlan.calico, link-type EN10MB (Ethernet), capture size 262144 bytes
^C63 packets captured
76 packets received by filter
0 packets dropped by kernel
[root@node1 ~]#
- 在node1 物理机设备eth0抓包
[root@node1 ~]# tcpdump -enp -i vxlan.calico -w node1-pod-peth0.pcap
tcpdump: listening on vxlan.calico, link-type EN10MB (Ethernet), capture size 262144 bytes
^C56 packets captured
66 packets received by filter
0 packets dropped by kernel
从物理网卡上可以看到封装好的vxlan报文,在最外层封装了物理node的ip和mac
- 在node3上的物理eth0网卡抓包
[root@node3 ~]# tcpdump -enp -i eth0 -w node3-pod-peth0.pcap
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C378 packets captured
387 packets received by filter
0 packets dropped by kernel
通过在node3上的eth0网卡抓包分析和node1上的eth0是一致的,未进行任何改变
- 在node3上的vxlan.calico设备抓包
[root@node3 ~]# tcpdump -enp -i vxlan.calico -w node3-pod-vxlan-calico.pcap
tcpdump: listening on vxlan.calico, link-type EN10MB (Ethernet), capture size 262144 bytes
^C81 packets captured
81 packets received by filter
0 packets dropped by kernel
以下可知经过内核中的vxlan模块解封之后,去掉了vxlan的包头,露出原始的报文,src mac和dst mac为各自节点的vtep 设备的mac地址。
- 在node3上的veth pair calixxxxx设备抓包
[root@node3 ~]# tcpdump -enp -i calic6d8dba2cdd -w node3-pod-calic6d8dba2cdd.pcap
tcpdump: listening on calic6d8dba2cdd, link-type EN10MB (Ethernet), capture size 262144 bytes
^C64 packets captured
64 packets received by filter
0 packets dropped by kernel
从vtep 设备出来之后,src 和dst mac分别换成了ee:ee:ee:ee:ee:ee和真实pod的mac地址,并最后通过veth pair设备的proxy arp 功能将网络请求发送给目标地址。
以上就是calico vxlan模式下,数据包的在不同主机的转发路径以及封装过程。首先需要给所有的 pod 配置一条特殊的路由,并利用 veth 的代理 ARP 功能让 pod出来的所有流量转发都变成三层路由转发,然后再利用主机的路由进行转发。这种方式不仅实现了同主机的二三层转发,也能实现跨主机转发。