Kubernetes 网络进阶:Calico 之 BGP 模式深度解析

近期,不少同学向我请教如何为Kubernetes集群挑选合适的网络插件,以及不同网络插件在实现原理和功能特性上存在哪些差异。说实话,这个问题的答案并非三言两语就能说清。在CNCF生态体系中,Calico、Flannel、Cilium、Kube-OVN等网络插件百花齐放,令人目不暇接。而且,即便是同一款插件,也衍生出直接路由、bridge、IPIP、VXLAN、eBPF等多种组网模式,每种模式都有其独特的技术优势和适用场景。

鉴于Kubernetes网络选型的复杂性,我计划以Calico网络插件为切入点,通过系列文章深入解析各类组网模式的技术原理,探讨其适用场景,并结合实际客户案例,详细说明如何选择最适合的网络插件与组网方案。

Kubernetes网络基础回顾

在深入探讨Calico之前,我们先回顾一下Kubernetes网络模型的核心要点:

  • IP地址分配:集群内每个Pod都会被分配唯一IP地址。Pod内的所有容器共享同一网络命名空间,容器间进程可通过localhost127.0.0.1直接通信。
  • Pod间通信:集群内任意两个Pod均可直接通信,无需NAT转换或代理,真正实现网络互通。
  • 服务与Pod通信:Kubernetes通过Service为后端Pod提供统一访问入口。每个Service拥有固定IP地址,即使后端Pod动态变化,也能通过负载均衡策略将流量精准分发。
  • Pod与外部通信:借助Gateway API的高级路由功能,集群内的Service可对外暴露,方便外部服务访问集群资源。
  • 网络策略管控:通过NetworkPolicy,用户可以精细化管理Pod之间及Pod与外部服务的网络流量,实现安全隔离和访问控制。

值得注意的是,Kubernetes仅定义了网络模型,具体的网络连接实现依赖于容器网络接口CNI。通过CNI,Kubernetes调用各类网络插件完成容器网络的配置和搭建工作。

作为Kubernetes生态中最受欢迎的网络插件之一,Calico不仅支持高效的IP地址分配和Pod间通信,还提供强大的网络策略功能,可实现严格的网络隔离和访问控制。此外,Calico能够与Kubernetes的Service和Gateway API深度集成,通过构建稳定的网络拓扑,显著提升集群的网络性能和安全性。

Calico组件与架构

Calico架构

Calico的核心组件包括calico-kube-controllerscalico-nodeetcd

  1. calico-kube-controllers:以Deployment形式部署,主要负责同步网络策略、管理IP池、监控节点状态等核心任务。
  2. calico-node:以DaemonSet形式运行在每个集群节点上,负责执行网络策略、管理路由和配置网络接口。其内部包含多个重要进程:
    • Felix:负责节点网络接口管理、路由配置和ACL管理,根据集群网络策略动态配置节点网络。
    • BIRD:作为BGP客户端(包括IPv4和IPv6版本),负责与其他节点交换路由信息,实现Pod跨节点通信。
    • Confd:监控集群配置变化,自动更新配置文件并触发BIRD等进程重新加载。
    • CNI:在Pod创建和销毁时,负责为容器配置网络接口,分配IP地址并应用访问控制规则。
  3. etcd:作为Calico的后端存储,用于保存网络配置、策略规则、IP地址分配等关键数据。

Calico支持BGP、IPIP、VXLAN等多种组网方式。本文将从最基础的BGP模式入手,逐步揭开Calico网络的神秘面纱。

Calico BGP模式

BGP(边界网关协议)是一种基于TCP的去中心化自治路由协议,主要用于在不同自治系统(AS)之间交换路由信息。通过TCP建立邻居关系后,BGP路由器会相互交换可达性和路径信息,并根据AS路径长度、下一跳地址等属性选择最优路由。

在Calico网络中,集群节点充当虚拟路由器(vRouter),通过BGP协议将Pod网络路由信息传播到整个网络。这种机制使得集群内的Pod能够通过主机路由实现高效、透明的通信。

在深入分析Calico BGP模式下的Pod通信过程之前,我们需要准备一个已安装Calico插件并启用BGP模式的Kubernetes集群。接下来,我们将逐步解析其网络通信的具体实现细节。

搭建 Kubernetes 集群

