土法炼钢兴趣小组的算法知识备份

BGP 基础:互联网的路由协议,也是 K8s 网络的关键拼图

目录

上一篇我们搞清楚了 Linux 路由表和隧道 – 包在本机怎么查路由、跨节点怎么用 VXLAN/IPIP 封装。最后留了一个悬念:在不使用隧道的情况下,怎么让整个数据中心知道你的 Pod 在哪?

答案是 BGP

如果你用 Calico 的 BGP 模式(而不是 VXLAN),每个节点上的 BIRD 守护进程会通过 BGP 协议把本节点的 Pod CIDR 广播给其他节点。收到通告的节点往路由表里插一条 10.244.1.0/24 via 10.0.0.2 proto bird,于是下次有包要去 10.244.1.5,内核直接查路由表就知道下一跳是 10.0.0.2 – 不需要任何隧道封装,纯三层转发,性能接近裸机。

听起来很简单。但 BGP 本身并不简单。它是驱动整个互联网的路由协议,RFC 4271 有 104 页,覆盖了状态机、路径属性、路由选择、错误处理等一大堆细节。

这篇文章不是要把 RFC 4271 翻译成中文。我们只关注在 Kubernetes 网络场景下你必须理解的部分:

  1. BGP 基本概念:AS、IBGP、EBGP、path attributes
  2. BGP 状态机:理解 peer 建立连接的过程
  3. 路由选择算法:BGP 怎么决定用哪条路
  4. BIRD:Calico 用来跑 BGP 的守护进程
  5. Route Reflector:大规模集群为什么需要它
  6. BGP EVPN:数据中心的 Overlay + Underlay 统一
  7. 实验:用 containerlab + FRRouting 搭一个 3 节点 BGP 网络

本文的实验环境:Linux 6.x, x86_64。BGP 实验使用 containerlab + FRRouting 容器。


一、为什么 K8s 网络要关心 BGP

先搞清楚一个问题:BGP 是互联网骨干网的路由协议,ISP 之间交换路由信息用的。一个跑在机架里的 Kubernetes 集群,为什么需要这么”重量级”的协议?

Calico BGP 模式的工作原理

假设你有 3 个节点:

节点 节点 IP Pod CIDR
node-1 10.0.0.1 10.244.0.0/24
node-2 10.0.0.2 10.244.1.0/24
node-3 10.0.0.3 10.244.2.0/24

当 Calico 在 node-1 上创建一个 Pod(比如 10.244.0.5),它会做两件事:

  1. 在 node-1 上创建 veth pair,把一端放到 Pod 的 netns,另一端留在宿主机,然后添加一条本地路由:10.244.0.5 dev cali1234 scope link
  2. 通过 BGP 告诉 node-2 和 node-3:“去 10.244.0.0/24 的流量请发给我(10.0.0.1)”

node-2 收到这条 BGP UPDATE 消息后,往自己的路由表里插一条:

$ ip route show proto bird
10.244.0.0/24 via 10.0.0.1 dev eth0
10.244.2.0/24 via 10.0.0.3 dev eth0

proto bird 说明这条路由是 BIRD 守护进程通过 BGP 学来的。之后 node-2 上的 Pod 想访问 node-1 上的 Pod,内核直接查路由表就知道下一跳 – 零封装、零隧道开销。

对比 VXLAN 模式

VXLAN 模式下,Calico 不需要 BGP。它把 Pod 的包用 VXLAN 封装,通过 FDB 表查 VTEP 对端地址。好处是不需要底层网络支持 BGP,坏处是每个包多 50 字节封装开销,而且 MTU 要减小。

BGP 模式的前提是:所有节点在同一个 L2 域,或者底层网络(ToR 交换机)能理解 BGP 路由。 如果你的节点分布在不同的 L3 子网,且中间的路由器不跑 BGP,那纯 BGP 模式走不通,要么用隧道(IPIP/VXLAN),要么让 Calico 跟 ToR 交换机对等建立 BGP 邻居。

什么场景该用 BGP 模式


二、BGP 协议核心概念

Autonomous System(AS)

