从两个VPS位置实现BGP故障切换和多宿主

3 分钟阅读·Matthieu·bird2bfdfailoverfrrbgpmultihoming|

使用BGP从两个位置宣告相同前缀,实现自动故障切换。涵盖LOCAL_PREF、MED、AS-path prepending、BFD和优雅关闭,包含完整的BIRD2和FRR配置。

本教程介绍如何通过BGP从两个独立的VPS位置宣告相同的IP前缀。你将配置LOCAL_PREF和MED实现主备偏好,启用BFD实现亚秒级故障检测,并实现优雅关闭(graceful shutdown)用于计划维护。所有示例同时展示BIRD2和FRR的配置。

前提条件:

什么是BGP多宿主,为什么在VPS上使用?

BGP多宿主(multihoming)是指通过eBGP从两个或更多位置宣告相同的IP前缀。每个位置与其上游提供商保持独立的BGP会话。如果一个位置故障,另一个继续宣告前缀并自动接收所有流量。收敛时间取决于hold timer(默认配置通常为180-240秒)或BFD(正确配置下可达亚秒级)。

在VPS上,多宿主提供了不依赖单个数据中心的冗余能力。你在不同位置运行两个VPS实例,都宣告你的前缀。一个作为主节点,另一个作为备份。流量工程属性(LOCAL_PREF、MED、AS-path prepending)控制正常情况下哪条路径处理流量。

如何设计跨两个位置的BGP故障切换架构?

该架构使用两个位于不同欧洲位置的Virtua VPS,每个都与本地上游路由器保持eBGP会话。两者都宣告相同的/24和/48。

                    Internet
                   /        \
            Upstream A     Upstream B
            (Frankfurt)    (Amsterdam)
                |              |
           eBGP session   eBGP session
                |              |
          +-----------+  +-----------+
          |  VPS-PRI  |  |  VPS-BKP  |
          | AS 64500  |  | AS 64500  |
          | BIRD2/FRR |  | BIRD2/FRR |
          +-----------+  +-----------+
           announces       announces
          198.51.100.0/24  198.51.100.0/24
          2001:db8::/48    2001:db8::/48

两个节点都属于你的AS(示例中为AS 64500)。请将ASN、前缀和对等IP替换为你的实际值。

两个节点的防火墙规则:

BGP使用TCP端口179。BFD使用UDP端口3784和3785。在继续之前,请在你的VPS和上游对等端之间开放这些端口。

# nftables example - adjust PEER_IP to your upstream
nft add rule inet filter input ip saddr PEER_IP tcp dport 179 accept
nft add rule inet filter input ip saddr PEER_IP udp dport { 3784, 3785 } accept

如何控制BGP路径偏好?

三个属性可以影响流量走向,每个在不同范围内起作用。

属性 方向 范围 是否发送给对等端? 使用场景
LOCAL_PREF 出站(你的出口) AS内部 否(仅iBGP) 控制哪个节点发送出站流量
MED 入站(来自上游) 你和一个上游AS之间 是(发给直接邻居) 告诉单个上游优先选择哪个入口点
AS-path prepending 入站(全局) 路径中的所有AS 是(会传播) 让一条路径在整个互联网上看起来更长

LOCAL_PREF和MED是精确的工具。AS-path prepending比较粗糙,但当你的两个位置连接不同上游时很有效。

如何配置LOCAL_PREF实现主备路径?

LOCAL_PREF决定你的AS优先选择哪条出站路径。值越高越优先,默认值为100。在主节点设置200,在备份节点保持100。这只影响离开你网络的流量。

BIRD2 LOCAL_PREF配置

主节点VPS-PRI),创建或修改import过滤器:

# /etc/bird/bird.conf - Primary node

filter upstream_import_primary {
    bgp_local_pref = 200;
    accept;
}

protocol bgp upstream_v4 {
    local 192.0.2.2 as 64500;
    neighbor 192.0.2.1 as 64496;
    ipv4 {
        import filter upstream_import_primary;
        export where net = 198.51.100.0/24;
    };
}

protocol bgp upstream_v6 {
    local 2001:db8:1::2 as 64500;
    neighbor 2001:db8:1::1 as 64496;
    ipv6 {
        import filter upstream_import_primary;
        export where net = 2001:db8::/48;
    };
}

备份节点VPS-BKP),保持默认LOCAL_PREF:

# /etc/bird/bird.conf - Backup node

filter upstream_import_backup {
    bgp_local_pref = 100;
    accept;
}

protocol bgp upstream_v4 {
    local 203.0.113.2 as 64500;
    neighbor 203.0.113.1 as 64497;
    ipv4 {
        import filter upstream_import_backup;
        export where net = 198.51.100.0/24;
    };
}

重新加载BIRD2并检查路由:

birdc configure
birdc show route for 0.0.0.0/0 all
0.0.0.0/0          unicast [upstream_v4 12:00:00] * (100/?) [AS64496i]
        via 192.0.2.1 on eth0
        Type: BGP univ
        BGP.origin: IGP
        BGP.as_path: 64496
        BGP.local_pref: 200

主节点上的BGP.local_pref: 200表示它将被优先用于出站流量。

FRR LOCAL_PREF配置

主节点

vtysh -c "configure terminal
route-map UPSTREAM-IN permit 10
 set local-preference 200
exit
router bgp 64500
 neighbor 192.0.2.1 remote-as 64496
 address-family ipv4 unicast
  neighbor 192.0.2.1 route-map UPSTREAM-IN in
  network 198.51.100.0/24
 exit-address-family
 neighbor 2001:db8:1::1 remote-as 64496
 address-family ipv6 unicast
  neighbor 2001:db8:1::1 route-map UPSTREAM-IN in
  network 2001:db8::/48
 exit-address-family
exit
exit"

备份节点,设置set local-preference 100(或省略route-map,因为100是默认值)。

检查路由表:

vtysh -c "show ip bgp"
   Network          Next Hop            Metric LocPrf Weight Path
*> 0.0.0.0/0        192.0.2.1                     200      0 64496 i

如何使用MED控制入站流量?

MED(Multi-Exit Discriminator)告诉上游优先选择哪个入口点。值越低越优先。在主节点设置MED 0,在备份节点设置MED 100。MED只在来自同一邻居AS的路径之间比较,因此当两个位置与同一上游提供商对等时效果最好。

BIRD2 MED配置

主节点,在export过滤器中设置MED:

filter upstream_export_primary {
    if net = 198.51.100.0/24 || net = 2001:db8::/48 then {
        bgp_med = 0;
        accept;
    }
    reject;
}

protocol bgp upstream_v4 {
    local 192.0.2.2 as 64500;
    neighbor 192.0.2.1 as 64496;
    ipv4 {
        import filter upstream_import_primary;
        export filter upstream_export_primary;
    };
}

备份节点

filter upstream_export_backup {
    if net = 198.51.100.0/24 || net = 2001:db8::/48 then {
        bgp_med = 100;
        accept;
    }
    reject;
}

FRR MED配置

主节点

vtysh -c "configure terminal
route-map UPSTREAM-OUT permit 10
 set metric 0
exit
router bgp 64500
 address-family ipv4 unicast
  neighbor 192.0.2.1 route-map UPSTREAM-OUT out
 exit-address-family
exit
exit"

备份节点,使用set metric 100

检查导出的路由:

vtysh -c "show ip bgp neighbors 192.0.2.1 advertised-routes"
   Network          Next Hop            Metric LocPrf Weight Path
*> 198.51.100.0/24  0.0.0.0                  0         32768 i

Metric列在主节点上显示0,备份节点将显示100

什么时候应该用AS-path prepending替代MED?

当你的两个位置连接到不同的上游提供商时,使用AS-path prepending。MED只在来自同一AS的路径之间比较,如果上游是不同AS则没有效果。Prepending使备份路径看起来更长,将全局路由决策推向主节点。

在备份节点上将你自己的ASN添加1-3次。超过3次prepend很少改变路由决策,只是增加噪音。

BIRD2(备份节点export过滤器):

filter upstream_export_backup_prepend {
    if net = 198.51.100.0/24 || net = 2001:db8::/48 then {
        bgp_path.prepend(64500);
        bgp_path.prepend(64500);
        accept;
    }
    reject;
}

FRR(备份节点):

vtysh -c "configure terminal
route-map UPSTREAM-OUT permit 10
 set as-path prepend 64500 64500
exit
exit"

应用后,从looking glass或远程主机检查AS path:

# From an external machine
traceroute -A 198.51.100.1

备份路径现在显示64500 64500 64500(你的ASN出现三次:一次真实,两次prepend),而主路径只显示一次64500

如何启用BFD实现快速故障检测?

没有BFD时,BGP依赖hold timer检测对等端故障。BIRD2默认hold time为240秒,FRR为180秒。有了BFD,在低延迟链路上检测时间降至亚秒级。

参数 默认值 VPS推荐值
发送间隔 300 ms 300 ms
接收间隔 300 ms 300 ms
检测倍数 3 3
有效检测时间 900 ms 900 ms

对于同一提供商骨干网上的VPS环境,300 ms间隔配合倍数3可以提供可靠的亚秒级检测而不会误报。不要在VPS实例上将间隔设置低于100 ms,虚拟化抖动可能导致路由震荡。

BIRD2 BFD配置

添加BFD协议并在BGP会话上启用:

protocol bfd {
    interface "*" {
        min rx interval 300 ms;
        min tx interval 300 ms;
        multiplier 3;
    };
}

protocol bgp upstream_v4 {
    local 192.0.2.2 as 64500;
    neighbor 192.0.2.1 as 64496;
    bfd graceful;
    ipv4 {
        import filter upstream_import_primary;
        export filter upstream_export_primary;
    };
}

bfd graceful选项意味着当BFD检测到故障时,BIRD2将触发优雅重启(保留过期路由)而不是硬重置会话。如果对等端不运行BFD,会话仍然正常建立。

重新加载后,检查BFD状态:

birdc show bfd sessions
BFD sessions:
IP address       Interface  State   Since       Interval  Timeout
192.0.2.1        eth0       Up      12:00:00    300 ms    900 ms

FRR BFD配置

vtysh -c "configure terminal
bfd
 profile vps-detect
  receive-interval 300
  transmit-interval 300
  detect-multiplier 3
 exit
exit
router bgp 64500
 neighbor 192.0.2.1 bfd profile vps-detect
 neighbor 2001:db8:1::1 bfd profile vps-detect
exit
exit"

检查BFD对等端状态:

vtysh -c "show bfd peers"
BFD Peers:
        peer 192.0.2.1 vrf default
                ID: 1
                Remote ID: 2
                Status: up
                Uptime: 5 minute(s)
                Diagnostics: ok
                Remote diagnostics: ok
                Peer Type: configured
                Local timers:
                        Receive interval: 300ms
                        Transmission interval: 300ms
                        Echo receive interval: disabled
                        Echo transmission interval: disabled
                Peer timers:
                        Receive interval: 300ms
                        Transmission interval: 300ms
                        Echo receive interval: disabled

BFD需要对等端之间开放UDP端口3784和3785。如果之前跳过了防火墙步骤,BFD会话将保持Down状态。

如何执行优雅关闭进行维护?

RFC 8326定义了GRACEFUL_SHUTDOWN知名community(65535:0)。在计划维护之前,用这个community标记所有路由。尊重该community的对等端会将这些路由的local-preference设置为0,使流量在你关闭会话之前转移到备用路径。这避免了正常BGP收敛期间出现的流量黑洞。

优雅关闭流程:

  1. 标记路由:在要关闭的节点上用GRACEFUL_SHUTDOWN community标记所有路由
  2. 等待收敛(30-60秒让互联网重新路由)
  3. 确认流量已转移:通过looking glass或流量计数器检查
  4. 关闭BGP会话
  5. 执行维护
  6. 恢复会话并移除community标记
  7. 确认重新收敛

BIRD2优雅关闭

在维护前,要在主节点上发起优雅关闭,修改export过滤器:

# Temporary export filter for graceful shutdown
filter upstream_export_shutdown {
    if net = 198.51.100.0/24 || net = 2001:db8::/48 then {
        bgp_community.add((65535, 0));
        bgp_med = 65535;
        accept;
    }
    reject;
}

通过修改BGP协议中的export过滤器并重新加载来应用:

# Edit bird.conf: change export filter to upstream_export_shutdown
# Then reload
birdc configure

尊重来自对等端的优雅关闭(在两个节点上应用),在import过滤器中添加检查。顺序很重要:优雅关闭检查必须在if块内调用accept,否则后续的bgp_local_pref赋值会覆盖它。

filter upstream_import_backup {
    if (65535, 0) ~ bgp_community then {
        bgp_local_pref = 0;
        accept;
    }
    bgp_local_pref = 100;
    accept;
}

FRR优雅关闭

FRR提供一个命令自动处理标记:

vtysh -c "configure terminal
router bgp 64500
 bgp graceful-shutdown
exit
exit"

这会将GRACEFUL_SHUTDOWN community(65535:0)添加到所有路由并将local-preference设置为0。会向所有对等端触发路由刷新。

确认community正在发送:

vtysh -c "show ip bgp neighbors 192.0.2.1 advertised-routes"
   Network          Next Hop            Metric LocPrf Weight Path
*> 198.51.100.0/24  0.0.0.0                  0      0  32768 i
                                         Community: graceful-shutdown

维护后移除:

vtysh -c "configure terminal
router bgp 64500
 no bgp graceful-shutdown
exit
exit"

要让FRR尊重来自对等端的优雅关闭,配置入站route-map:

vtysh -c "configure terminal
bgp community-list standard GRACEFUL_SHUTDOWN permit graceful-shutdown
route-map UPSTREAM-IN permit 5
 match community GRACEFUL_SHUTDOWN
 set local-preference 0
exit
route-map UPSTREAM-IN permit 10
 set local-preference 200
exit
exit"

序列5匹配携带该community的路由并将local-preference降至0。序列10正常处理所有其他路由。

如何测试BGP故障切换?

