Calico 的三种工作模式
Calico 的运行支持三种模式:
- vxlan(overlay)
- ipip(overlay)
- bgp(underlay)
模式概述
IPIP
- 工作原理:在
tun0
设备上将 Pod 发来的数据包的 MAC 层去除,仅保留 IP 层,并使用宿主机的 IP 封装传输。 - 优缺点:要求节点之间三层网络互通,支持跨网段通信;封装/解封操作会引入一定性能损耗。
VXLAN
- 工作原理:通过
vxlan
设备将原始数据包封装为 UDP 数据包,源/目的 MAC 地址替换为本机与对端vxlan
网卡的 MAC 地址。 - 优缺点:同样要求节点三层互通,支持跨网段;但也存在封包开销问题。
BGP
- 工作原理:不进行任何封装,直接通过三层路由转发。
- 优缺点:利用 BGP 实现节点间三层互通,无需封装;但需底层物理网络支持 BGP。
CrossSubnet
VXLAN 和 IPIP 模式均支持配置 CrossSubnet
,即:
- 同子网节点间使用直连(BGP)通信;
- 不同子网节点之间才进行封装传输。
Calico Pod 之间的通信
同节点 Pod 通信
部署测试的 Workload:
1 | root@suse-o11y-01:~# kubectl get pod -owide |
在其中一个 Pod ping 另一个 Pod,网络通信没问题:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-96f94bdb4-m4v56 -- ping -c 3 10.42.42.254 |
检查 Pod 里的路由信息,都是只有两条路由,根据下面的路由,所有流量都会匹配第一条默认路由,从 eth0
网卡到达网关 169.254.1.1
:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-96f94bdb4-m4v56 -- route -n |
但在 Pod 里查看网卡信息,是不存在 IP 为 169.254.1.1
的网卡的:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-96f94bdb4-m4v56 -- ip -4 a |
查看 Pod 的 ARP 缓存,可以发现网关 169.254.1.1
的 MAC 地址是 ee:ee:ee:ee:ee:ee
:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-96f94bdb4-m4v56 -- ip neigh |
容器网络都是基于 Linux veth-pair
技术实现 Network namespace 间通信,所以在 Pod 和主机节点上使用一对 veth-pair
连接。
在 Pod 内,eth0@xxx
就是 veth-pair
的其中一端,可以通过下面的命令查看:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-96f94bdb4-m4v56 -- ip addr show dev eth0 |
另一端则是在节点上,节点上的 calixxx@yyy
网卡就是每个 Pod veth-pair
的对端网卡,每 veth-pair
的接口均有相同的 inteface index,如上 Pod 的 interface index 是 68,那么在节点上就可以通过这个 interface index 查找到对端网卡:
1 | root@suse-o11y-01:~# ip link show | egrep '^68:' -A 1 |
所以 cali1d5453727e1
就是 Pod busybox-96f94bdb4-m4v56
在节点上的对端网卡,它与 Pod 内的 eth0
组成了一个 veth-pair
,两张网卡也在同一个广播域,当 eth0
向 169.254.1.1
发出 ARP 请求的时候,就会被 cali1d5453727e1
接收,但从上面的命令查看发现,cali1d5453727e1
也没有配置 IP 169.254.1.1
,但依旧能回复 ARP 请求。
这是因为将 /proc/sys/net/ipv4/conf/<interface-dev>/proxy_arp
设置为 1
的时候,该网卡就会看起来像一个网关,会响应所有的 ARP 请求,并将自己的 MAC 地址告诉客户端。
通过命令查看,可以发现 cali1d5453727e1
的 proxy_arp
为 1
,说明 cali1d5453727e1
收到 Pod 发出的 ARP 请求的时候,会将自己的 MAC 地址返回给 Pod:
1 | root@suse-o11y-01:~# cat /proc/sys/net/ipv4/conf/cali1d5453727e1/proxy_arp |
当 Pod 拿到 MAC 地址后就会进行封包,将报文转发到 cali1d5453727e1
网卡上,报文到达主机后,就会匹配主机路由:
1 | root@suse-o11y-01:~# route -n | grep "10.42.42.254" |
可以发现发往 10.42.42.254(另一个 busybox Pod 的 IP)的报文直接通过 cali9f5a511495b
,该网卡也就是另一个 busybox Pod 的 veth-pair
在主机上的一端。
报文通过 cali9f5a511495b
进入 Pod 内,进而完成了一次通信的全过程,流量图如下:
抓包进行验证,抓包前先在 Pod 内清理 ARP 缓存,避免获取不到 ARP 报文:
1 | ip neigh flush dev eth0 |
通过 tcpdump
抓取对端网卡报文:
1 | root@suse-o11y-01:~# tcpdump -nn -i cali1d5453727e1 -e |
由此可知,同节点 Calico Pod 之间的通信主要是基于
veth-pair
实现,还没应用到 IPIP/VXLAN 等技术。
跨节点 Pod 通信(VXLAN 模式)
VXLAN(虚拟扩展局域网)是一种网络虚拟化技术,通过在 UDP 之上封装第二层以太网帧,实现在三层网络上的二层网络扩展,从而允许创建多达 1600 万个隔离的虚拟网络,远超 VLAN 的 4096 个网络限制。VXLAN 报文格式如下:
- VXLAN 标识头:VXLAN header 里主要关注 VNI(VXLAN 网络标识符),VNI 是一个 24 位的标识符,用来唯一标识一个虚拟二层网络,就像传统 VLAN 中的 VLAN ID 一样,VNI 用来区分不同的虚拟网络。
- UDP 头:在外层新增 UDP 头,默认目的端口号是 4789。
- IP 头:在外层新增 IP 头,源地址是当前节点物理网卡 IP,目的地址是对端节点物理网卡 IP。
- MAC 头:源 MAC 是当前节点物理网卡 MAC 地址,目的 MAC 是对端节点物理网卡 MAC 地址。
RKE2 使用 Calico CNI 构建集群时,默认使用的就是 VXLAN 模式:
1 | root@suse-o11y-01:~# kubectl get installations.operator.tigera.io default -oyaml | grep encapsulation |
部署测试的 Workload:
1 | root@suse-o11y-01:~# kubectl get pod -owide -l app=busybox |
在其中一个 Pod ping 另一个 Pod,网络通信没问题:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-67979bccf7-dx4ck -- ping -c 3 10.42.83.245 |
查看 Pod 内部的路由,可以看到仍然是只有两条路由,说明无论跨不跨节点,Pod 的流量都会先到达宿主机,然后再根据主机路由进行下一跳:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-67979bccf7-dx4ck -- route -n |
对端 Pod IP 为 10.42.83.245
,在宿主机上查看路由:
1 | root@suse-o11y-01:~# route -n | grep 10.42.83 |
Calico 会根据配置为每个节点上分配一个子网段,每个节点上的 Calico Pod IP 都在该子网里,所以说每个节点上都会存在到其他节点的子网路由。如果集群中有 n 个节点,那么每个节点都会有 n-1 个类似上述的路由。
通过上述路由可知,所有目标地址在 10.42.83.192/26
范围内的包,都将通过 vxlan.calico
接口发送,并封装为 VXLAN 报文,发给远端的隧道端点 10.42.83.192
。
在对端宿主机查看,也可以看到 vxlan.calico
网卡的 IP 就是 10.42.83.192
:
1 | root@suse-o11y-02:~# ip -4 a |
任何一个 VXLAN 设备创建时都会指定一个三层物理网络设备作为 VTEP, Calico 也不例外, 可通过以下命令进行查看:
1 | root@suse-o11y-01:~# ip -d link show vxlan.calico |
VXLAN 为 overlay 的容器网络,是个大二层互通的解决方案,但最终报文还是要借助物理网卡出去,该 vxlan.calico
也是基于物理网卡 ens34
创建的,所以说该网卡上的报文依然基于 ens34
转发。
Calico 会为每个节点都创建一个 vxlan.calico
的网卡,作为 VXLAN 报文的网关设备。报文根据路由到达该网卡时,原始数据包会作为 VXLAN 数据包的负载,同时设置上述说到的 VXLAN 标识头、UDP 头、IP 头、MAC 头,报文如下:
- 原始报文:包含内层源/目 IP,内层源/目 MAC,以及对应报文负载:
- 内层源/目 IP:即源/目 Pod IP。
- 内层源/目 MAC:因为 Pod 流量会先到达
vxlan.calico
进行封装,所以源 MAC 就是vxlan.calico
网卡的 MAC 地址,目的 MAC 同理,不同的是目的 MAC 不是通过 ARP 获取的,而是 Calico 在每个节点预设好的,由 Calico 负责维护,没有过期时间。
1 | root@suse-o11y-01:~# ip n | grep vxlan |
- 外层 IP:为源/目节点的物理网卡 IP 地址,源 IP 为当前节点物理网卡 IP 地址,目的 IP 通过 FDB(Forwarding Database 转发数据库)表查询,:
1 | root@suse-o11y-01:~# bridge fdb show | grep vxlan.calico |
- 外层 MAC:源/目节点物理网卡对应的 MAC 地址。
经过封装后的报文的源 IP 地址、目的 IP 地址都是主机的物理机网卡地址,所以只要两个主机节点能够通信,报文即可传输到对端节点。
当报文到达对端节点的时候,内核会根据 VXLAN 报文标识将报文传输到 vxlan.calico
上,然后再将先前封装的 VXLAN 标识头、UDP 头、IP 头、MAC 头去掉,去掉后即可匹配节点的路由:
1 | root@suse-o11y-02:~# route -n | grep 10.42.83.245 |
最后就会通过 Pod 与节点对应的 veth-pair
设备,将流量顺利转发到 Pod 中,流量图如下:
抓包验证:
1 | root@suse-o11y-01:~# tcpdump -nnn -i ens34 -e udp -w vxlan.pcap |
通过 Wireshark 查看报文,通过下面的方法进行过滤:
1 | ip.src == 10.42.42.255 && ip.dst == 10.42.83.245 |
Calico VXLAN 网络模式的优势体现在采用 VXLAN 技术能够实现 1600 万虚拟网络,远超传统 4096 VLAN 限制,适合大规模集群。
因为 VXLAN 中使用 VNI 标记报文,通过 UDP 封装报文,所以在传输过程中,底层网络设备只能看到外层的 IP 和 UDP 头部,无法解析内部的二层帧,从而确保不同虚拟网络的流量不会相互干扰。每个 VTEP 只处理与其关联的 VNI 流量,确保不同虚拟网络的流量在 VTEP 处得到隔离。
另外 VXLAN 作为 Overlay 技术,允许跨三层网络进行通信,解决了传统 VLAN 只能在同子网内通信的限制。也就是说即使节点之间跨子网,不属于同一 VLAN,跨节点的 Pod 也可以正常通信,因为 VXLAN 报文通过封装后,最终从物理网卡传输。
跨节点 Pod 通信(IPIP 模式)
IP-in-IP 就是把一个 IP 数据包又套在一个 IP 包里,即把 IP 层封装到 IP 层的一个 tunnel 。它的作用其实基本上就相当于一个基于 IP 层的网桥。一般来说,普通的网桥是基于 MAC 层的,根本不需要 IP,而 IPIP 则是通过两端的路由做一个 tunnel,把两个本来不通的网络通过点对点连接起来。
IPIP 报文格式如下:
- 外层 IP 头:在外层新增的 IP 头,源目地址是两端物理节点网卡的 IP。
- 内层 IP 头:源目地址是两端 Pod 的 IP。
1 | 0 3 7 15 23 31 |
由于 RKE2 使用 Calico 构建集群的时候默认使用 VXLAN,所以要先切换模式到 IPIP:
如果是自建 RKE2,可以直接创建 HelmChartConfig
进行配置:
1 | cat <<EOF | kubectl apply -f - |
需要手动修改 IPPool
使用的模式:
1 | kubectl patch ippool default-ipv4-ippool \ |
手动删除所有 calico-node
Pod,然后检查配置是否正常:
1 | root@suse-o11y-01:~# kubectl get installations.operator.tigera.io default -oyaml | grep encapsulation |
检查跨节点通信:
1 | root@suse-o11y-01:~# kubectl get pod -owide |
查看 Pod 的路由,依旧是只有两条,所以 IPIP 模式也是同理,Pod 的流量需要到达宿主机后才能转发下一跳:
1 | root@suse-o11y-01:~# kubectl exec -it busybox-67979bccf7-ns9vj -- route -n |
对端 Pod IP 为 10.42.83.247
,在宿主机上查看路由:
1 | root@suse-o11y-01:~# route -n | grep 10.42.83 |
Calico 会根据配置为每个节点上分配一个子网段,每个节点上的 Calico Pod IP 都在该子网里,所以说每个节点上都会存在到其他节点的子网路由。如果集群中有 n 个节点,那么每个节点都会有 n-1 个类似上述的路由。
通过上述路由可知,所有目标地址在 10.42.83.192/26
范围内的包,都将通过 tunl0
接口发送,网关 IP 为 172.16.16.111
,即对端主机物理网卡的 IP,可以发现网关 IP 的表现与 vxlan.calico
不同。
IPIP 模式下同样也会在主机上创建一个 IPIP 设备,默认是 tunl0
,tunl0
网卡 IP 地址与本机 Pod IP 属于同一个网段:
1 | root@suse-o11y-01:~# ip -4 a |
tunl0
是 Linux 支持的隧道设备接口,当有这个接口时,出这个主机的 IP 包就会本封装成 IPIP 报文。
报文根据路由到达 tunl0
网卡时,原始数据包会作为 IPIP 数据包的负载,同时设置外层 IP 报文,此时只要两个主机之间能够通信,报文就可以正常传输到对端节点。
当报文到达对端节点物理网卡的时候就会解封外层的 IP 报文,根据内层 IP 报文匹配路由:
1 | root@suse-o11y-02:~# route -n | grep 10.42.83.247 |
最后就会根据 calid1b248f937e
对应的 veth-pair
对端设备,也就是 Pod 内的 eth0
网卡,将报文转发到 Pod 中,流量图如下:
抓包验证:
1 | root@suse-o11y-01:~# tcpdump -nnn -i ens34 -w ipip.pcap |
通过 Wireshark 查看报文,通过下面的规则进行过滤:
1 | ip.src == 10.42.42.199 && ip.dst == 10.42.83.247 |
IPIP 网络模式与 VXLAN 都属于 Overlay 网络,即需要结合 Linux 内核技术对原始报文进行封包、解包。
IPIP 相较 VXLAN 其数据包更小,但是一般需要底层网络允许 IPIP 协议;对于 VXLAN 即需要底层网络允许 UDP 流量。
Calico 的三种工作模式
install_url
to use ShareThis. Please set it in _config.yml
.