BGP 的世界观很简单:整个互联网由一堆自治系统(Autonomous System, AS)组成,每个 AS 有一个唯一编号(ASN)。AS 内部用 IGP(OSPF、IS-IS 等)交换路由,AS 之间用 BGP。

AS 号是 16 位或 32 位整数。公网 AS 号由 IANA 分配(1-64495),私有 AS 号范围是 64512-65534(16 位)和 4200000000-4294967294(32 位)。

在 Calico 里,默认所有节点属于同一个 AS(通常是 64512),跑的是 IBGP。如果你需要跟外部网络对等,就要配 EBGP。

IBGP vs EBGP

BGP 会话分两种:

EBGP(External BGP):两个不同 AS 的路由器之间建立的 BGP 会话。特点: - 默认 TTL=1,只能跟直连邻居建立(可以用 multihop 改) - 收到的路由转发给其他所有 BGP 邻居(IBGP 和 EBGP 都转) - NEXT_HOP 会被改成自己的地址

IBGP(Internal BGP):同一个 AS 内部路由器之间的 BGP 会话。特点: - 默认 TTL=255,可以跨多跳建立 - 从 IBGP 邻居学到的路由不会再转发给其他 IBGP 邻居(防环规则) - NEXT_HOP 默认不修改

第二点非常关键。IBGP 的防环规则意味着:如果你有 N 个 IBGP 节点,要让每个节点都能学到所有路由,你必须建立 full-mesh – 每两个节点之间都有一条 BGP session。这就是 O(n^2) 问题的根源,后面讲 Route Reflector 的时候会详细说。

BGP 消息类型

BGP 只有四种消息(RFC 4271 Section 4):

消息类型 用途
OPEN 建立连接时的握手消息,携带 ASN、Hold Time、Router ID
UPDATE 通告新路由或撤回旧路由
KEEPALIVE 保活,默认每 60 秒发一次(Hold Time 的 1/3)
NOTIFICATION 错误通知,发完就断连接

UPDATE 消息是 BGP 的核心载荷,结构大概是这样:

UPDATE Message:
  Withdrawn Routes Length: 0
  Total Path Attribute Length: 30
  Path Attributes:
    ORIGIN: IGP
    AS_PATH: 64512
    NEXT_HOP: 10.0.0.1
    MED: 0
  NLRI (Network Layer Reachability Information):
    10.244.0.0/24

一条 UPDATE 可以同时通告多个前缀(NLRI),也可以撤回多个前缀(Withdrawn Routes)。

Path Attributes(路径属性)

路径属性是 BGP 路由选择的核心数据。每条路由都携带一组属性:

Well-known Mandatory(必须存在、必须识别):

Well-known Discretionary(必须识别、可选存在):

Optional Transitive(可选、可传递):

Optional Non-Transitive(可选、不传递):


三、BGP 路由选择算法

BGP 可能从多个邻居学到到达同一目的前缀的多条路由。它用一个确定性的算法来选择最优路由,按优先级从高到低评估:

1. 最高 LOCAL_PREF                    (越大越好, 仅 IBGP)
2. 最短 AS_PATH                       (越短越好)
3. 最低 ORIGIN 类型                    (IGP < EGP < INCOMPLETE)
4. 最低 MED                           (越小越好, 仅比较来自同一邻居 AS 的路由)
5. EBGP > IBGP                        (优先外部学来的路由)
6. 最近的 IGP 下一跳                   (到 NEXT_HOP 的 IGP metric 最小)
7. 最老的路由                          (优先稳定的路由, 仅 EBGP)
8. 最低 Router ID 的邻居               (平局打破器)
9. 最短 CLUSTER_LIST                   (Route Reflector 环境)
10. 最低邻居 IP 地址                   (终极平局打破器)

在 Calico 的典型场景里,所有节点在同一个 AS,跑 IBGP,AS_PATH 长度都一样(就一个 AS 号)。所以实际起作用的主要是 LOCAL_PREF 和 IGP metric。


四、BGP 状态机

BGP 邻居建立连接的过程是一个有限状态机,定义在 RFC 4271 Section 8。理解这个状态机对排查 BGP 邻居建不起来的问题至关重要。

BGP 有限状态机

六个状态:

Idle

