Calico IPIP模式详解
Kubernetes 集群中的Calico网络插件有几种网络模式,例如BGP, IPIP, VXLAN (Calico v3.7之后支持此模式),本文主要介绍IPIP模式。
Calico IPIP模式其实是利用了Linux 的tun/tap设备,对IP层的报文再加了一层IP层的封装实现的一种overlay模式。因为IPIP模式比BGP模式多了一层封包与拆包,所以性能会有所损耗。既然如此,为什么不直接使用BGP模式就行了呢?因为BGP模式是需要通过路由广播交换容器网络的路由信息,而路由广播只能在局域网中进行,在BGP模式下,如果kubernetes集群中的工作节点不在同一个子网,则跨子网的工作节点上的POD无法正常通信。
下面通过一些例子来详细了解Calico IPIP模式是如何工作的。
我们使用的集群中加上管理节点总共有两个工作节点
[root@master01 /]# kubectl get node NAME STATUS ROLES AGE VERSION master01 Ready master 57d v1.15.12 node01 Ready <none> 62m v1.15.12
部署了calico网络插件
[root@master01 /]# kubectl -n kube-system get po -owide | grep calico-node calico-node-9shgc 1/1 Running 0 11m 192.168.92.128 master01 <none> <none> calico-node-c2scz 1/1 Running 0 11m 192.168.92.129 node01 <none> <none>
容器网段为172.16.0.0/16
- name: CALICO_IPV4POOL_CIDR value: 172.16.0.0/16
kubectl get ipamblocks可以看到有两个容器网段分别分配给了master01和node01。
[root@master01 ~]# kubectl get ipamblocks NAME AGE 172-16-196-128-26 59m 172-16-241-64-26 60m [root@master01 ~]# kubectl get ipamblocks 172-16-196-128-26 -oyaml apiVersion: crd.projectcalico.org/v1 kind: IPAMBlock metadata: annotations: projectcalico.org/metadata: '{"creationTimestamp":null}' creationTimestamp: "2021-03-30T04:17:26Z" generation: 6 name: 172-16-196-128-26 resourceVersion: "2380" selfLink: /apis/crd.projectcalico.org/v1/ipamblocks/172-16-196-128-26 uid: f10cf88f-f999-49c6-bd7f-17670ed0e173 spec: Deleted: false affinity: host:node01 [root@master01 ~]# kubectl get ipamblocks 172-16-241-64-26 -oyaml apiVersion: crd.projectcalico.org/v1 kind: IPAMBlock metadata: annotations: projectcalico.org/metadata: '{"creationTimestamp":null}' creationTimestamp: "2021-03-30T04:16:40Z" generation: 6 name: 172-16-241-64-26 resourceVersion: "983" selfLink: /apis/crd.projectcalico.org/v1/ipamblocks/172-16-241-64-26 uid: 97e63164-1f4b-4621-9c08-2b1fdb766cb1 spec: Deleted: false affinity: host:master01
查看主机master01上的路由表,可以看到目标地址为172.26.196.128/26的请求会被通过网卡tunl0转发到192.168.92.129,也就是node01上。
而master01节点本机上的POD IP,则会直接被路由到对应的calico网卡。
[root@master01 /]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.92.2 0.0.0.0 UG 100 0 0 ens33 172.16.196.128 192.168.92.129 255.255.255.192 UG 0 0 0 tunl0 172.16.241.64 0.0.0.0 255.255.255.192 U 0 0 0 * 172.16.241.69 0.0.0.0 255.255.255.255 UH 0 0 0 cali68d0bd52ca7 172.16.241.70 0.0.0.0 255.255.255.255 UH 0 0 0 cali55ce5618d78 172.16.241.71 0.0.0.0 255.255.255.255 UH 0 0 0 cali6156df79841 172.16.241.72 0.0.0.0 255.255.255.255 UH 0 0 0 cali81d206a1716 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.92.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33 192.168.241.64 0.0.0.0 255.255.255.192 U 0 0 0 *
同样,在node01上也可以看到类似的路由条目。
[root@node01 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.92.2 0.0.0.0 UG 100 0 0 ens33 172.16.196.128 0.0.0.0 255.255.255.192 U 0 0 0 * 172.16.196.129 0.0.0.0 255.255.255.255 UH 0 0 0 cali6321cd990f9 172.16.196.131 0.0.0.0 255.255.255.255 UH 0 0 0 cali63b1569e518 172.16.241.64 192.168.92.128 255.255.255.192 UG 0 0 0 tunl0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.92.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
现在我们在每个工作节点上分别部署一个busybox POD,然后再详细看看跨节点的POD之间是怎么通信的。
[root@master01 ~]# kubectl -n kube-system get po -owide | grep busybox busybox-b8ffb94c4-2ltgw 1/1 Running 0 17s 172.16.196.132 node01 <none> <none> busybox-b8ffb94c4-c84gp 1/1 Running 0 17s 172.16.241.69 master01 <none> <none>
在其中一个busybox POD上访问另一个节点上的busybox。
[root@master01 ~]# kubectl -n kube-system exec -it busybox-b8ffb94c4-c84gp sh / # ping 172.16.196.132 PING 172.16.196.132 (172.16.196.132): 56 data bytes 64 bytes from 172.16.196.132: seq=0 ttl=62 time=0.580 ms
在另一台主机上抓包。
[root@node01 ~]# tcpdump -i any -nn -vvvv host \(192.168.92.128 or 172.16.241.69\) -w /tmp/busybox.cap
使用wireshark打开抓包文件,可以看到报文在POD A (172.16.241.69) 和POD B (172.16.196.132)之间,经过了两层的IP封装。
因为跨节点的POD之间通信时,报文需要通过宿主机的网络发送到另一个节点,但宿主机网络无法识别容器IP,所以,报文从POA A发送到宿主机A的tunl0网卡时,内核自动在报文基础上封装了一层IP头,之后再转发给主机B(192.168.92.129)。主机B拆开IP报文之后,根据本机的路由表匹配到目标POD的网卡,最终把报文转发给了POD B。
这里有一个问题,主机192.168.92.128和192.168.92.129是在同一个子网的,他们之间其实是可以通过BGP路由信息交换,使到POD之间可以直接路由可达,而不需要通过IPIP协议,毕竟IPIP协议还是有损耗的。
在Calico IPIP模式下,其实有一个配置项(CALICO_IPV4POOL_IPIP),可以让calico只有在跨子网的情况下才使用IPIP协议,同一子网中的POD直接通过路由信息直连。该参数为如下,添加到calico-node的环境变量中即可。
- name: CALICO_IPV4POOL_IPIP
value: CrossSubnet
如果是已经部署了calico之后再修改这个参数,还需要修改ippool中的ipipMode参数。
[root@master01 ~]# kubectl get ippool default-ipv4-ippool -oyaml apiVersion: crd.projectcalico.org/v1 kind: IPPool metadata: annotations: projectcalico.org/metadata: '{"uid":"3797d863-3b04-45cd-b9ba-ac1980a21520","creationTimestamp":"2021-03-30T04:16:40Z"}' creationTimestamp: "2021-03-30T04:16:40Z" generation: 1 name: default-ipv4-ippool resourceVersion: "732" selfLink: /apis/crd.projectcalico.org/v1/ippools/default-ipv4-ippool uid: 4c7acedb-0026-4062-994d-e82d261f29cc spec: blockSize: 26 cidr: 172.16.0.0/16 ipipMode: CrossSubnet
修改完以上参数之后,我们再看看主机上的路由表,可以看到容器网段172.16.196.128/26的路由条目,出口的网卡变成了ens33,也就是不再通过tunl0进行IPIP协议的封装,而是直接根据路由信息转发。
[root@master01 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.92.2 0.0.0.0 UG 100 0 0 ens33 172.16.196.128 192.168.92.129 255.255.255.192 UG 0 0 0 ens33 172.16.241.64 0.0.0.0 255.255.255.192 U 0 0 0 * 172.16.241.65 0.0.0.0 255.255.255.255 UH 0 0 0 cali99ba0e1a1c8 172.16.241.66 0.0.0.0 255.255.255.255 UH 0 0 0 cali3941b92be17 172.16.241.67 0.0.0.0 255.255.255.255 UH 0 0 0 cali0fd794bcf81 172.16.241.68 0.0.0.0 255.255.255.255 UH 0 0 0 cali291c708c629 172.16.241.69 0.0.0.0 255.255.255.255 UH 0 0 0 cali67beaa0e9f4 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.92.0 0.0.0.0 255.255.255.0 U 100 0 0 ens33
同样的在POD A ping POD B并抓包看看,我们可以看到这次只有一个IP层报文。