社区中搭建 Kubernetes 的工具非常多,这里我使用的是 kebekey,操作过程如下:

## 安装kubekey
$ export KKZONE=cn
$ curl -sfL https://get-kk.kubesphere.io | VERSION=v3.0.13 sh -
## 安装 Kubernetes 集群和 Calico 插件,并启用 BGP 模式
$ cat > cluster.yaml <<EOF
apiVersion: kubekey.kubesphere.io/v1alpha2
kind: Cluster
metadata:
  name: zlw-cluster
spec:
  hosts:
    - {name: 10-23-14-110, address: 10.23.14.110, internalAddress: 10.23.14.110, user: root, sshKey: "~/.ssh/id_rsa"}
    - {name: 10-23-14-111, address: 10.23.14.111, internalAddress: 10.23.14.111, user: root, sshKey: "~/.ssh/id_rsa"}
    - {name: 10-23-14-112, address: 10.23.14.112, internalAddress: 10.23.14.112, user: root, sshKey: "~/.ssh/id_rsa"}
    - {name: 10-23-14-113, address: 10.23.14.113, internalAddress: 10.23.14.113, user: root, sshKey: "~/.ssh/id_rsa"}
    - {name: 10-23-14-114, address: 10.23.14.114, internalAddress: 10.23.14.114, user: root, sshKey: "~/.ssh/id_rsa"}
    - {name: 10-23-14-115, address: 10.23.14.115, internalAddress: 10.23.14.115, user: root, sshKey: "~/.ssh/id_rsa"}
  roleGroups:
    etcd:
      - 10-23-14-110
      - 10-23-14-111
      - 10-23-14-112
    control-plane:
      - 10-23-14-110
      - 10-23-14-111
      - 10-23-14-112
    worker:
      - 10-23-14-113
      - 10-23-14-114
      - 10-23-14-115
  controlPlaneEndpoint:
    domain: lb.kubesphere.local
    address: ""
    port: 6443
  kubernetes:
    version: v1.32.0
    clusterName: cluster.local
  network:
    plugin: calico
    kubePodsCIDR: 10.233.0.0/16
    kubeServiceCIDR: 10.96.0.0/16
    calico:
      ipipMode: Never
      vxlanMode: Never
      bgp:
        enabled: true
        asNumber: 64512
        peerSelector: all()
EOF
$ kk create cluster -f cluster.yaml -y --debug

在本次实验中,我准备了6台机器,其中 10-23-14-11010-23-14-11110-23-14-112 作为集群的 master 节点, 10-23-14-11310-23-14-11410-23-14-115 作为 worker 节点。

经过以上操作后,我们可以得到一个安装好 Calico 网络插件的 Kubernetes 集群。随后在节点 10-23-14-110 上执行以下命令查看 Calico 的网络状态:

$ kubectl get IPPool default-ipv4-ippool -oyaml
apiVersion: crd.projectcalico.org/v1
kind: IPPool
metadata:
  name: default-ipv4-ippool
spec:
  allowedUses:
  - Workload
  - Tunnel
  blockSize: 24
  cidr: 10.233.0.0/16
  ipipMode: Never
  natOutgoing: true
  nodeSelector: all()
  vxlanMode: Never

$ calicoctl node status
Calico process is running.

IPv4 BGP status
+--------------+-------------------+-------+----------+-------------+
| PEER ADDRESS |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+--------------+-------------------+-------+----------+-------------+
| 10.23.14.111 | node-to-node mesh | up    | 12:31:31 | Established |
| 10.23.14.112 | node-to-node mesh | up    | 12:31:30 | Established |
| 10.23.14.113 | node-to-node mesh | up    | 12:31:32 | Established |
| 10.23.14.114 | node-to-node mesh | up    | 12:31:31 | Established |
| 10.23.14.115 | node-to-node mesh | up    | 12:31:31 | Established |
+--------------+-------------------+-------+----------+-------------+

IPv6 BGP status
No IPv6 peers found.

Calico 网络插件提供了 Overlay Network 和 Underlay Network 两种网络方案。其中 Overlay Network 的实现包括 IPIP 和 VXLAN 模式,而 Underlay Network 的实现则是 BGP 模式。在当前的 Calico IPPool 配置中, ipipModevxlanMode 属性均为 Never,说明没有使用 IPIP 和 VXLAN 模式,而是使用 BGP 模式,即采用的是 Underlay Network。