初始状态。BGP 进程刚启动或者连接被重置后回到这里。在这个状态下,BGP 拒绝所有入站连接(除非收到 ManualStart 或 AutomaticStart 事件)。

你在 calicoctl node status 里看到 peer 状态是 Idle,通常意味着: - BGP peer 配置错误(IP 写错了) - 对端不可达(防火墙挡了 TCP 179) - 刚刚经历了一次连接失败,正在 exponential backoff

Connect

收到 Start 事件后,BGP 发起 TCP 连接到对端的 179 端口。在等待 TCP 三次握手完成的过程中,状态处于 Connect。

ConnectRetry 计时器开始计时。如果超时还没建立 TCP 连接,就转到 Active 状态继续尝试。

Active

BGP 正在积极尝试建立 TCP 连接。这个状态意味着前一次连接尝试失败了,BGP 还在重试。

在 Calico 的 calicoctl node status 输出里看到 Active 是一个警告信号 – 它说明 TCP 连接建不起来。常见排查手段:

# 检查 TCP 179 端口是否通
$ nc -zv 10.0.0.2 179
Connection to 10.0.0.2 179 port [tcp/bgp] succeeded!

# 如果不通,检查防火墙
$ iptables -L INPUT -n | grep 179
ACCEPT  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:179

# 检查 BIRD 是否在监听
$ ss -tlnp | grep 179
LISTEN  0  8  0.0.0.0:179  0.0.0.0:*  users:(("bird",pid=1234,fd=7))

OpenSent

TCP 连接建立成功后,BGP 发送 OPEN 消息,进入 OpenSent 状态,等对端的 OPEN 消息。

OPEN 消息包含: - Version:BGP 版本号(4) - My AS Number:自己的 ASN - Hold Time:建议的保活超时(默认 90 秒) - BGP Identifier:Router ID(通常是最大的 loopback IP) - Optional Parameters:能力协商(multiprotocol、route refresh 等)

如果收到对端的 OPEN 消息且参数可接受,发送 KEEPALIVE,转到 OpenConfirm。如果参数不可接受(比如 ASN 不匹配),发送 NOTIFICATION 然后回到 Idle。

OpenConfirm

双方都发了 OPEN,本端已经发了 KEEPALIVE,在等对端的 KEEPALIVE。

如果收到对端的 KEEPALIVE,连接建立成功,转到 Established。如果收到 NOTIFICATION,回到 Idle。

Established

连接建立完成,可以交换 UPDATE 消息了。这是正常工作状态。

在这个状态下: - 定期互发 KEEPALIVE(默认每 30 秒) - 收发 UPDATE 消息来通告或撤回路由 - 如果 Hold Time 内没收到任何消息,认为对端挂了,发 NOTIFICATION 回到 Idle

# 查看 Calico 节点的 BGP 邻居状态
$ sudo calicoctl node status
Calico process is running.

IPv4 BGP status
+---------------+-------------------+-------+----------+-------------+
| PEER ADDRESS  |     PEER TYPE     | STATE |  SINCE   |    INFO     |
+---------------+-------------------+-------+----------+-------------+
| 10.0.0.2      | node-to-node mesh | up    | 03:14:22 | Established |
| 10.0.0.3      | node-to-node mesh | up    | 03:14:22 | Established |
+---------------+-------------------+-------+----------+-------------+

状态是 Established 就对了。如果长时间停在 ActiveConnect,查 TCP 连通性和防火墙。如果停在 OpenSentOpenConfirm,查 OPEN 参数是否匹配。


五、BIRD:Calico 的 BGP 引擎

BIRD(BIRD Internet Routing Daemon)是一个轻量级的路由守护进程,支持 BGP、OSPF、RIP、静态路由等协议。Calico 选择 BIRD 作为 BGP 引擎有几个原因:

BIRD 在 Calico 里的角色

每个 Kubernetes 节点上,Calico 运行一个 calico-node Pod(DaemonSet),里面包含两个主要进程:

Felix 把本节点的 Pod 路由写入内核路由表,BIRD 读取这些路由并通过 BGP 通告给其他节点。反过来,BIRD 从其他节点学到的路由也会写入内核路由表,Felix 确保对应的转发规则存在。

