BGP路由反射器原理与配置实战:告别iBGP全互联
1. 为什么全网互联的 iBGP 那么难伺候BGP路由反射器这个特性我第一次接触的时候觉得它就是个“偷懒工具”让iBGP不用全网状互联少建几十条邻居配置量瞬间就下来了。后来在现网里被问题教育过几次才明白这东西远不是“省几行配置”那么简单。它牵涉到防环、选路、冗余、割接甚至多集群层次设计。这篇把RR的原理、配置、验证和常见坑一起梳理一遍适合正在给AS内iBGP会话做收敛的人也适合那些已经配了RR但遇到“路由学不全”“反射后消失”这类问题的人。1.1 iBGP 的全互联不是设计者在炫技为什么BGP非要搞全互联这得从BGP的防环逻辑说起。BGP的AS_PATH字段只能在EBGP之间传递时累积iBGP邻居之间不追加AS号。如果允许iBGP路由器把从某个iBGP邻居学到的路由再转发给另一个iBGP邻居很容易形成环路。所以BGP设计了一条规矩从iBGP邻居学到的路由不再通告给其他iBGP邻居。这就是常说的“iBGP水平分割”。这条规矩带来的结果是残酷的每一个iBGP路由器都必须和AS内所有其他iBGP路由器建立邻居关系才能保证全网路由可达。n台路由器的iBGP全互联数量是n(n-1)/2。四台是6条十台是45条五十台直接跳到1225条。到了数据中心Spine-Leaf规模稍微大一点光是维护邻居状态、排查对端抖动就够喝一壶。而且全互联不只是配置量大实际的故障半径也大。任意一台设备的重启、任何一条链路闪断都可能触发整个BGP会话表的震荡。每次震荡都要经过Open、Keepalive、UPDATE、Bestpath重算这一整串流程控制面的压力跟着上来。于是大家开始琢磨能不能允许一台设备“代表”其他iBGP路由器转发路由同时还能保证不环。1.2 路由反射器的核心价值路由反射器就是为这件事而生的。它的思路是说我不是一股脑转发所有iBGP路由而是按照事先约定的规则把从某些指定iBGP邻居学到的路由有选择地反射给其他邻居。被指定的那些邻居叫“客户端”反射器本身和客户端之间仍然是一条普通的iBGP会话只是反射器在逻辑上多了一个身份。这样做最大的收益是客户端之间不需要互相建iBGP邻居也不需要对全AS的iBGP路由器保持全互联。每个客户端只需要连到反射器路由由反射器统一扩散。这个“星型”结构把原来的O(n²)连接数降到了O(n)。但要注意反射器并没有改变BGP“请求”语义它仍然是控制面的功能。它只负责把路由信息反射过去不负责数据转发。数据面怎么走取决于IGP和接口路由这一点新手特别容易混。我见过有人以为把流量指向RRRR就能帮忙转发结果数据面黑洞。记住RR只是BGP路由信息的“中转站”不是流量转发节点。1.3 反射器也不是万金油和联盟Confederation相比路由反射器实现起来更简单兼容性也更好。联盟会把AS拆成多个子AS虽然能减少全互联但要把原AS内的配置全部重写一遍还要处理子AS之间对外表现的问题。绝大多数场景下RR是更优先的收敛手段。但RR也有自己的毛病。最明显的是它打破了水平分割就必须靠额外的属性来防环。这引出了ORIGINATOR_ID、CLUSTER_LIST这些概念同时RR反射路由时默认不改下一跳、不改AS_PATH、不改LocalPref所以反射后的路由是否可达仍取决于IGP对下一跳的可达性。很多人配完RR发现BGP表里有路由但进不了路由表多半就是下一跳不可达。另一个容易被忽略的问题是RR本身变成单点。反射器一旦挂了所有客户端之间就失去了路由交换通道。所以生产环境必须做RR冗余而且冗余不是简单堆两台设备就够了集群ID、客户端双连、防环策略都要一起设计。2. RR 的反射规则与防环机制2.1 三个角色的关系理解RR先分清三个角色。第一个是RR本身也就是配置了路由反射器功能的路由器。它会和两类邻居打交道客户端和非客户端。客户端是普通iBGP邻居只是被RR这边标记成了route-reflector-client。这个标记是单向的只存在于RR的配置里客户端自己并不知道自己“被反射”了。换言之客户端只管建立iBGP会话至于是不是客户端RR说了算。非客户端就是RR的普通iBGP邻居。它和RR之间没有客户端关系同时也需要和非客户端之间保持全互联因为RR不会把从非客户端学到的路由反射给其他非客户端。还有一个角色是Originator也就是路由的原始生成者。当RR第一次反射一条路由时会记录这条路由最初是谁注入到iBGP里的这个叫Originator ID。它是一条防环信息客户端收到后如果发现Originator ID是自己的Router ID说明路由绕了一圈又回来了直接丢弃。2.2 反射规则背下来还是理解下来反射规则表面上只有三条但很容易记混。我后来发现光背规则不行得把它和“客户端是RR的嫡系非客户端是旁系”这个画面结合起来。从客户端学到的iBGP路由反射给所有其他客户端也反射给所有非客户端。也就是说客户端路由是全扩散。从非客户端学到的iBGP路由只反射给客户端不反射给其他非客户端。因为非客户端之间本来应该全互联RR没必要帮他们转发。从EBGP邻居学到的路由反射给所有iBGP邻居不管对方是客户端还是非客户端。这三条规则的本质是在“打破水平分割”和“避免环路”之间找一个平衡点。RR允许客户端之间“借用”它来交换路由结果是客户端可以不用全互联但非客户端之间仍然要靠全互联来保证互通这就限制了RR的反射范围避免反射关系无限递归。你可以用一句话记客户端的路由RR全反射非客户端的路由RR只给客户端EBGP进来的路由RR谁都给。实际配置和排障时先把这句话在脑子里过一遍很多问题就有方向了。2.3 ORIGINATOR_ID 和 CLUSTER_LIST 是怎么拦住环路的ORIGINATOR_ID在第一跳RR上被写入后面每一级RR都不会改它它始终指向最初产生这条路由的路由器Router ID。这种做法相当于给路由挂了一个“溯源标签”。CLUSTER_LIST是另一个关键属性。每个RR可以配置一个Cluster ID默认情况下如果RR没有配置会用自己的Router ID作为Cluster ID。每经过一个RR这个Cluster ID就会被追加到CLUSTER_LIST里。当一个RR收到一条路由如果发现CLUSTER_LIST里已经包含自己的Cluster ID说明这条路由可能已经被自己反射过一圈直接丢弃。把这两个属性配合起来RR网络里就能形成一个完备的防环机制。尤其是有多台RR、多层RR的时候CLUSTER_LIST会变长路由在BGP表里的路径信息也会变复杂。我曾经排查过一个“路由学回来后又被丢掉”的问题最后就是CLUSTER_LIST里重复出现了同一段Cluster ID说明有一条RR转发路径自己把自己绕进去了。3. 单集群 RR 配置实战3.1 拓扑与规划先想清楚再敲命令配置RR之前先把拓扑和IP规划写出来。别以为这是小事我见过因为IP规划没做反射器之间互相把Loopback地址宣告错了导致邻居起不来。假设有一个AS 65001内部跑OSPF作为IGP所有设备的Loopback地址都宣告进IGP。拓扑如下AS 65001 R1 (边界, 收到外部路由) /\ R2 R3 - 两个RR, cluster-id 10.0.0.10 / \ / \ C1 C2 C3 C4R1作为边界路由器从上级AS学习到外部路由。R2和R3是一对冗余RR使用相同的Cluster ID 10.0.0.10。C1到C4是普通客户端每个客户端同时和R2、R3建立iBGP会话但客户端之间不需要互相建立会话。地址规划可以这样R1的Loopback是10.0.0.1R2是10.0.0.2R3是10.0.0.3C1到C4分别是10.0.0.4到10.0.0.7。iBGP会话全部使用Loopback作为源地址底层IPv4互通由OSPF保证。3.2 在反射器上配置 client这里我用一套接近主流厂商风格的配置来演示。不同厂商的命令有差异但思路是一致的。R2的配置router bgp 65001 bgp router-id 10.0.0.2 bgp cluster-id 10.0.0.10 neighbor 10.0.0.1 remote-as 65001 neighbor 10.0.0.1 update-source Loopback0 neighbor 10.0.0.1 route-reflector-client neighbor 10.0.0.4 remote-as 65001 neighbor 10.0.0.4 update-source Loopback0 neighbor 10.0.0.4 route-reflector-client neighbor 10.0.0.5 remote-as 65001 neighbor 10.0.0.5 update-source Loopback0 neighbor 10.0.0.5 route-reflector-clientR3的配置和R2类似Router ID换成10.0.0.3邻居同样是R1和C1到C4Cluster ID保持10.0.0.10。把Cluster ID配成相同是为了让R2和R3在同一个冗余集群内客户端无论从哪台RR收到路由Cluster信息都是一致的。这里有个关键点route-reflector-client只需要在RR本端配置客户端C1的配置里不需要也不能写这个关键词。客户端C1只要建立普通的iBGP邻居关系就行它并不知道自己已经被RR标记成了客户端。3.3 客户端和普通路由器的配置差异客户端C1的配置大概是这样的router bgp 65001 bgp router-id 10.0.0.4 neighbor 10.0.0.2 remote-as 65001 neighbor 10.0.0.2 update-source Loopback0 neighbor 10.0.0.3 remote-as 65001 neighbor 10.0.0.3 update-source Loopback0C2、C3、C4也类似唯一的差别就是Router ID和对应的Loopback。这样看起来客户端配置量非常轻因为它只需要和R2、R3两台RR建邻居不需要和AS内其他所有iBGP路由器建邻居。如果你想进一步省事可以开启RR的client-to-client反射功能。开启后RR会把从一个客户端学到的路由反射给其他客户端。默认在大多数实现里这个功能是开启的。但如果客户端之间本身已经做了全网状互联建议关掉这个功能否则RR会重复反射既浪费带宽也增加BGP表里的路径数量。3.4 验证的时候到底该看什么配置完别急着收工先做一轮验证。我通常分三步走。第一步确认邻居状态。在R2上查看所有iBGP邻居是不是Established。如果某个客户端邻居一直起不来先查IGP路由再看Loopback能不能互相ping通。第二步看BGP路由表的路径属性。用R1向AS内宣告一条外部路由比如192.0.2.0/24然后在R2上查看这条路由display bgp ipv4 unicast 192.0.2.0/24重点看两个字段Originator和Cluster List。正确的情况下能清楚看到这条路由的Originator是10.0.0.1Cluster List里包含10.0.0.10。这说明RR已经正确记录了原始路由来源和集群ID。如果Cluster List为空或者出现多个不同Cluster就要检查是不是有多套集群配置混在一起。第三步在客户端C4上再查一次这条路由。如果客户端能从R2或R3学到192.0.2.0/24并且路径里的Originator和Cluster List和RR上一致说明反射链路是通的。如果客户端上没有就要回到第二段讲的反射规则看看是不是路由来源属于非客户端而目标邻居又是非客户端。4. 高级部署冗余 RR、两级 RR 与 Add-Path4.1 冗余 RR 的正确姿势单台RR就是单点生产环境至少要配两台。最简单的冗余方案就是把两台RR放进同一个Cluster使用相同的Cluster ID然后让所有客户端同时连接这两台RR。这样做的好处是某台RR挂了客户端还有另一台RR可以继续接收反射路由。但这里有个前提客户端的会话必须同时双挂RR否则剩下那台RR的反射覆盖范围就不完整。还有一个容易被忽视的细节两台RR之间要不要互相建立iBGP会话如果所有客户端都双挂RR理论上可以不建RR间会话因为每个RR都能直接从客户端学到路由。但如果客户端没有全部双挂RR之间就需要有会话否则某些路由只有A这台RR知道B这台RR反射不出来。设计的时候先画清楚每台客户端连到哪台RR再决定是否保留RR间会话。我的建议是大部分规模不大的网络直接让客户端双挂并且把RR间会话也建上多几条邻居换来的故障切换体验是值得的。如果两个RR放在不同物理位置Cluster ID可以配成不同也可以配成相同。相同Cluster ID可以避免跨Cluster产生不必要的反射跳数不同Cluster ID则能更清楚地区分故障域。具体选择取决于你对故障隔离的要求。如果要求RR直接互为备份用同一个Cluster ID如果希望RR分别服务不同的客户端组用不同Cluster ID。4.2 两级 RR 与区域化网络规模大了之后单集群RR也会有压力。一方面所有客户端都连到同一对RR连接数虽然没有全互联那么爆炸但到了几百台设备时邻居数量、路由更新量还是不小。另一方面RR要完成全网路由的反射每台RR需要维护完整的BGP表内存和CPU压力都集中在RR上。这时候可以用两级RR。核心层放一级RR区域层放二级RR。二级RR作为一级RR的客户端同时又是本区域普通路由器的RR。路由先从区域内的客户端上报到二级RR二级RR反射给一级RR一级RR再把路由扩散给其他区域的二级RR再由二级RR反射给各自区域的客户端。配置二级RR时要和一级RR之间建立iBGP会话并且在一级RR上把二级RR配成route-reflector-client。然后在二级RR上把本区域内的设备配成自己的客户端。这样每一级都遵守RR的反射规则路由可以一级一级往下传递但不会跨级乱串。两级RR带来的最大收益不是省配置而是把故障域缩小了。某一台区域RR震荡时只影响该区域内的客户端不会把震荡扩散到整个AS。代价是路径属性里会多出几段Cluster List排查时要看得更仔细。4.3 Add-Path 和最优反射传统RR有一个短板它只把“最优路径”反射给客户端。如果网络中存在多条等价或次优路径客户端只能看到其中一跳。这在普通路由场景下问题不大但在数据中心EVPN或者需要快速收敛的场景里少一条备用路径就意味着故障切换时要等新的BGP更新而不是立即切到已有备用路径。解决思路是BGP Add-Path。RR可以和客户端协商Add-Path能力然后不仅反射最优路径还把符合条件的备用路径也一起反射过去。这样客户端手里始终有不止一条路由故障时可以更快切换。Add-Path不是全自动的。它需要RR和客户端两端都支持并开启能力协商同时要在路由策略里明确允许反射哪些附加路径。配置上通常是在BGP地址族下开启add-path send/receive能力然后通过策略控制让RR在反射时保留额外路径。在实际项目里我遇到过一种场景客户端明明配置了Add-Path接收能力RR上也开了发送能力但客户端BGP表里还是只有一条路径。最后查出来是策略里没有放行第二条路径的Community过滤反射器把所有附加路径都过滤掉了。Add-Path和路由策略是配套使用的只开能力不写策略等于没开。5. 常见故障排查速查5.1 路由反射后“消失”了这是最常见的故障RR的BGP表里有路由可客户端就是看不到或者看到了但不优。先检查反射规则是否匹配。如果路由来源是非客户端而目标也是非客户端RR本来就不会反射这不是故障是规则。如果目标确实是客户端那就继续看下一跳。RR反射默认不改下一跳客户端如果IGP里没有到下一跳的路由BGP会认为路由不可达直接不把它放进路由表。排查命令一般就是查看具体前缀在客户端上的BGP状态看是不是有“invalid”或者“not advertised”的标记。解决办法通常是在边界设备上对iBGP邻居配置next-hop-self或者在IGP里把外部下一跳地址补上。还要注意RR上配置了next-hop-self也可能带来副作用因为RR反射时如果不改下一跳可能造成路由从两个方向回来都带不同下一跳选路结果不可预测。5.2 路由学重了、学不全路由学重通常是多个RR的Cluster ID配置不一致导致的。Cluster ID不一致会把同一条路由当成不同来源反复反射BGP路径表里出现多个相同前缀。检查所有RR的cluster-id配置保证冗余组内统一。路由学不全重点看两台RR之间是否需要额外的反射或会话。如果所有客户端都双挂RR一般不会出现一台RR缺失路由的情况。如果客户端只挂了一台RR而那台RR某条路由没有学到就要检查RR到其他RR、RR到非客户端之间的会话是否完整。还有一个隐蔽问题是client-to-client反射被关闭了。有些版本默认关闭如果客户端之间依赖RR反射关了之后从客户端A学到的路由到不了客户端B。配置RR时不要照搬默认值先确认自己的拓扑到底靠不靠这个功能。5.3 割接时的几个小提醒从全互联收敛到RR我建议不要一次把所有会话都拆掉。先把RR配置好新会话正常建立并开始反射路由然后观察一段时间。确认反射出的路由和原来全互联下的路由一致再逐对拆除不需要的iBGP邻居。这个过程宁可慢不要快否则一旦漏了一条关键路由排查成本会很高。割接窗口选业务低峰期。BGP会话重建和Bestpath重算会带来短暂的路由抖动。RR上线后先看内存和CPU峰值再看有没有异常的路由振荡。如果某台RR的CPU一直很高问题可能在路由策略过滤不严导致大量无用路由反复更新不解决这个问题RR早晚会被拖垮。6. 个人实操中的几条心得最后写几条我在实际项目里积累的体会未必所有场景都适用但很值得参考。第一RR的邻居关系尽量都走Loopback地址并且把底层IGP的可达性先保证。这个习惯能减少很多莫名其妙的邻居抖动。第二RR的Cluster ID一定要全局统一规划尤其是多集群、多级的网络建议做一张集群ID和Cluster List的对照表排查环路时直接按表查。第三客户端双挂RR不代表万事大吉还要定期检查两台RR的BGP表是否一致。不一致的时候就说明某条会话或反射策略已经出了偏差趁早处理别等到切换时再发现。还有一个细节是别在RR上堆太多复杂策略。RR的核心职责是反射复杂的路由策略应该放在边界路由器上做。策略堆在RR上不仅增加故障面还会让反射规则变得很难判断。大部分时候RR只需要保证反射关系的正确性和冗余性把路由“干干净净地”反射出去就够了。我踩过最深的坑是给一台RR既配了多个Cluster ID又开了Client-to-Client反射结果内部集群路由反复震荡BGP表里出现了大量相同的路径条目。后来把集群ID收敛成一个并明确关闭了不必要的反射功能问题彻底消失。RR这个特性设计时有多克制运行时的稳定性就有多高。