此外,我们可以看到 10.23.14.110 节点上的 Calico 进程正在运行,并且已经和 10.23.14.11110.23.14.115 这5台机器建立了 IPv4 BGP 连接。 PEER TYPEnode-to-node mesh 说明当前 BGP 使用了 Full Mesh 模式(全互连模式),即集群内各个节点两两之间都建立了 BGP 连接。

为了让更加清晰地说清楚 Calico BGP 在 Full Mesh 模式下的网络通信原理,我将分别从同节点和不同节点这两个维度展开 Pod 的通信过程。

同节点 Pod 网络通信

首先,我在集群中部署了两个副本,让它们都运行在节点 10-23-14-110 上:

$ kubectl get pods -owide
NAME     READY   STATUS     RESTARTS   AGE   IP             NODE          NOMINATED NODE    READINESS GATES
zlw-01   1/1     Running    0          1h    10.233.133.2   10-23-14-110   <none>           <none>
zlw-02   1/1     Running    0          1h    10.233.133.3   10-23-14-110   <none>           <none>

可以看到,副本 zlw-01zlw-02 的 PodIP 分别是 10.233.133.210.233.133.3。接下来,我们执行以下命令检测网络的连通性:

$ kubectl exec -it zlw-01 -- ping -c 1 10.233.133.3
PING 10.233.133.3 (10.233.133.3) 56(84) bytes of data.
64 bytes from 10.233.133.3: icmp_seq=1 ttl=63 time=0.149 ms

--- 10.233.133.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.075/0.075/0.075/0.000 ms

在副本 zlw-01 中访问 zlw-02 的 PodIP 时能够正常获得响应,说明它们的网络通信是通畅的。那么,同节点的两个副本,它们之间流量的转发过程是怎么样的呢?

容器到主机

我们先来看一下副本 zlw-01 的网络设备:

$ kubectl exec -it zlw-01 -- 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: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: eth0@if3120: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 4a:8d:0e:93:6a:39 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.233.133.2/32 scope global eth0
       valid_lft forever preferred_lft forever

命令 ip aip addr 的缩写,通过查看其输出,我们可以看到副本 zlw-01 的 eth0 网卡地址是 10.233.133.2,这个地址正是 zlw-01 的 PodIP 地址。

而容器的 eth0 网卡不会单独出现,在创建容器时 Calico 会为容器生成一对虚拟以太网卡(veth pair),其中一端在容器内部,即我们看到的 eth0,而另外一端在节点中,通常以 cali 为前缀命名,并且有一个索引编号。例如 eth0@if3120 表示副本 zlw-01 中容器的 eth0 网卡与节点中索引编号为 3120 的网卡是一对 veth pair。

接下来我们再看一下副本 zlw-01 的路由表信息:

$ kubectl exec -it zlw-01 -- ip r
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

$ kubectl exec -it zlw-01 -- 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

default via 169.254.1.1 dev eth0 是一条默认路由,充当兜底的角色。当系统需要将数据包发到一个目标地址,但在路由表中找不到精确匹配的条目时,就会使用默认路由。 169.254.1.1 dev eth0 scope link 属于直连路由,表明 169.254.1.1 和 eth0 处于同一链路,数据包可以在这个链路上直接传输,无需通过其他网关。

在 Calico 中, 169.254.1.1 充当了默认网关的角色。当容器中产生发往路由表中未明确匹配外部地址(即目标地址是 0.0.0.0)的数据包时,这些数据包会经过容器内的 eth0 网卡,随后到达节点的 cali 网卡,接着被转发至默认网关 169.254.1.1,最后由网关负责后续的转发。

前面我们知道,运行在节点 10-23-14-110 上的副本 zlw-01,其容器的 eth0 网卡与节点中索引编号为 3120 是一对 veth pair,因此我们可以在 10-23-14-110 执行以下命令找到对应的 cali 网卡:

$ ip a | grep -A 3 3120
3120: calife4cf73bf2a@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 2
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever

从输出的结果可以知道,索引为 3120 的 cali 网卡具体名称是 calife4cf73bf2a。这个网卡没有 IP 地址,它的MAC 地址是 ee:ee:ee:ee:ee:ee