BIRD 配置解析

Calico 自动生成 BIRD 配置,但理解它的结构有助于排查问题。以下是一个简化的配置:

# /etc/calico/confd/config/bird.cfg (auto-generated by confd)

router id 10.0.0.1;

log syslog all;

# 内核路由表协议 -- BIRD 把学到的路由同步到内核
protocol kernel {
    learn;                  # 学习内核中已有的路由
    persist;                # BIRD 退出时保留路由
    scan time 2;            # 每 2 秒扫描内核路由表
    import all;
    export filter calico_kernel_programming;
    graceful restart;
    merge paths on;         # ECMP:合并到同一前缀的多条路径
}

# 设备协议 -- 监控网络接口状态
protocol device {
    scan time 2;
}

# 直连路由协议 -- 从接口地址自动生成直连路由
protocol direct {
    interface "cali*", "vxlan.calico", "wg-v4";
}

# BGP 模板 -- 所有 BGP 邻居共享的配置
template bgp bgp_template {
    local as 64512;
    graceful restart;
    connect delay time 2;
    connect retry time 5;
    error wait time 5, 30;    # 错误后重试间隔:5-30 秒
}

# 具体的 BGP 邻居
protocol bgp Node_10_0_0_2 from bgp_template {
    neighbor 10.0.0.2 as 64512;
    import filter {
        if (net ~ [10.244.0.0/16{24,32}]) then accept;
        reject;
    };
    export filter {
        if (net ~ [10.244.0.0/16{24,32}]) then accept;
        reject;
    };
}

protocol bgp Node_10_0_0_3 from bgp_template {
    neighbor 10.0.0.3 as 64512;
    import all;
    export all;
}

几个关键点:

  1. merge paths on – 让 BIRD 支持 ECMP。如果到同一个前缀有多条等价路径,BIRD 会把所有路径都写入内核路由表,内核就可以做多路径负载均衡。

  2. graceful restart – BGP Graceful Restart(RFC 4724)。当 BIRD 重启时,对端不会立即撤回路由,而是等一段时间让 BIRD 恢复。这对 Calico 升级时保持网络连通性很重要。

  3. import filter / export filter – 精确控制接受和通告哪些路由。Calico 默认通告所有 Pod CIDR 路由。

用 birdc 排查问题

birdc 是 BIRD 的命令行客户端,在 calico-node 容器里可以直接用:

# 进入 calico-node 容器
$ kubectl exec -it calico-node-xxxxx -n calico-system -- /bin/sh

# 查看 BGP 邻居状态
$ birdc show protocols
BIRD 2.0.8 ready.
Name       Proto      Table      State  Since         Info
kernel1    Kernel     master4    up     2024-01-15    
device1    Device     ---        up     2024-01-15    
direct1    Direct     ---        up     2024-01-15    
Node_10_0_0_2 BGP   ---        up     2024-01-15    Established
Node_10_0_0_3 BGP   ---        up     2024-01-15    Established

# 查看某个邻居的详细信息
$ birdc show protocols all Node_10_0_0_2
  BGP state:          Established
    Neighbor address:   10.0.0.2
    Neighbor AS:        64512
    Local AS:           64512
    Session:            internal multihop AS4
    Hold timer:         83.456/90
    Keepalive timer:    19.223/30
    Channel ipv4
      State:          UP
      Table:          master4
      Import accepted: 28
      Import rejected: 0
      Export accepted: 25
      Export rejected: 0

# 查看从某个邻居学到的路由
$ birdc show route protocol Node_10_0_0_2
10.244.1.0/24   via 10.0.0.2 on eth0 [Node_10_0_0_2 2024-01-15] * (100i)
10.244.1.5/32   via 10.0.0.2 on eth0 [Node_10_0_0_2 2024-01-15] * (100i)

# 查看路由表
$ birdc show route
10.244.0.0/24   dev cali123 [direct1 2024-01-15] * (240)
10.244.1.0/24   via 10.0.0.2 on eth0 [Node_10_0_0_2 2024-01-15] * (100i)
10.244.2.0/24   via 10.0.0.3 on eth0 [Node_10_0_0_3 2024-01-15] * (100i)