通过关闭主BGP会话,从备份节点和外部视角观察来测试故障切换。

步骤1:检查两个节点上当前的路由状态。

BIRD2:

birdc show route for 198.51.100.0/24 all

FRR:

vtysh -c "show ip bgp 198.51.100.0/24"

步骤2:关闭主BGP会话。

BIRD2(在VPS-PRI上):

birdc disable upstream_v4
birdc disable upstream_v6

FRR(在VPS-PRI上):

vtysh -c "configure terminal
router bgp 64500
 neighbor 192.0.2.1 shutdown
 neighbor 2001:db8:1::1 shutdown
exit
exit"

步骤3:观察备份节点。

VPS-BKP上,路由现在应该显示为唯一路径:

# BIRD2
birdc show route for 198.51.100.0/24

# FRR
vtysh -c "show ip bgp summary"

步骤4:从外部测试。

从你的本地机器或looking glass,对你的前缀执行traceroute:

traceroute -A 198.51.100.1

流量现在应该通过备份位置进入。启用BFD后,切换在1秒内完成。没有BFD,需要等待完整的hold timer时间才能收敛。

检测方法 典型故障切换时间
仅BGP hold timer(BIRD2默认240 s) 160-240 s
仅BGP hold timer(FRR默认180 s) 120-180 s
缩短的hold timer(如30 s) 20-30 s
BFD(300 ms间隔,倍数3) < 1 s

使用NLNOG Looking Glass或bgp.tools确认全局路由收敛。

故障切换后如何恢复?

恢复主会话并确认流量回到首选路径。

BIRD2:

birdc enable upstream_v4
birdc enable upstream_v6

FRR:

vtysh -c "configure terminal
router bgp 64500
 no neighbor 192.0.2.1 shutdown
 no neighbor 2001:db8:1::1 shutdown
exit
exit"

几秒后,确认主路径再次被优先选择:

# BIRD2
birdc show route for 0.0.0.0/0 all | grep local_pref
        BGP.local_pref: 200
# FRR
vtysh -c "show ip bgp"
   Network          Next Hop            Metric LocPrf Weight Path
*> 0.0.0.0/0        192.0.2.1                     200      0 64496 i

再次从外部主机执行traceroute,确认流量重新通过主位置进入。

BIRD2与FRR配置对比

功能 BIRD2 FRR
LOCAL_PREF import过滤器中bgp_local_pref = 200; route-map中set local-preference 200
MED export过滤器中bgp_med = 0; route-map中set metric 0
AS-path prepend export过滤器中bgp_path.prepend(64500); route-map中set as-path prepend 64500
BFD protocol bfd {} + BGP中bfd graceful; bfd段 + neighbor X bfd profile Y
优雅关闭(发起) export过滤器中添加(65535, 0)bgp_community router bgpbgp graceful-shutdown
优雅关闭(尊重) import过滤器中检查(65535, 0) ~ bgp_community,设置bgp_local_pref = 0 route-map中match community GRACEFUL_SHUTDOWNset local-preference 0
禁用会话 birdc disable <protocol> neighbor X shutdown
重载配置 birdc configure write memory然后clear ip bgp *或重启

监控故障切换事件

设置监控以在故障切换发生时收到告警。在Linux上使用BGPalerter监控BGP公告介绍了BGPalerter的路由监控。至少应监控:

  • BGP会话状态变化:journalctl -u birdjournalctl -u frr
  • BFD会话震荡:birdc show bfd sessions / vtysh -c "show bfd peers"
  • 路由数量变化:如果导出前缀数量降为零则告警

故障排除

BGP会话卡在Active/Connect状态:

  • 检查TCP 179的防火墙规则
  • 确认对等端IP和ASN与上游期望的一致
  • 查看journalctl -u bird -fjournalctl -u frr -f的错误信息

BFD会话卡在Down状态:

  • UDP端口3784和3785必须双向开放
  • 确认对等端支持BFD并已配置
  • 检查路径上的MTU问题

MED不影响入站流量:

  • MED只在来自同一AS的路径之间比较。如果上游是不同AS,请改用AS-path prepending
  • 一些上游按策略忽略MED。请咨询你的提供商

优雅关闭community未被尊重:

  • 对等端必须明确支持RFC 8326。并非所有上游都支持
  • 向你的提供商确认是否尊重GRACEFUL_SHUTDOWN community
  • 一些实现需要显式配置才能尊重该community

流量未切换:

  • 确认两个节点都在宣告相同前缀:birdc show route export upstream_v4vtysh -c "show ip bgp neighbors X advertised-routes"
  • 从外部looking glass检查,不要从节点本身检查
  • 如果你在anycast前缀上使用按位置分配的IP提供服务,DNS TTL可能使客户端继续指向旧IP