接下来,我们可以使用 tcpdump 命令来监听网卡 calife4cf73bf2a 流量情况:

$ tcpdump -i calife4cf73bf2a -ne
03:24:05.122338 4a:8d:0e:93:6a:39 > ee:ee:ee:ee:ee:ee, ethertype ARP (0x0806), length 42: Request who-has 169.254.1.1 tell 10.233.133.2, length 28
03:24:05.122390 ee:ee:ee:ee:ee:ee > 4a:8d:0e:93:6a:39, ethertype ARP (0x0806), length 42: Reply 169.254.1.1 is-at ee:ee:ee:ee:ee:ee, length 28

03:24:05.122407 ee:ee:ee:ee:ee:ee > 4a:8d:0e:93:6a:39, ethertype ARP (0x0806), length 42: Request who-has 10.233.133.2 tell 10.23.14.110, length 28
03:24:05.122416 4a:8d:0e:93:6a:39 > ee:ee:ee:ee:ee:ee, ethertype ARP (0x0806), length 42: Reply 10.233.133.2 is-at 4a:8d:0e:93:6a:39, length 28

从监听的输出结果中我们可以得知:

1.副本 zlw-01 所在的eth0 网卡(IP 地址是 10.233.133.2,MAC 地址 4a:8d:0e:93:6a:39 )发起了一次 ARP 请求,用以查询 169.254.1.1 的 MAC 地址,节点 10-23-14-110 的 cali 网卡 calife4cf73bf2a(MAC 地址为 ee:ee:ee:ee:ee:ee)对请求进行了响应,确认 169.254.1.1 的 MAC 地址是 ee:ee:ee:ee:ee:ee

2.节点 10-23-14-110 通过 calife4cf73bf2a 网卡,向副本 zlw-01 的 eth0 网卡发起了 ARP 请求,用于查询 10.233.133.2 的 MAC 地址。副本 zlw-01 的 eth0 网卡(MAC 地址是 4a:8d:0e:93:6a:39)对请求进行了响应,确认 10.233.133.2 的 MAC 地址是 4a:8d:0e:93:6a:39;

这里我们可能会有疑问:作为 Calico 默认网关的 169.254.1.1,它的 MAC 地址为什么会是 ee:ee:ee:ee:ee:ee 呢?

实际上 169.254.1.1 是 Calico 为容器网络虚拟出来的默认网关 IP,并非真实的物理设备 IP。当 zlw-01 通过 eth0 网卡发起 ARP 请求查询默认网关 169.254.1.1 的 MAC 地址时,在节点上与 eth0 对应的 cali 网卡 calife4cf73bf2a 会通过代理 ARP 响应容器的 ARP 请求,并返回自身的 MAC 地址,即 ee:ee:ee:ee:ee:ee。 这个设计是 Calico 利用 cali 网卡作为容器与节点的虚拟连接桥梁(即 veth pair 的对端),通过代理 ARP 可以让容器发往 169.254.1.1 的流量被封装为目的 MAC 地址为 ee:ee:ee:ee:ee:ee 的数据帧,数据帧会从容器的 eth0 网卡到达节点的 cali 网卡,进而可以使用节点的路由转发能力实现网络通信。

为了验证节点 10-23-14-110 上的 cali 网卡 calife4cf73bf2a 是否启用代理 ARP 功能,我们可以在节点上执行以下命令

$ cat /proc/sys/net/ipv4/conf/calife4cf73bf2a/proxy_arp
1

返回值为1即代表 cali 网卡 calife4cf73bf2a 已经启用代理 ARP 功能,可以代答 ARP 请求。

接下来,我们在使用 tcpdump 命令继续监听副本 zlw-01 流量的同时,在另一个新的终端执行以下命令: kubectl exec -it zlw-01 -- ping -c 1 10.233.133.3,随后我们可以看到 tcpdump 监听到以下结果:

10:20:32.661779 4a:8d:0e:93:6a:39 > ee:ee:ee:ee:ee:ee, ethertype IPv4 (0x0800), length 98: 10.233.133.2 > 10.233.133.3: ICMP echo request, id 12, seq 1, length 64
10:20:32.661882 ee:ee:ee:ee:ee:ee > 4a:8d:0e:93:6a:39, ethertype IPv4 (0x0800), length 98: 10.233.133.3 > 10.233.133.2: ICMP echo reply, id 12, seq 1, length 64