# 查看导出给某个邻居的路由
$ birdc show route export Node_10_0_0_2
10.244.0.0/24   dev cali123 [direct1 2024-01-15] * (240)

Import accepted: 28 表示从 node-2 学到了 28 条路由。如果这个数字是 0 但对端显示 Export accepted 不是 0,说明 import filter 把路由过滤掉了。


六、Route Reflector:解决 O(n^2) 问题

Full-mesh IBGP 的问题

前面提到 IBGP 的防环规则:从 IBGP 邻居学到的路由不会转发给其他 IBGP 邻居。 这意味着在一个纯 IBGP 环境里,每个节点必须跟其他所有节点都建立 BGP session,才能学到所有路由。

5 个节点需要 10 条 session(5 * 4 / 2 = 10)。 50 个节点需要 1,225 条 session。 500 个节点需要 124,750 条 session。

Full-Mesh IBGP vs Route Reflector

Calico 默认开启 nodeToNodeMeshEnabled: true,就是 full-mesh IBGP。每加一个节点,BGP session 数量就多 N-1 条。在小集群里(几十个节点)问题不大,但超过 100 个节点就开始吃力了 – 每条 BGP session 都要维护 TCP 连接、收发 KEEPALIVE、处理 UPDATE,CPU、内存、带宽全都在涨。

Route Reflector 的原理

Route Reflector(路由反射器,RFC 4456)打破了 IBGP 的防环规则 – 但是以一种可控的方式。

RR 把 IBGP 邻居分成两类:

RR 的转发规则:

  1. 从 Client 学到的路由 -> 转发给所有 Client 和 Non-client
  2. 从 Non-client 学到的路由 -> 只转发给 Client
  3. 从 EBGP 学到的路由 -> 转发给所有 Client 和 Non-client

N 个节点 + 1 个 RR = N 条 session,从 O(n^2) 降到 O(n)。

防环机制

RR 打破了”不转发给 IBGP 邻居”的规则,那怎么防止路由环路?靠两个属性:

在 Calico 中配置 Route Reflector

第一步,关闭 full-mesh:

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  nodeToNodeMeshEnabled: false
  asNumber: 64512

第二步,给 RR 节点打标签:

# 选 2-3 个节点作为 Route Reflector(生产环境建议至少 2 个做冗余)
$ kubectl label node node-1 route-reflector=true
$ kubectl label node node-2 route-reflector=true

第三步,配置 RR 节点的 Calico Node 资源:

apiVersion: projectcalico.org/v3
kind: Node
metadata:
  name: node-1
  labels:
    route-reflector: "true"
spec:
  bgp:
    ipv4Address: 10.0.0.1/24
    routeReflectorClusterID: 224.0.0.1

第四步,配置 BGPPeer – 让所有节点跟 RR 对等:

apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  name: peer-to-rr
spec:
  nodeSelector: all()
  peerSelector: route-reflector == "true"

再配一个让 RR 之间互联的 peer:

apiVersion: projectcalico.org/v3
kind: BGPPeer
metadata:
  name: rr-mesh
spec:
  nodeSelector: route-reflector == "true"
  peerSelector: route-reflector == "true"

验证

配置完成后,用 calicoctl node status 检查。普通节点应该只看到跟 2 个 RR 的 session(而不是跟所有节点的 full-mesh),RR 节点应该看到跟所有节点的 session,全部 Established

生产建议


七、BGP 与 EVPN:数据中心的统一控制平面

传统数据中心的问题

传统数据中心网络有两种路子:

  1. Underlay(纯路由):基于 BGP 的 IP Fabric,Leaf-Spine 架构。问题是不支持 L2 扩展 – VM/容器迁移到另一个 Leaf 下面,IP 要变。

  2. Overlay(封装):VXLAN 隧道在 L3 上模拟 L2。问题是控制平面 – VTEP 之间怎么知道对方的 MAC/IP 映射?传统做法是 multicast flooding,扩展性差。

EVPN 的解决方案

EVPN(Ethernet VPN,RFC 7432)用 BGP 作为 VXLAN 的控制平面 – 用 BGP UPDATE 消息来分发 MAC/IP 映射信息,替代掉 multicast flooding。

