BGP 路由反射器(RR)减少了网络中对全互联(full-mesh)内部 BGP(iBGP)对等连接的需求,使用 full-mesh 时:
- 每台 iBGP 路由器都必须与所有其他 iBGP 路由器建立邻居关系
而使用路由反射器(RR)时,同一自治系统(AS)内的路由器只需与 RR 建立邻居关系即可。RR 将路由信息从一台 iBGP 路由器 "反射" 给其他的 iBGP 邻居。通过这种方式,就不需要构建 iBGP 全互联(full-mesh)拓扑了。这是一种 服务器-客户端 解决方案:其中 RR 充当服务器,其余所有 iBGP 路由器作为客户端。这种方案简化了配置,减少了 iBGP 建立邻居关系的数量,同时也节省了 CPU 和网络资源。
使用场景
在大型网络中使用 full-mesh 全互联时,iBGP 邻居关系的数量会成为一个问题。通过下图可以更清晰的说明这点:
如上图所示,一个包含六台 iBGP 路由器的网络。利用全互联(Full Mesh)公式N(N-1)/2,可以计算出 iBGP 对等体的数量:
- 6(6-1=5)/2 = 15 个 iBGP 对等连接。
当我们使用路由反射器时,网络拓扑大概长这样:
这六台路由器其中五台,仅与顶层的路由反射器建立了 iBGP 对等连接。当其中一台 iBGP 路由器向路由反射器通告某条路由时,该路由将被反射给所有其他的 iBGP 路由器。
这极大地简化了 iBGP 配置,但也存在一个弊端:如果路由反射器发生故障怎么办?在上图场景中,它构成了一个单点故障。对此的解决方案是,可以在网络中部署多个路由反射器,对路由反射器之间配置 full-mesh 全互联模式。
部署流程
通过 Kind 快速生成集群并部署 Calico BGP Route Reflector 模式
默认情况下,Calico 常见的 RR 方式是:
- 选择几个 k8s node 作为 RR 节点,其余 k8s node 作为 Client 连接这些 RR 节点;
- 而 RR 节点之间通过 iBGP FullMesh 方式相互连接。
但在真实场景中,有物理交换机 ToR(Top of Rack),他们天然充当 RR 节点,这时做法就变成了:
🔴 注:本文中出现的交换机均指硬件上是交换机,同时支持路由功能 3 层交换机。
- 本实验中 VyOS 叫"软路由",干的活和真实数据中心的"三层交换机"是一样的:既做二层交换又跑 BGP 路由;
- 所以,在 Spine-Leaf 架构里,不要把"交换机"和"路由器"分开理解,它们都是能跑 BGP 的网络设备,只是所处层级不同!
- k8s node 不需要作为 RR 节点;
- 机架/机柜顶部的 ToR 交换机就是 RR,而 k8s node 与 ToR 建立 iBGP;
- ToR 之间通过上层网络(Spine)交换路由。
本文实验场景构造,通过 VyOS 模拟真实场景案例,不从 k8s node 选择 RR,直接连接模拟出来的 leaf 路由器:
VyOS 底层是用 FRR 做路由引擎,他只是提供了一个封装层(CLI + 配置管理),核心路由能力来自 FRR。
Rack 1 · 10.1.8.0/24 · AS 65008
Rack 0 · 10.1.5.0/24 · AS 65005
Spine 核心层
spine0
spine1
leaf0 路由器\n10.1.5.1
server1 ↔ control-plane\n10.1.5.10\nPodCIDR: 10.244.0.0/24
server2 ↔ worker\n10.1.5.11\nPodCIDR: 10.244.1.0/24
leaf1 路由器\n10.1.8.1
server3 ↔ worker2\n10.1.8.10\nPodCIDR: 10.244.2.0/24
server4 ↔ worker3\n10.1.8.11\nPodCIDR: 10.244.3.0/24
Rack 1 · AS 65008 iBGP
Rack 0 · AS 65005 iBGP
Spine eBGP
eBGP
eBGP
eBGP
eBGP
iBGP · RR Client\n同 AS 65005
iBGP · RR Client\n同 AS 65005
iBGP · RR Client\n同 AS 65008
iBGP · RR Client\n同 AS 65008
spine0 · AS 500
spine1 · AS 800
leaf0 路由反射器\n10.1.5.1
control-plane\n10.1.5.10
worker\n10.1.5.11
leaf1 路由反射器\n10.1.8.1
worker2\n10.1.8.10
worker3\n10.1.8.11
1.主脚本
Kind 创建的集群所有节点在同一子网中,没有中间路由器,无法模拟真实的跨机架/跨交换机场景,所以需要下面 containerlab 脚本创建额外的容器,模拟路由器。
#!/bin/bash set -v # 1. Prepare NoCNI environment cat << EOF | HTTP_PROXY= HTTPS_PROXY= http_proxy= https_proxy= kind create cluster --name=calico-bgp-rr --image=kindest/node:v1.27.3 --config=- kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 networking: disableDefaultCNI: true podSubnet: "10.244.0.0/16" nodes: - role: control-plane kubeadmConfigPatches: - | kind: InitConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.5.10 node-labels: "rack=rack0" - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.5.11 node-labels: "rack=rack0" - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.8.10 node-labels: "rack=rack1" - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.8.11 node-labels: "rack=rack1" EOF # 2. Remove taints controller_node_ip=`kubectl get node -o wide --no-headers | grep -E "control-plane|bpf1" | awk -F " " '{print $6}'` kubectl taint nodes $(kubectl get nodes -o name | grep control-plane) node-role.kubernetes.io/control-plane:NoSchedule- ./2-setup-clab.sh # 3. Collect startup message controller_node_name=$(kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' | grep control-plane) if [ -n "$controller_node_name" ]; then timeout 1 docker exec -t $controller_node_name bash -c 'cat << EOF > /root/monitor_startup.sh #!/bin/bash ip -ts monitor all > /root/startup_monitor.txt 2>&1 EOF chmod +x /root/monitor_startup.sh && /root/monitor_startup.sh' else echo "No such controller_node!" fi # 4. Install calico and enabel bgp configuration ./3-prep-calico-bgp.sh2.通过 containerlab 创建网卡绑定至集群
创建 VyOS(一种虚拟路由器系统)容器模拟物理交换机,并用 Linux 网桥把 Kind 节点和虚拟交换机连接起来,形成一个完整的 Spine-Leaf 网络拓扑。
2.1.通过 containerlab 创建容器与 kind 集群共享网络空间
./2-setup-clab.sh
#!/bin/bash set -v for br in br-leaf0 br-leaf1; do ip link set $br down > /dev/null 2>&1 ip link delete $br ip link add $br type bridge ip link set $br up done cat << EOF >clab.yaml | containerlab deploy -t clab.yaml - name: calico-bgp-rr topology: nodes: ## 模拟核心层交换机 spine0: kind: linux image: burlyluo/vyos:1.4.9 cmd: /sbin/init binds: - /lib/modules:/lib/modules - ./startup-conf/spine0-boot.cfg:/opt/vyatta/etc/config/config.boot spine1: kind: linux image: burlyluo/vyos:1.4.9 cmd: /sbin/init binds: - /lib/modules:/lib/modules - ./startup-conf/spine1-boot.cfg:/opt/vyatta/etc/config/config.boot ## 这两个 leaf 网桥模拟接入层交换机(BGP Route Reflector) leaf0: kind: linux image: burlyluo/vyos:1.4.9 cmd: /sbin/init binds: - /lib/modules:/lib/modules - ./startup-conf/leaf0-boot.cfg:/opt/vyatta/etc/config/config.boot leaf1: kind: linux image: burlyluo/vyos:1.4.9 cmd: /sbin/init binds: - /lib/modules:/lib/modules - ./startup-conf/leaf1-boot.cfg:/opt/vyatta/etc/config/config.boot ## 二层网桥 br-leaf0: kind: bridge br-leaf1: kind: bridge server1: kind: linux image: hub.deepflow.yunshan.net/network-demo/nettool network-mode: container:calico-bgp-rr-control-plane exec: - ip addr add 10.1.5.10/24 dev net0 - ip route replace default via 10.1.5.1 server2: kind: linux image: hub.deepflow.yunshan.net/network-demo/nettool network-mode: container:calico-bgp-rr-worker exec: - ip addr add 10.1.5.11/24 dev net0 - ip route replace default via 10.1.5.1 server3: kind: linux image: burlyluo/nettool:latest network-mode: container:calico-bgp-rr-worker2 exec: - ip addr add 10.1.8.10/24 dev net0 - ip route replace default via 10.1.8.1 server4: kind: linux image: burlyluo/nettool:latest network-mode: container:calico-bgp-rr-worker3 exec: - ip addr add 10.1.8.11/24 dev net0 - ip route replace default via 10.1.8.1 ## 用 links 把所有设备连起来后,Kind 节点流量就必须经过 leaf 交换机才能跨机架通信: ## - server1/server2 → br-leaf0(rack0 的节点连到 rack0 的网桥) ## - server3/server4 → br-leaf1(rack1 的节点连到 rack1 的网桥) ## - leaf0 → br-leaf0(leaf 交换机连到对应网桥) ## - leaf1 → br-leaf1 ## - leaf0/leaf1 → spine0/spine1(上联到核心交换机) links: - endpoints: ["br-leaf0:br-leaf0-net0", "server1:net0"] mtu: 1500 - endpoints: ["br-leaf0:br-leaf0-net1", "server2:net0"] mtu: 1500 - endpoints: ["br-leaf1:br-leaf1-net0", "server3:net0"] mtu: 1500 - endpoints: ["br-leaf1:br-leaf1-net1", "server4:net0"] mtu: 1500 - endpoints: ["leaf0:eth1", "spine0:eth1"] mtu: 1500 - endpoints: ["leaf0:eth2", "spine1:eth1"] mtu: 1500 - endpoints: ["leaf0:eth3", "br-leaf0:br-leaf0-net2"] mtu: 1500 - endpoints: ["leaf1:eth1", "spine0:eth2"] mtu: 1500 - endpoints: ["leaf1:eth2", "spine1:eth2"] mtu: 1500 - endpoints: ["leaf1:eth3", "br-leaf1:br-leaf1-net2"] mtu: 1500 EOF2.2.VyOS 路由器配置
./startup-conf/spine1-boot.cfg
route-reflector-client 是整个实验的核心:它让 leaf 交换机成为 BGP 路由反射器。K8s 节点不需要彼此建立 BGP 会话,只需要和 leaf 交换机建立会话,leaf 会把从一个节点学到的路由 "反射" 给其他节点。
interfaces { ## 通过上方脚本 2 将 leaf0:eth1 连向 spine0:eth1 ethernet eth1 { address "10.1.10.1/24" duplex "auto" mtu "9000" speed "auto" } ## 通过上方脚本 2 将 leaf0:eth2 连向 spine1:eth1 ethernet eth2 { address "10.1.12.1/24" duplex "auto" mtu "9000" speed "auto" } ## 通过上方脚本 2 将 leaf0:eth3 连向 br-leaf0:br-leaf0-net2 ethernet eth3 { address "10.1.5.1/24" duplex "auto" mtu "9000" speed "auto" } loopback lo { } } ## 让容器内的所有内网 IP 能通过 eth0 访问外网 nat { source { rule 100 { outbound-interface { ## eth0 是 containerlab 自动创建的管理口 name "eth0" } source { ## 匹配的内网范围,这覆盖了所有内网子网: ## - 10.1.5.0/24(rack0 节点) ## - 10.1.8.0/24(rack1 节点) ## - 10.1.10.0/24、10.1.12.0/24(互联子网) ## - 10.244.0.0/16(Pod 子网,虽然不在 /16 范围内,但 Pod 路由会被 BGP 处理) address "10.1.0.0/16" } translation { ## 动作: SNAT ## 就是把源地址改成 eth0 自己的 IP(宿主机 docker 网络分配的地址),对端看到的来源就是 eth0 的地址,而不是原始的 10.1.x.x 内网地址 ## 当源地址是 10.1.0.0/16 且从 eth0 出去的流量,就把源地址伪装成 eth0 的 IP ## 没有这条规则的话,K8s 节点发出的包到达外网时,外网不知道怎么回包到 10.1.x.x 这些私有地址 address "masquerade" } } } } protocols { bgp { ## 自治系统编号 system-as "65005" address-family { ## 主动宣告: 要去这些网段,把包交给我(leaf0) ipv4-unicast { network 10.1.5.0/24 { } network 10.1.10.0/24 { } network 10.1.12.0/24 { } } } ## 与谁建立 BGP 关系: ## 与同子网 k8s node 建立 iBGP 互联: system-as/remote-as 一致 neighbor 10.1.5.10 { address-family { ipv4-unicast { ## 下一跳是我自己 nexthop-self ## 正常情况下 BGP 从邻居学来的路由不会再转发给其他邻居; ## 添加此配置后,leaf0 就变成了路由反射器 route-reflector-client } } remote-as "65005" } neighbor 10.1.5.11 { address-family { ipv4-unicast { nexthop-self route-reflector-client } } remote-as "65005"