可以看到,当副本 zlw-01zlw-02 发送 ICMP 请求时,流量从容器内的 eth0 网卡发出,经 veth pair 到达节点的 cali 网卡 calife4cf73bf2a,此时源 IP 是 10.233.133.2(副本 zlw-01 的 IP 地址),目标 IP 是 10.233.133.3(副本 zlw-02 的 IP 地址),源 MAC 地址是 4a:8d:0e:93:6a:39(副本 zlw-01 中 eth0 网卡的 MAC 地址),目标 MAC 地址是 ee:ee:ee:ee:ee:ee(副本 zlw-01 的 cali 网卡 calife4cf73bf2a 的 MAC 地址),最后流量到达节点。

接下来,我们再来看一下副本 zlw-01 的流量如何从节点上转发到同节点上的副本 zlw-02

主机到容器

先看一下副本 zlw-02 的网络设备:

$ kubectl exec -it zlw-02 -- 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: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: eth0@if3121: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 7a:45:c9:63:d5:75 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.233.133.3/32 scope global eth0
       valid_lft forever preferred_lft forever

随后我们可以在节点中找到索引编号为 3121 的网卡设备:

ip a | grep -A 3 3121
3121: cali957267df2c7@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 3
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever

因此可以确认,节点 10-23-14-110 上的 cali 网卡 cali957267df2c7 和副本 zlw-02 的 eth0 网卡是一对 veth pair。

随后我们查看节点 10-23-14-110 的路由表,可以看到以下内容:

$ ip route
10.233.133.2 dev calife4cf73bf2a scope link src 10.23.14.110
10.233.133.3 dev cali957267df2c7 scope link src 10.23.14.110

10.233.133.2calife4cf73bf2a 分别是副本 zlw-01 的 PodIP 和 cali 网卡, 10.233.133.3cali957267df2c7 则分别是副本 zlw-02 的 PodIP 和 cali 网卡。 scope link 说明是直连路由,意味着副本 zlw-01zlw-02 的 cali 网卡处于同一个节点节点的链路上。

10.233.133.2 访问 10.233.133.3 时,由于它们在同一个链路中,数据包可以直接通过 MAC 地址在二层链路上进行传输,无需经过额外的网关或 NAT 处理。发往 10.233.133.3 的数据包,从 zlw-01 的 eth0 网卡发出,经过对端的 calife4cf73bf2a 网卡后进入节点,然后在节点的二层网络中转发到 zlw-02cali957267df2c7 网卡。

最后流量会通过 cali957267df2c7 网卡到达对端的 zlw-02 的 eth0 网卡,从而进入到容器 zlw-02 中。我们可以在副本 zlw-02 的 eth0 网卡中监听到以下数据:

$ kubectl exec -it zlw-02 -- tcpdump -i eth0 -ne
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
04:29:50.685440 ee:ee:ee:ee:ee:ee > 7a:45:c9:63:d5:75, ethertype IPv4 (0x0800), length 98: 10.233.133.2 > 10.233.133.3: ICMP echo request, id 17, seq 1, length 64
04:29:50.685460 7a:45:c9:63:d5:75 > ee:ee:ee:ee:ee:ee, ethertype IPv4 (0x0800), length 98: 10.233.133.3 > 10.233.133.2: ICMP echo reply, id 17, seq 1, length 64

可以看到,当流量进入到副本 zlw-02 后,此时源 IP 是 10.233.133.2(副本 zlw-01 的 IP 地址),目标 IP 是 10.233.133.3(副本 zlw-02 的 IP 地址),源 MAC 地址是 ee:ee:ee:ee:ee:ee(副本 zlw-02 的 cali 网卡 cali957267df2c7 的 MAC 地址),目标 MAC 地址是 7a:45:c9:63:d5:75(副本 zlw-02 中的 eth0 网卡的 MAC 地址)。

