简单介绍:
在 k8s 中,最重要的就是 CNI,CRI,CSI,其中 :
CRI 为容器运行时,目前使用广泛的有 Container,Gvisor,Kata,其中 Container 因为其出身,目前较受欢迎
CSI 即为 Storage Interface,存储接口,难度较高,需要专业人才来精通
CNI 为容器网络接口,目前来说,学习程度相比其他两个来说难度适中,而且 CNI 也有较多,比如 Flannel,Calico,Cilium,Multus 等等,而且每种 CNI 也有多种模式,比如 Flannel 的 UDP 、Vxlan、IPIP、Host-GW 等多种模式, Calico 的 IPIP、Vxlan、BGP-FullMesh、BGP-RR 等等多种模式, Multus 为 SRV 的硬件级通信,偏向运营商解决方案 但是仅有 CNI 是远远不够的,CNI 帮我们解决通信,我们还需要有 CNI 对应的
IPAM ( IP Address Management ) 来进行管理,否则容器内部的 IP 地址就不能很好的管理,解决地址冲突等等问题 PS:本文章拿 Flannel Vxlan 模式、 Calico IPIP 模式进行说明,帮助理解
underlay和overlay
所谓underlay,也就是没有在宿主机网络上的虚拟层,容器和宿主机处于同一个网络层面上。
在这种情形下,Kubernetes 内外网络是互通的,运行在kubernetes中的容器可以很方便的和公司内部已有的非云原生基础设施进行联动,比如DNS、负载均衡、配置中心等,而不需要借助kubernetes内部的DNS、ingress和service做服务发现和负载均衡。
所谓overlay,其实就是在容器的IP包外面附加额外的数据包头,然后整体作为宿主机网络报文中的数据进行传输。容器的IP包加上额外的数据包头就用于跨主机的容器之间通信,容器网络就相当于覆盖(overlay)在宿主机网络上的一层虚拟网络。
同一 POD 内部通信原理
不论是 Flannel Vxlan、还是 Calico IPIP ,同一 POD 内部不同 Container 之间的通信,采用的 Container 模式,都是共享同一个 Network Namespace ,于宿主机隔离,新创建的容器不会新建自己的网卡,配置自己的 IP ,而是和一个指定的容器共享 IP、端口范围等,两个容器的进程是可以通过 Lo 网卡(127.0.0.1)通信,但是除网络方面,其他的文件系统,进程等都是隔离的。
# container-1
sh-4.2$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1440
inet 10.234.119.43 netmask 255.255.255.255 broadcast 10.234.119.43
ether 0e:57:05:1c:e3:12 txqueuelen 0 (Ethernet)
RX packets 4662749 bytes 4220056657 (3.9 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 4587887 bytes 4242002557 (3.9 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
RX packets 20357 bytes 110675601 (105.5 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 20357 bytes 110675601 (105.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# container-2
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 0E:57:05:1C:E3:12
inet addr:10.234.119.43 Bcast:10.234.119.43 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1440 Metric:1
RX packets:4663185 errors:0 dropped:0 overruns:0 frame:0
TX packets:4588351 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:4220103158 (3.9 GiB) TX bytes:4242042363 (3.9 GiB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:20357 errors:0 dropped:0 overruns:0 frame:0
TX packets:20357 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:110675601 (105.5 MiB) TX bytes:110675601 (105.5 MiB)
# 通过上边可以看到,container-2 复用 container-1 的网卡
同节点不同 POD 通信原理
1. Flannel Vxlan 模式同节点不同 POD 通信
前提条件 [重要]:
1. Ifconfig 命令看到的网卡,不论是实体网卡还是 Docker 等应用程序虚拟出来的网卡,均为内核级别,在内核层
2. 通常情况,在 Flannel 上解决同节点 Pod 之间的通信是通过的 Linux Bridge ,与 Docker 不一样的是,Flannel 使用的 Linux Bridge 为 cni0,而不是 docker0
3. Vxlan 是属于 Veth 设备,在 overlay 这一层,属于大二层,二层(数据链路层)通信采用的是 MAC 地址
Vxlan 是需要 Veth pair 对来进行通信,通过绑定 cni0 这个 flannel 的桥接网卡,然后另一端绑定不同的 veth 虚拟网卡来进行通信,相当于交换机的不同端口。 kubernetes 使用 veth pair 将容器与主机的网络协议栈连接起来,从而使数据包可以进出 Pod,容器放在主机根 network namespace 中的 veth pair 连接到网桥 cbr0,可以让同一节点的各个 Pod 之间相互通信,如图所示:
2. Calico IPIP 模式同节点不同 POD 通信
kubernetes 使用 veth pair 将容器与主机的网络协议栈连接起来,从而使数据包可以进出 Pod,容器放在主机根 network namespace 中的 veth pair 连接到网卡 eth0,然后因为其是 32位主机ip,所以只能通过路由的方式寻找本机路由,通过路由找到目标 IP 的 Iface ,然后就能通过 veth pair 寻找到目标 Pod 。 通过最终抓包我们可以看到,ee:ee:ee:ee:ee:ee > ff:ff:ff:ff:ff:ff 这种是 Proxy_ARP 的方式进行封包,因为其实是tun 三层寻找路由,所以 MAC 地址就不太重要,与二层无关,node内的Pod访问不会用到ipip隧道封装。
<sub>]# kubectl get pod -o wide | grep node-2
net-tools-6d94675c74-p4qmb 1/1 Running 0 100s 10.233.69.18 node-2 <none> <none>
net-tools-6d94675c74-thw2p 1/1 Running 0 100s 10.233.69.17 node-2 <none> <none>
# node-2 环境
</sub>]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.2.1 0.0.0.0 UG 0 0 0 eth0
10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
10.233.69.0 0.0.0.0 255.255.255.0 U 0 0 0 *
*<strong> # 目标路由
10.233.69.17 0.0.0.0 255.255.255.255 UH 0 0 0 calieda1954ad3b
10.233.69.18 0.0.0.0 255.255.255.255 UH 0 0 0 cali7fc7cd82cf3
</strong>
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
<sub>]# ifconfig calieda1954ad3b
calieda1954ad3b: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1440
inet6 fe80::ecee:eeff:feee:eeee prefixlen 64 scopeid 0x20<link>
ether ee:ee:ee:ee:ee:ee txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
</sub>]# ifconfig cali7fc7cd82cf3
cali7fc7cd82cf3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1440
inet6 fe80::ecee:eeff:feee:eeee prefixlen 64 scopeid 0x20<link>
ether ee:ee:ee:ee:ee:ee txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# ethtool -S 网卡 查看ROOT NS中对应的接口
[root@node-2 <sub>]# ethtool -S calieda1954ad3b
NIC statistics:
peer_ifindex: 4
[root@node-2 </sub>]# ethtool -S cali7fc7cd82cf3
NIC statistics:
peer_ifindex: 4
<sub>]# kubectl exec -it net-tools-6d94675c74-p4qmb -- bash
bash-5.1# ifconfig
eth0 Link encap:Ethernet HWaddr 72:0A:FA:F1:9B:CD
inet addr:10.233.69.18 Bcast:10.233.69.18 Mask:255.255.255.255 # 32位地址
UP BROADCAST RUNNING MULTICAST MTU:1440 Metric:1
RX packets:5 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:446 (446.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
bash-5.1# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0 # 此时我们知道所有的数据报文从该Pod中出去,那么需要发送到169.254.1.1对应的下一跳
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
</sub>]# kubectl exec -it net-tools-6d94675c74-thw2p -- bash
bash-5.1# ifconfig
eth0 Link encap:Ethernet HWaddr 0E:BD:EE:1D:EC:61
inet addr:10.233.69.17 Bcast:10.233.69.17 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1440 Metric:1
RX packets:5 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:446 (446.0 B) TX bytes:0 (0.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
bash-5.1# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
# ip mac关联信息
10.233.69.17 0E:BD:EE:1D:EC:61
10.233.69.18 72:0A:FA:F1:9B:CD
# 注意我们的地址都是32位的主机地址,也就意味着他们并不是属于同一个网段。这点很重要
# 目标路由
*<strong>
10.233.69.17 0.0.0.0 255.255.255.255 UH 0 0 0 calieda1954ad3b
10.233.69.18 0.0.0.0 255.255.255.255 UH 0 0 0 cali7fc7cd82cf3
</strong>
# 通过 ping 抓包演示,然后分析
<sub>]# kubectl exec -it net-tools-6d94675c74-p4qmb -- bash
bash-5.1# ping 10.233.69.17
PING 10.233.69.17 (10.233.69.17): 56 data bytes
64 bytes from 10.233.69.17: seq=0 ttl=63 time=0.954 ms
^C
--- 10.233.69.17 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.238/0.478/0.954 ms
bash-5.1#
</sub>]# kubectl exec -it net-tools-6d94675c74-thw2p -- bash
bash-5.1# tcpdump -ne -i eth0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
13:03:43.125708 ee:ee:ee:ee:ee:ee > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 10.233.69.17 tell 10.0.2.212, length 28
13:03:43.125763 0e:bd:ee:1d:ec:61 > ee:ee:ee:ee:ee:ee, ethertype ARP (0x0806), length 42: Reply 10.233.69.17 is-at 0e:bd:ee:1d:ec:61, length 28
13:03:43.125781 ee:ee:ee:ee:ee:ee > 0e:bd:ee:1d:ec:61, ethertype IPv4 (0x0800), length 98: 10.233.69.18 > 10.233.69.17: ICMP echo request, id 11776, seq 0, length 64
不同节点 POD 通信原理
1. Flannel Vxlan 模式不同节点 POD 通信
- Pod-1根据默认路由规则,将IP包发往cni网桥,出现在宿主机的网络栈上;
- flanneld预先在宿主机上创建好了路由规则,数据包到达cni网桥后,随即被转发给了flannel.1,flannel.1是一个VTEP设备,它既有 IP 地址,也有 MAC 地址;
- 在node2上的目的VTEP设备启动时,node1上的flanneld会将目的VTEP设备的IP地址和MAC地址分别写到node1上的路由表和ARP缓存表中。
- 因此,node1上的flannel.1通过查询路由表,知道要发往目的容器,需要经过10.1.16.0这个网关。其实这个网关,就是目的VTEP设备的ip地址。
1$ route -n
2Kernel IP routing table
3Destination Gateway Genmask Flags Metric Ref Use Iface
4...
510.1.16.0 10.1.16.0 255.255.255.0 UG 0 0 0 flannel.1
- 又由于这个网关的MAC地址,事先已经被flanneld写到了ARP缓存表中,所以内核直接把目的VTEP设备的MAC地址封装到链路层的帧头即可:
- flanneld还负责维护FDB(转发数据库)中的信息,查询FDB,就可以通过这个目的VTEP设备的MAC地址找到宿主机Node2的ip地址。
- 有了目的IP地址,接下来进行一次常规的、宿主机网络上的封包即可。
- 整个过程如下图所示:
可以看出,VXLAN模式中,flanneld维护的都是内核态数据:路由表、arp缓存表、FDB,VXLAN模式几乎全程运行在内核态。性能要比UDP模式好不少。
2. Calico IPIP 模式不同节点 POD 通信
通过创建两个分配在不同 node 节点的pod,我们来查看 pod 的通信过程 calico ipip 模型会在每个主机上创建一个名为 tunl0 的 tunnel 设备,tunnel 设备是一个隧道设备。 在不同节点 pod 通信过程中,pod 的数据报文会通过 tunl 设备,将数据报文封装为 raw data 数据报文(没有 mac 地址),,然后在重新封装一层网络层的信息,就是封装一层外部ip,达到 podIP in nodeIP 的结果,然后通过本地的 route 信息,会发往 eth0 网卡,然后寻求下一跳的地址,解包过程为封装过程的逆过程
kubectl run pod1 --image=burlyluo/nettoolbox
kubectl run pod2 --image=burlyluo/nettoolbox
<sub>]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod1 1/1 Running 0 30s 10.233.69.19 node-2 <none> <none>
pod2 1/1 Running 0 18s 10.233.110.15 node-3 <none> <none>
# 查看 node-2 节点的路由,查看 destination 为 10.233.110.0 的路由为 tunl0 接口
</sub>]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.2.1 0.0.0.0 UG 0 0 0 eth0
10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
10.233.69.0 0.0.0.0 255.255.255.0 U 0 0 0 *
10.233.69.19 0.0.0.0 255.255.255.255 UH 0 0 0 calice0906292e2
10.233.110.0 10.0.2.213 255.255.255.0 UG 0 0 0 tunl0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
# 查看 node-3 节点的路由,查看 destination 为 10.233.69.0 的路由为 tunl0 接口
<sub>]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.2.1 0.0.0.0 UG 0 0 0 eth0
10.0.2.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
10.233.69.0 10.0.2.212 255.255.255.0 UG 0 0 0 tunl0
10.233.110.0 0.0.0.0 255.255.255.0 U 0 0 0 *
10.233.110.15 0.0.0.0 255.255.255.255 UH 0 0 0 calibd2348b4f67
10.233.112.0 10.0.2.211 255.255.255.0 UG 0 0 0 tunl0
10.233.113.0 10.0.2.183 255.255.255.0 UG 0 0 0 tunl0
169.254.0.0 0.0.0.0 255.255.0.0 U 1002 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
# 查看任一节点的 tunl0 设备的信息,发现其为 tunnel 隧道设备
</sub>]# ifconfig tunl0
tunl0: flags=193<UP,RUNNING,NOARP> mtu 1440
inet 10.233.69.0 netmask 255.255.255.255
tunnel txqueuelen 1000 (IPIP Tunnel)
RX packets 825941 bytes 102211004 (97.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 732824 bytes 142142735 (135.5 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# pod1 ping pod2
<sub>]# kubectl exec -it pod1 -- ping 10.233.110.15
PING 10.233.110.15 (10.233.110.15): 56 data bytes
64 bytes from 10.233.110.15: seq=0 ttl=62 time=7.978 ms
# 对 tunl0 设备进行抓包
</sub>]# tcpdump -ne -i tunl0
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tunl0, link-type RAW (Raw IP), capture size 262144 bytes
10:18:58.704253 ip: 10.233.69.19 > 10.233.110.15: ICMP echo request, id 4352, seq 4, length 64
10:18:58.704394 ip: 10.233.110.15 > 10.233.69.19: ICMP echo reply, id 4352, seq 4, length 64
`
# 抓取 eth0 设备的包,使用 wireshark 分析抓到的包
tcpdump -ne -i tunl0 -w eth0.pcap
# 我们就可以查看分析出来 ip in ip 模型驶入和封包的