EVPN 定义了几种路由类型(Route Type):

Type 名称 用途
1 Ethernet Auto-discovery 多宿主冗余
2 MAC/IP Advertisement 分发 MAC-to-VTEP 映射
3 Inclusive Multicast BUM 流量的组播树
4 Ethernet Segment 多宿主 ES 选举
5 IP Prefix 纯 L3 前缀通告

最关键的是 Type 2 和 Type 5:

Type 2(MAC/IP Advertisement):当一台 VM 或 Pod 在 Leaf-1 下面启动,Leaf-1 通过 BGP EVPN Type 2 告诉所有 Leaf:“MAC aa:bb:cc:dd:ee:ff、IP 10.244.0.5 在我这里,我的 VTEP IP 是 10.0.0.1”。其他 Leaf 收到后更新 FDB 表和 ARP 表,不需要 flooding。

Type 5(IP Prefix):跟传统 BGP 的 IP 前缀通告类似,但包裹在 EVPN 的地址族里,可以跟 Type 2 的 MAC/IP 信息统一管理。

EVPN 与 Calico

Calico 本身不直接用 EVPN – 它用的是传统的 BGP IPv4 Unicast 地址族来通告 Pod CIDR。但如果你的数据中心底层网络用了 EVPN(很多现代 DC 都是),理解 EVPN 有助于你理解 Calico 的 BGP 路由是怎么被底层网络传播的。

在一个典型的 EVPN + Calico 架构里:

Pod -> veth -> Node -> BGP -> ToR(Leaf) -> EVPN/VXLAN -> Spine -> Leaf -> Node -> Pod
              \__ Calico BGP __/     \____ Fabric EVPN ____/

Calico 节点跟 ToR 交换机建立 EBGP session,把 Pod 路由通告给 ToR。ToR 可能把这些路由重分发进 EVPN Type 5,让其他 Leaf 下面的节点也能学到。

BGP 地址族

顺便提一下 BGP 的地址族(Address Family)。最初 BGP 只支持 IPv4 Unicast,后来通过 Multiprotocol Extensions(RFC 4760)扩展:

AFI / SAFI 组合:
  AFI 1 (IPv4)   + SAFI 1 (Unicast)    -> IPv4 Unicast      (Calico 默认)
  AFI 2 (IPv6)   + SAFI 1 (Unicast)    -> IPv6 Unicast      (Calico IPv6)
  AFI 1 (IPv4)   + SAFI 128 (MPLS-VPN) -> IPv4 VPN
  AFI 25 (L2VPN) + SAFI 70 (EVPN)      -> L2VPN EVPN        (数据中心 overlay)

Calico 用的是最基本的 IPv4/IPv6 Unicast。在 BIRD 配置里,channel ipv4channel ipv6 就是在指定地址族。


八、实验:用 containerlab + FRRouting 搭 3 节点 BGP 网络

纸上得来终觉浅。我们用 containerlab 搭一个真实的 BGP 网络来体验一下。

拓扑设计

三个路由器节点,都在 AS 64512,跑 IBGP full-mesh:

    router-1 (lo: 1.1.1.1)
    /               \
   /                 \
router-2           router-3
(lo: 2.2.2.2)     (lo: 3.3.3.3)

链路地址: - router-1 – router-2: 10.1.12.1/2410.1.12.2/24 - router-1 – router-3: 10.1.13.1/2410.1.13.3/24 - router-2 – router-3: 10.1.23.2/2410.1.23.3/24

安装 containerlab

# 安装 containerlab
$ sudo bash -c "$(curl -sL https://get.containerlab.dev)"

# 验证安装
$ containerlab version

创建拓扑文件

# bgp-lab.clab.yml
name: bgp-lab
topology:
  nodes:
    router-1:
      kind: linux
      image: frrouting/frr:v8.5.0
      binds:
        - router1/daemons:/etc/frr/daemons
        - router1/frr.conf:/etc/frr/frr.conf
    router-2:
      kind: linux
      image: frrouting/frr:v8.5.0
      binds:
        - router2/daemons:/etc/frr/daemons
        - router2/frr.conf:/etc/frr/frr.conf
    router-3:
      kind: linux
      image: frrouting/frr:v8.5.0
      binds:
        - router3/daemons:/etc/frr/daemons
        - router3/frr.conf:/etc/frr/frr.conf
  links:
    - endpoints: ["router-1:eth1", "router-2:eth1"]
    - endpoints: ["router-1:eth2", "router-3:eth1"]
    - endpoints: ["router-2:eth2", "router-3:eth2"]

