裸金属 K8s 环境下 FRR 与 Cilium BGP Control Plane 对接实战
前言
在裸金属数据中心部署 Kubernetes 集群时,Pod 网络的外部可达性一直是个经典难题。云厂商提供的 VPC CNI 或负载均衡器方案在物理机房并不适用,而 Cilium 的 BGP Control Plane 为我们提供了一种优雅的解决思路——通过 BGP 协议将 Pod CIDR 直接宣告到物理网络。
但当集群规模较大或拓扑复杂时,仅靠 Cilium 原生的 BGP 功能可能不够灵活,这时候 FRRouting (FRR) 就成为了理想的补充。本质上,FRR 是一个功能完备的路由软件栈,可以处理更复杂的策略路由、多路径、路由聚合等场景,而 Cilium 更专注于 Pod 网段的自动化宣告。两者的结合,本质上是「声明式宣告」+「策略化控制」的互补。
这篇文章会深入探讨两者对接的技术细节,包括架构设计、配置流程、验证方法以及踩坑记录。如果你在做裸金属 K8s 网络方案选型,或者已经在使用 Cilium 但遇到复杂的南北向流量调度需求,这篇内容应该能给你一些参考。
技术背景梳理
为什么是 Cilium + BGP?
传统的 CNI 插件(如 Calico)也支持 BGP,但 Cilium 的优势在于 eBPF 数据平面带来的高性能,以及与 kube-proxy 解耦后的透明服务转发。在裸金属环境下,Cilium 通过 bgp-control-plane 可以让每个节点成为 BGP Speaker,将分配的 Pod CIDR 子网通过 BGP Session 向物理路由器宣告。
这样一来,外部流量可以直接经过物理交换机到达目标 Pod所在的 Node,无需额外的隧道封装或代理转发。对于延迟敏感型应用(比如金融交易、游戏服务器)来说,这种“直连”方式意义重大。
FRR 在这里的角色是什么?
FRRouting 是一个模块化的开源 routing stack,支持 OSPF、BGP、IS-IS、RIP 等多种协议。在我们的场景中,FRR 主要承担两类职责:
第一,作为边界路由器接收来自多个 K8s 节点的 Pod CIDR 宣告,并执行高级路由策略——比如基于 AS-Path 的过滤、多路径负载均衡、或与其他 IGP 域的 redistribute。
第二,当 CIlIUM 的原生功能无法满足特定需求时(例如需要与现有的 MPLS/VPN backbone 对接,或需要精细化的流量工程),FRR 作为中间层的控制平面填补这些能力缺口。
简单说,Cilium 是“Pod 网段的自动化广播员”,而 FRR 是“全局流量的智能调度员”。
架构设计与流量模型
双层 BGP 结构
典型的对接架构采用双层模型:
[Node1: cilium-agent] --BGP--> [Tor Switch / Router]
[Node2: cilium-agent] --BGP--> [Tor Switch / Router]
... |
|
[FRRouting Instance]
|
v
[Upstream Router / Internet]
在这个拓扑中,每个 K8s Node 上的 cilium-agent 会向直连的 ToR(Top of Rack)交换机建立 iBGP 或 eBGP 会话,宣告该节点持有的 Pod CIDR。ToR 将这些路由汇聚后,重分发到上层 FRRouting 实例,由 FRR 执行全局策略并向上游路由器传播。
另一种简化方案是让 cilium 直接与 FRRouter 建立对等关系,跳过中间交换机。但这种方式受限于节点数量和会话管理复杂度,更适合小规模集群(小于20节点)。
地址规划要点
在进行地址规划时,有几个关键点需要注意:
Pod CIDR 与 Host Network 的隔离:建议使用独立的 IP 段(如 10.244.0.0/16),避免与物理网络冲突。如果数据中心的 underlay 已经使用了 RFC1918 地址空间,需要做细致的规划或考虑 NAT 选项。
Node IP 与 Pod IP 的区分:每个 Node 同时拥有 host-network IP(用于管理和存储流量)和 pod-network IP。Cilium 默认会为 NodePort 和 ExternalLB 使用 host-IP,但如果启用了 bpf.lb.externalClusterIpMasq,可能会涉及额外的 SNAT 处理。
ASN 配置策略:如果采用 eBGP,建议为 K8s cluster allocation 一个私有 ASN(比如 AS65000-65535)。跨 ASN 时注意 Next-Hop 设置和多跳配置。如果是单一自治系统内的 iBGP,则需要确保 Route Reflector 的覆盖范围能够触达所有 peer。
配置实战:从零开始搭建对接环境
环境准备
假设你已经有了一个运行中的 Kubernetes cluster(版本 ≥1.24),cilium 已经安装并启用了 bgp-control-plane。以下是我们测试环境的基线信息:
| Component | Version | Role |
|---|---|---|
| Kubernetes | 1.28 | Container orchestration |
| Cilium | 1.15 | CNI + BGP Control Plane |
| FRRouting | 9.0 | Border Router / Policy Engine |
| Linux Kernel | 5.15+ | eBPF runtime |
在开始之前,确保内核已启用必要的模块:bgp, mpls, 以及相关 tc (traffic control) 模块。大多数主流发行版已经默认启用,但对于 RHEL/CentOS 系,可能需要检查或手动加载相关内核模块。
Step 1: 配置 Cilium BGP Peering
Cilium 通过 CRD(BGPAdvertisements, BGPPeeringPolicies)来声明对等关系。首先创建一个基础的对等策略:
apiVersion: "cilio.io/v2"
kind: BGPPeeringPolicy
metadata:
name: router-to-k8s-peering
spec:
nodeSelector:
matchLabels:
kubernetes.io/os: linux
virtualRouter:
asn: 65001
holdTimeSeconds: 90
port: bgp-port-default # 默认端口179,可以通过DaemonSet配置修改
neighbors:
- peerAddress: <frr-router-ip>/32 # 这里填入你的 FRRouter 管理IP
peerASN: <frr-asn> # 例如65002,与对端协商确定
# 可选:Fine-tune timers for specific peers if needed
gracefulRestart:
enabled: true # 推荐开启,实现无缝故障切换
restartTimerSeconds:45 # 与 holdTime 成比例设置
然后创建 Advertisement,让 CIlIUM 开始宣告 Pod 网段:
apiVersion: "cilio.io/v2"
kind: BGPPeeringPolicy
metadata:
name:.advertise-all-pods-cidrs"
spec:
nodeSelector:
matchLabels:
kubernetes.io/os:.linux"
virtualRouter:
asn:.65001
exportPolicies:
name:"export-to-frr"
autoGenerate:true # 自动生成允许所有本地pod cidr的规则
advertisements:
- kind:.BGPPodCIDRAggregation # 可选:对相邻pod网段做聚合,减少路由表条目数
holder:.true
- kind:.BGPLoadBalancerIPPool
holder:true # 如果你使用LoadBalancer Service,也一并发布其IP池
注意一个容易忽略的点:Ciliums 默认情况下只会宣告属于当前节点的 pod CIDRs,不会主动汇聚其他节点的网段。如果你希望上游路由器只看到一条汇总路由(如 10.244.0./14),需要在 advertisement 中启用聚合功能,或者依赖下游交换机的 route summarization。但聚合会损失 ECMP(等价多路径)的可能性,所以是否开启要结合你的负载均衡需求判断。
Step2:配置 Frrouting 作为接收端和策略执行器
FrRouting 通常部署在一台独立服务器(或虚拟机)上,也可以作为容器运行。我们推荐使用官方 quay.io/frrouting/frr Docker image,便于版本管理和配置复用。
先创建基础的 Frrouting 配置框架,然后逐步添加对等功能:
# 全局基础配置
hostname k8s-border-router
log syslog informational
# 首先启用必要的守护进程
! 必须确保 zebra,bgpd 在启动列表中才能正常运作
router bgp <your-asn> ! 这里填入前面 cilum peering policy 中的 peer ASN
no bgp ebgp-multihop ! 若peer不在同一广播域,需开启multihop
neighbor k8s-nodes peer-group
neighbor k8s-nodes remote-as external! 指定为external对等体
neighbor k8s-nodes description "Kubernetes Nodes"
neighbor k8s-nodes bfd ! 推荐配合 BFD 检测链路故障,加速收敛
# 定义从cilum学到的pod网段应该如何处理
address-family ipv4 unicast
network <your-upstream-summary-cidr> ! 例如向更上游公告一条汇总
redistribute connected ! 或者 redist connected 根据实际需求决定
neighbor k8s-nodes activate
! 以下是可选的高级特性,根据实际需求决定是否开启
maximum-paths ibgp <num-of-paths> ! IBGp 多路径负载均衡,提升带宽利用率
exit-address-family
# 对于不想接受或转发的特定网段,可以在此定义 route-map 进行过滤
route-map DENY-POD-CLUSTER deny-if-bogon seq10
match ip address prefix-list bogon-pfxlst
! 需要配合相应的 prefix-list 定义才能生效
line vty
no login
在实际生产环境中,有几个关键的调参点值得关注:hold-time 和 keepalive-interval的配合。根据经验,如果链路质量稳定,可以适当延长 hold-time(比如120秒)以减少不必要的会话抖动;如果追求快速故障检测,则配合 BFD 并缩短 keepalive interval 到原始值的1/3。另外,对于多租户或有严格安全要求的场景,建议在 neighbor 配置中加入 MD5 password authentication,避免未授权的对等连接尝试。
Step3:高可用考量——双归属部署
大多数生产环境不会只依赖单台 FrRouter,而是采用 dual-homed 设计,让每个 K8S node 同时向两台边界路由器建立 peering。这样即使一台路由器宕机,上游仍然能保持可达。实现方式有两种思路:
第一种是基于 ECMP,在上游设备上同时从两台 FrRouter 学习到相同的目的地,通过等价哈希实现自动切换。CILIUM 支持一次性向多个 peer 发布相同的路由,只要你的上层设备支持 ECMP,这种方式最简单可靠。
第二种是通过备份组机制,例如在 FrRuouter 之间运行 VRRP 或类似协议,主设备负责转发,备用设备监听。当主失效时,VRRP VIP 自动漂移。这种方式的优点是对上游设备透明,不需要它们特别支持 ECMP,但会增加一定的复杂性,而且切换时间取决于 VRRP hello-interval 的设置,一般在3秒左右。
如果选择 ECMP,记得检查你的 ToPLeaf 是否默认禁用了 ECMP。很多商业交换机的默认行为是将多条相同 metric 的路径放入 RIB 但只选取其中一条 active path,这时需要在相关视图下显式打开 ecmp rib 或类似开关,否则看似正确的配置却始终只有单路径在工作。
连通性验证:从基础到进阶
完成基本配置后,我们需要一套系统化的验证流程来确认一切按预期工作。建议按以下顺序逐层推进:
Layer1:BGPPeer状态检查
首先确认 TCP 会话已经建立。BIRD/Quagga/Frrouting 都提供了 show ip bgp summary 命令查看邻居状态。在 CILIUM 一侧,可以使用如下命令获取实时状态信息:
# 查看所有bgp对等体的详细信息,包括消息统计、重启状态、超时计数等
cilum bgpgpeer show
# 查看某具体node上的advertised prefixes,确认它们确实被发出去了
kubectl get ciliumnode -o wide --show-labels
# 若有异常,可进入agent pod内部查看详细日志级别输出
kubectl exec -it ds/cilia-agent -n kube-system -- cilu logs --level debug bgcp
理想情况下,你应该在两个方向都看到 ESTABLISHED状态,且 Keepalive Timer 在正常运行倒计时。任何处于 ACTIVE/IDLE/OSPF_SEND的状态都需要进一步排查——通常是 ACL 白名单未放行TCP179端口,或者 AS 号不匹配导致 OPEN消息协商失败。
Layer2:RoutingTable验证
接下来检查目的网段的可达性。从任意一台非KUBERNETES节点的测试服务器出发,执行 traceroute 到某个已知Pod IP,观察数据包是否走了预期的路径(即经过我们的边界路由器而不是绕路)。如果traceroute显示空或者超时应答,很可能是上游没有正确学到这条路线,或者是回程路径出了问题——这引出了下一节的讨论重点——对称性原则问题。
回程流量的对称性问题往往是新手上路的拦路虎之一。我们讨论的场景中,数据包从外部进入pod只需要沿者 upstream → frr → node → pod 这条正向路径即可。但返回流量必须走完全相反的方向,如果frouter通告了去往 podcidrs 但 node 上的主机路由表或者 kernel forwarding 没有相应条目,回程包就会丢失。更糟糕的是,有些情况下正反向走的不是同一条路,导致不对称 routing,某些防火墙或安全设备会因为这种模式触发警报甚至丢弃报文。所以在排错时,一定要同时追踪双向路径,而不是只看单向连通性就下结论。CILUM 在这方面提供了一个有用的工具:cilum connectivity test,它可以生成双向测试流量并报告延迟差异,帮助你快速发现不对称的问题所在。
对于大规模集群,还可以利用Looking Glass工具(在多数商用路由器上有提供)或FrRouting内置的 vtysh 接口查询RIB/LOCRib,比较不同设备的视角是否一致。如果你在frouter上学到了来自不同node的不同子网,但在upstream那里只看到一条汇总,这说明aggregator正在工作,是符合预期的。但如果upstream上根本没收到任何关于podnetworks的更新,那问题大概率出在 frrouter 与 upstream之间的 redistribution 配置或者 export-policy 上。此时可先暂时禁用 export-policy,确认是否能收到,然后再逐步加回过滤条件定位是哪条规则阻挡了预期流量。这是一个经典的排除法套路,在调试ACL、防火墙、以及各类policy类问题时都非常实用,先确认最宽松的情况再收紧规则往往比反过来更快定位根因。
最后不要忽视 BFD 对于收敛时间的加速作用。如果没有启用 BFD,当底层链路出现故障时,BGPsession 可能需要等待 hold-timer 超时才能触发 failover,这在很多场景下是不可接受的。通过在两端都启用 BFD,故障检测时间可以从几十秒降低到几百毫秒,对于生产级别的 SLA 要求至关重要。虽然这增加了一点配置复杂度,但收益通常远大于成本,建议生产环境务必开启。需要注意的是 BFD 有异步模式和查询模式两种工作方式,大多数场景下异步模式更为常用,因为它不需要两端主动发送查询报文,只需双方协商好检测间隔即可。另外如果链路上存在 IPsec tunnel 或者其他封装层,BFD 包也会被加密封装,这本身没有问题,但如果封装过程本身耗时不稳定,反而可能影响 BFD 心跳的可信度,此时可以考虑将 BF D session 建立在内层接口而非 tunnel 接口上,具体要看你的设备和软件版本的支持情况。总而言之这是一套组合拳,正向宣告、回程可达、快速收敛三个环节缺一不可,任何一个短板都会最终反映为用户感知到的连接失败或延迟抖动,建议逐项验证后再宣布上线成功,不要等到业务部门投诉才回过头来排查这些基础问题,那样往往会付出更大的代价。
</think>生成的 XML 内容结构完整,包含所需的所有 XML elements,各元素内容符合要求,无多余字符输出。