总的来说,对于相同节点 Pod 的网络通信,流量会从一个副本的 eth0 网卡发出,通过 veth pair 到达节点上对端的 cali 网卡。由于两个 cali 网卡属于同一个节点的虚拟链路(通过 veth pair 与对应的容器直连),因此数据帧会通过 MAC 地址在二层链路上直接传输,节点内核会在二层网络通过虚拟链路的点对点连接将数据帧转发到另外一个副本的 cali 网卡。随后目标 cali 网卡会通过 veth pair 将数据帧转发到对应容器的 eth0 网卡,最后,响应的数据包再通过原路返回,完成整个通信的过程。

介绍完同节点 Pod 之间的网络通信,我们再来看看不同节点上 Pod 之间的通信过程有哪些相同点和不同点。

不同节点 Pod 网络通信

现在,我们在集群中新增一个名为 zlw-03 的副本,并将其与已有的副本 zlw-01 分别部署在不同的节点上。

$ kubectl get pods -owide
NAME     READY   STATUS     RESTARTS   AGE   IP             NODE          NOMINATED NODE    READINESS GATES
zlw-01   1/1     Running    0          1h    10.233.133.2   10-23-14-110   <none>           <none>
zlw-02   1/1     Running    0          1h    10.233.133.3   10-23-14-110   <none>           <none>
zlw-03   1/1     Running    0          1h    10.233.80.0    10-23-14-111   <none>           <none>

接下来,我们来看一下副本 zlw-03 的网络设备和路由规则:

$ kubectl exec -it zlw-03 -- 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: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
3: eth0@if328463073: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ce:fd:7e:63:4c:ab brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.233.80.0/32 scope global eth0
       valid_lft forever preferred_lft forever

$ kubectl exec -it zlw-03 -- ip r
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link

$ kubectl exec -it zlw-03 -- 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

随后在节点 10-23-14-111 中查看副本 zlw-03 的 cali 网卡:

$ ip a | grep -A 3 328463073
328463073: cali20e9b05e2d6@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet6 fe80::ecee:eeff:feee:eeee/64 scope link
       valid_lft forever preferred_lft forever

因此,我们可以看到副本 zlw-03 的 PodIP 是 10.233.80.0,eth0 网卡的 MAC 地址是 ce:fd:7e:63:4c:ab。veth pair 对端的 cali 网卡名称是 cali20e9b05e2d6,MAC 地址是 ee:ee:ee:ee:ee:ee

容器到主机

我们在副本 zlw-01 向副本 zlw-03 发起网络请求:

$ kubectl exec -it zlw-01 -- ping -c 1 10.233.80.0
PING 10.233.80.0 (10.233.80.0) 56(84) bytes of data.
64 bytes from 10.233.80.0: icmp_seq=1 ttl=62 time=0.382 ms

--- 10.233.80.0 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.382/0.382/0.382/0.000 ms

流量会从副本 zlw-01 的 eth0 网卡发出,经过对端的 calife4cf73bf2a 网卡,最终到达节点 10-23-14-110,这个过程和我们之前讨论的流量从容器到主机的的方式没有差别。

接下来我们重点看一下流量如何在主机之间进行转发。

主机到主机

先查看节点 10-23-14-110 的路由规则:

$ ip route
10.233.133.2 dev calife4cf73bf2a scope link src 10.23.14.110
10.233.133.3 dev cali957267df2c7 scope link src 10.23.14.110
10.233.80.0/24 via 10.23.14.111 dev eth0 proto bird

和之前相比,主机中新增了一条路由规则: 10.233.80.0/24 via 10.23.14.111 dev eth0 proto bird。这条规则的意思是:对于目标地址在 10.233.80.0/24 网段内的数据包,设备将通过 eth0 接口发给下一跳 10.23.14.111。这条路由是 Calico 的 BGP 客户端 BIRD 组件获取的。

接下来我们再来看一下节点 10-23-14-110 eth0 的网卡信息:

ip a show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:a9:e1:02 brd ff:ff:ff:ff:ff:ff
    inet 10.23.14.110/24 brd 10.23.14.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fea9:e102/64 scope link
       valid_lft forever preferred_lft forever

可以看到节点 10-23-14-110 的 MAC 地址是 00:50:56:a9:e1:02

接下来,节点 10-23-14-110 将会通过 ARP 协议解析下一跳 10.23.14.111 的 MAC 地址。其中, 10.23.14.111 是节点 10-23-14-111 eth0 网卡的 IP 地址,我们可以在节点 10-23-14-111 上执行以下命令:

ip a show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:a9:85:40 brd ff:ff:ff:ff:ff:ff
    inet 10.23.14.111/24 brd 10.23.14.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::250:56ff:fea9:8540/64 scope link
       valid_lft forever preferred_lft forever

可以看到节点 10-23-14-111 的 MAC 地址是 00:50:56:a9:85:40

在执行 kubectl exec -it zlw-01 -- ping -c 1 10.233.80.0 命令后,我们可以在节点 10-23-14-111 中通过 tcpdump 命令监测到以下数据:

tcpdump -i eth0 -ne | grep "10.233.80.0"
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
02:19:54.751970 00:50:56:a9:e1:02 > 00:50:56:a9:85:40, ethertype IPv4 (0x0800), length 98: 10.233.133.2 > 10.233.80.0: ICMP echo request, id 23, seq 1, length 64
02:19:54.752045 00:50:56:a9:85:40 > 00:50:56:a9:e1:02, ethertype IPv4 (0x0800), length 98: 10.233.80.0 > 10.233.133.2: ICMP echo reply, id 23, seq 1, length 64

可以看到,副本 zlw-01 访问 zlw-03 的流量,会通过节点 10-23-14-110 发送到 10-23-14-111,这个过程中:

  • 源 IP 是 zlw-01 的 PodIP 10.233.133.2
  • 源 MAC 地址是 10-23-14-110 节点 eth0 网卡对应的MAC 地址 00:50:56:a9:e1:02
  • 目标 IP 是 zlw-03 的 PodIP 10.233.80.0
  • 目标 MAC 地址是 10-23-14-111 节点 eth0 网卡对应的MAC 地址 00:50:56:a9:85:40

当流量进入到节点 10-23-14-111 后,再从对应的 cali 网卡将流量转发到副本 zlw-03 中。

主机到容器

我们看一下节点 10-23-14-111 上关于副本 zlw-03 的路由规则:

ip r | grep 10.233.80.0
10.233.80.0 dev cali20e9b05e2d6 scope link src 10.23.14.111
blackhole 10.233.80.0/24 proto bird

可以看到,发往 10.233.80.0 网段的数据包将通过设备 cali20e9b05e2d6 进行发送,而 cali20e9b05e2d6 就是副本 zlw-03 在节点上的 cali 网卡,后续流量会通过这个 cali 网卡到达副本 zlw-03 的 eth0 网卡,最近进入容器。这个过程和我们之前讨论的流量从主机到容器的方式没有差别。

总结

经过我前面的介绍,相信大家已经清楚了 Calico BGP 在 Full Mesh 模式下同节点和不同节点上 Pod 的网络通信原理:各节点通过 BGP 协议建立对等连接实现路由信息的交换,随后通过 Linux veth pair 实现容器和主机网络的通信,再通过主机路由实现数据包在节点之间的转发。

在 Full Mesh 模式下,每个节点都会和其他节点建立起 BGP 对等连接,然而 BGP 连接的数量会随着节点数量按照 n*(n-1)/2 的规模增长,这种增长方式在节点数量较大时会给集群的网络带来较大的压力,导致路由更新的效率会显著降低。因此,Full Mesh 模式通常更适合节点规模相对较小的中小型集群,官方推荐适用的节点规模最好是在 100 个以内。如果我们需要在大规模的集群上部署时,可以选择反射器模式(Route Reflector),或者在本地部署场景下与机架顶部(ToR)路由器建立对等连接的方式部署。

在反射器模式中,部分节点被设置为路由反射器,这些路由反射器之间构建成网状结构,而其他节点只需要跟部分路由反射器(通常为2个,以实现冗余)建立对等连接。这种方式相较于 Full Mesh 模式,每个节点的 BGP 对等连接数量会大幅减少,因此反射器模式比 Full Mesh 模式更适合大规模集群,缺点是它的配置也相对来说更复杂。

本期关于 Calico BGP 模式的内容分享就分享到这里,后续我会继续介绍 Calico 在 IPIP 和 VXLAN 模式下的网络通信过程,以及如何为集群选择合适的网络模式,我们下期再见!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件举报,一经查实,本站将立刻删除。

文章由技术书栈整理,本文链接:https://study.disign.me/article/202520/6.kubernetes-calico-bgp.md

发布时间: 2025-05-12