配置 FRRouting

每个节点需要两个文件:daemons(启用哪些守护进程)和 frr.conf(路由配置)。

daemons 文件(三个节点共用)– 只需要启用 bgpd 和 staticd:

bgpd=yes
staticd=yes
# 其他守护进程全部设为 no(ospfd, ripd, isisd, ldpd 等)

router-1 的 frr.conf:

frr version 8.5
frr defaults datacenter
hostname router-1
!
interface lo
 ip address 1.1.1.1/32
!
interface eth1
 ip address 10.1.12.1/24
!
interface eth2
 ip address 10.1.13.1/24
!
router bgp 64512
 bgp router-id 1.1.1.1
 no bgp ebgp-requires-policy
 !
 neighbor 2.2.2.2 remote-as 64512
 neighbor 2.2.2.2 update-source lo
 neighbor 3.3.3.3 remote-as 64512
 neighbor 3.3.3.3 update-source lo
 !
 address-family ipv4 unicast
  network 1.1.1.1/32
  neighbor 2.2.2.2 next-hop-self
  neighbor 3.3.3.3 next-hop-self
 exit-address-family
!
ip route 2.2.2.2/32 10.1.12.2
ip route 3.3.3.3/32 10.1.13.3
!
line vty

router-2 和 router-3 的配置结构完全相同,只需替换 IP 地址和 hostname。核心区别:

router-2 router-3
loopback 2.2.2.2/32 3.3.3.3/32
eth1 10.1.12.2/24 10.1.13.3/24
eth2 10.1.23.2/24 10.1.23.3/24
BGP 邻居 1.1.1.1, 3.3.3.3 1.1.1.1, 2.2.2.2
静态路由 via 10.1.12.1, 10.1.23.3 via 10.1.13.1, 10.1.23.2

启动与验证

$ mkdir -p router1 router2 router3
# 把上面的配置写入对应文件后,启动 lab
$ sudo containerlab deploy --topo bgp-lab.clab.yml
# 查看拓扑
$ sudo containerlab inspect --topo bgp-lab.clab.yml

验证 BGP

# 进入 router-1 的 FRR CLI
$ docker exec -it clab-bgp-lab-router-1 vtysh

router-1# show bgp summary
IPv4 Unicast Summary:
BGP router identifier 1.1.1.1, local AS number 64512
Neighbor        V   AS  MsgRcvd  MsgSent  TblVer  InQ  OutQ  Up/Down  State/PfxRcd
2.2.2.2         4 64512      12       14       0    0    0    00:05:32  1
3.3.3.3         4 64512      11       13       0    0    0    00:05:28  1

router-1# show bgp ipv4 unicast
   Network          Next Hop       Metric  LocPrf  Weight  Path
*> 1.1.1.1/32       0.0.0.0             0          32768   i
*>i2.2.2.2/32       2.2.2.2             0     100       0  i
*>i3.3.3.3/32       3.3.3.3             0     100       0  i

router-1# ping 2.2.2.2 source 1.1.1.1
64 bytes from 2.2.2.2: icmp_seq=1 ttl=64 time=0.234 ms

观察路由通告与撤回

在 router-2 上通告一条新路由,看其他节点多快能学到:

# 在 router-2 上通告新前缀
router-2# conf t
router-2(config-router-af)# network 192.168.99.0/24
router-2(config-router-af)# end

# 在 router-1 上立即可见
router-1# show bgp ipv4 unicast 192.168.99.0/24
BGP routing table entry for 192.168.99.0/24
  2.2.2.2 from 2.2.2.2 (2.2.2.2)
    Origin IGP, metric 0, localpref 100, valid, internal, best

