
BGP 路由反射器RR减少了网络中对全互联full-mesh内部 BGPiBGP对等连接的需求使用 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-15)/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 方式相互连接。但在真实场景中有物理交换机 ToRTop of Rack他们天然充当 RR 节点这时做法就变成了 注本文中出现的交换机均指硬件上是交换机同时支持路由功能 3 层交换机。本实验中 VyOS 叫软路由干的活和真实数据中心的三层交换机是一样的既做二层交换又跑 BGP 路由所以在 Spine-Leaf 架构里不要把交换机和路由器分开理解它们都是能跑 BGP 的网络设备只是所处层级不同k8s node 不需要作为 RR 节点机架/机柜顶部的 ToR 交换机就是 RR而 k8s node 与 ToR 建立 iBGPToR 之间通过上层网络Spine交换路由。本文实验场景构造通过 VyOS 模拟真实场景案例不从 k8s node 选择 RR直接连接模拟出来的 leaf 路由器VyOS 底层是用 FRR 做路由引擎他只是提供了一个封装层CLI 配置管理核心路由能力来自 FRR。Rack 1 · 10.1.8.0/24 · AS 65008Rack 0 · 10.1.5.0/24 · AS 65005Spine 核心层spine0spine1leaf0 路由器\n10.1.5.1server1 ↔ control-plane\n10.1.5.10\nPodCIDR: 10.244.0.0/24server2 ↔ worker\n10.1.5.11\nPodCIDR: 10.244.1.0/24leaf1 路由器\n10.1.8.1server3 ↔ worker2\n10.1.8.10\nPodCIDR: 10.244.2.0/24server4 ↔ worker3\n10.1.8.11\nPodCIDR: 10.244.3.0/24Rack 1 · AS 65008 iBGPRack 0 · AS 65005 iBGPSpine eBGPeBGPeBGPeBGPeBGPiBGP · RR Client\n同 AS 65005iBGP · RR Client\n同 AS 65005iBGP · RR Client\n同 AS 65008iBGP · RR Client\n同 AS 65008spine0 · AS 500spine1 · AS 800leaf0 路由反射器\n10.1.5.1control-plane\n10.1.5.10worker\n10.1.5.11leaf1 路由反射器\n10.1.8.1worker2\n10.1.8.10worker3\n10.1.8.111.主脚本Kind 创建的集群所有节点在同一子网中没有中间路由器无法模拟真实的跨机架/跨交换机场景所以需要下面 containerlab 脚本创建额外的容器模拟路由器。#!/bin/bash set -v # 1. Prepare NoCNI environment cat EOF | HTTP_PROXY HTTPS_PROXY http_proxy https_proxy kind create cluster --namecalico-bgp-rr --imagekindest/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: rackrack0 - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.5.11 node-labels: rackrack0 - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.8.10 node-labels: rackrack1 - role: worker kubeadmConfigPatches: - | kind: JoinConfiguration nodeRegistration: kubeletExtraArgs: node-ip: 10.1.8.11 node-labels: rackrack1 EOF # 2. Remove taints controller_node_ipkubectl 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 21 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 21 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.cfgroute-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