# 撤回路由后 router-1 上立即消失
router-2(config-router-af)# no network 192.168.99.0/24
router-1# show bgp ipv4 unicast 192.168.99.0/24
% Network not in table

BFD 加速故障检测

BGP 默认靠 Hold Timer(90 秒)检测邻居故障。生产环境中用 BFD 可以在亚秒级检测链路故障:

# FRRouting BFD 配置
router bgp 64512
 neighbor 2.2.2.2 bfd
 neighbor 3.3.3.3 bfd
!
bfd
 peer 2.2.2.2
  receive-interval 300
  transmit-interval 300
  detect-multiplier 3
 !

每 300ms 发一次 BFD 探测包,3 次没收到回复就判定邻居故障 – 900ms 检测时间,比 90 秒的 Hold Timer 快 100 倍。

清理

$ sudo containerlab destroy --topo bgp-lab.clab.yml

九、BGP 安全与调优

BGP 劫持

BGP 协议诞生于互联网早期,设计时假设所有参与者都是善意的。任何一个 AS 都可以通告任意前缀的路由。如果有人(恶意或误操作)通告了不属于自己的前缀,流量就会被吸引过去。

2018 年 4 月,一家小 ISP 错误地通告了属于 Amazon DNS 服务的 IP 前缀,导致部分用户的 DNS 流量被劫持到恶意服务器。

在 Kubernetes 场景下,如果 Calico 节点跟外部网络建立了 BGP session,一定要配 import/export filter:

# BIRD 过滤器 -- 只接受 Pod CIDR 范围内的路由
filter calico_import {
    if (net ~ [10.244.0.0/16{24,32}]) then accept;
    if (net ~ [10.96.0.0/12{12,32}]) then accept;
    reject;
}

filter calico_export {
    if (net ~ [10.244.0.0/16{24,32}]) then accept;
    reject;
}

MD5 认证

BGP 支持在 TCP 层使用 MD5 签名(RFC 2385)来验证邻居身份:

# BIRD 配置 MD5 密码
protocol bgp Node_10_0_0_2 from bgp_template {
    neighbor 10.0.0.2 as 64512;
    password "your-shared-secret";
}

Graceful Restart 与调优

BGP Graceful Restart(RFC 4724)允许 BGP 进程重启时不中断转发。Calico 默认开启了 Graceful Restart:

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  nodeToNodeMeshEnabled: true
  asNumber: 64512
  nodeMeshMaxRestartTime: 120s

BGP Communities 做路由策略

跟外部网络集成时,用 BGP Communities 来做精细的路由策略:

apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  prefixAdvertisements:
    - cidr: 10.244.0.0/16
      communities:
        - name: internal-pods
          value: "64512:100"

外部路由器收到这些路由后,可以根据 community 值做不同的处理 – 调整 LOCAL_PREF、设置 MED、过滤等。


结语

回顾一下这篇文章的核心要点:

BGP 基础: - BGP 是 AS 间交换路由信息的协议,Calico 用它在 K8s 节点间分发 Pod 路由 - IBGP 有防环规则:不转发从 IBGP 邻居学到的路由 - 路由选择基于 LOCAL_PREF > AS_PATH 长度 > ORIGIN > MED 等属性

状态机: - Idle -> Connect -> OpenSent -> OpenConfirm -> Established - 停在 Active/Connect = TCP 连不上;停在 OpenSent = OPEN 参数不匹配

BIRD: - Calico 用 BIRD 跑 BGP,Felix 管数据面,BIRD 管控制面 - birdc 是排查 BGP 问题的利器

Route Reflector: - Full-mesh IBGP 是 O(n^2),超过 100 节点考虑 Route Reflector - RR 把 O(n^2) 降到 O(n),通过 ORIGINATOR_ID 和 CLUSTER_LIST 防环

EVPN: - 用 BGP 作为 VXLAN 的控制平面,替代 multicast flooding - Calico 本身用 IPv4 Unicast,但底层网络可能用 EVPN 传播路由

下一篇我们会深入 eBPF 网络 – Cilium 怎么用 eBPF 绕过 iptables 和整个 Linux 网络栈的关键路径,以及 XDP、TC、socket 层各种 hook 点的工作原理。


参考资料


By .