上一篇我们搞清楚了 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 网络场景下你必须理解的部分:
- BGP 基本概念:AS、IBGP、EBGP、path attributes
- BGP 状态机:理解 peer 建立连接的过程
- 路由选择算法:BGP 怎么决定用哪条路
- BIRD:Calico 用来跑 BGP 的守护进程
- Route Reflector:大规模集群为什么需要它
- BGP EVPN:数据中心的 Overlay + Underlay 统一
- 实验:用 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),它会做两件事:
- 在 node-1 上创建 veth pair,把一端放到 Pod 的
netns,另一端留在宿主机,然后添加一条本地路由:
10.244.0.5 dev cali1234 scope link - 通过 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 eth0proto 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 模式
- 节点在同一 L2 网段(最简单的情况)
- 数据中心网络支持 BGP(现代 Clos 架构几乎都支持)
- 你对网络性能有极致要求,不想要隧道封装的开销
- 你需要跟物理网络集成(比如让外部路由器知道 Pod 的 IP)
二、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(必须存在、必须识别):
- ORIGIN:路由的来源。
IGP(0)表示来自 IGP 协议,EGP(1)表示来自 EGP,INCOMPLETE(2)表示通过 redistribute 学来的 - AS_PATH:路由经过的 AS 列表。这是 BGP 的核心防环机制 – 如果一条路由的 AS_PATH 里包含自己的 ASN,就丢弃它。同时也是路由选择的关键因素
- NEXT_HOP:到达这个目的网络的下一跳 IP
Well-known Discretionary(必须识别、可选存在):
- LOCAL_PREF:本地偏好,只在 IBGP 内部传播。值越大越优先
- ATOMIC_AGGREGATE:提示路由被聚合过
Optional Transitive(可选、可传递):
- COMMUNITY:路由标签,32 位值,常用格式
AS:value。可以用来做路由策略
Optional Non-Transitive(可选、不传递):
- MED(Multi-Exit Discriminator):多出口鉴别器。值越小越优先
- ORIGINATOR_ID、CLUSTER_LIST:Route Reflector 使用的属性,后面会讲
三、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 邻居建不起来的问题至关重要。
六个状态:
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 就对了。如果长时间停在
Active 或 Connect,查 TCP
连通性和防火墙。如果停在 OpenSent 或
OpenConfirm,查 OPEN 参数是否匹配。
五、BIRD:Calico 的 BGP 引擎
BIRD(BIRD Internet Routing Daemon)是一个轻量级的路由守护进程,支持 BGP、OSPF、RIP、静态路由等协议。Calico 选择 BIRD 作为 BGP 引擎有几个原因:
- 轻量:一个 BIRD 进程只占几 MB 内存
- 稳定:在 Linux 路由领域有十几年的历史
- 灵活:内置过滤器语言,可以做复杂的路由策略
- 性能:全表处理性能满足 Kubernetes 场景
BIRD 在 Calico 里的角色
每个 Kubernetes 节点上,Calico 运行一个
calico-node
Pod(DaemonSet),里面包含两个主要进程:
- Felix:负责数据平面 – 编程路由表、iptables/eBPF 规则、IPAM
- BIRD:负责控制平面 – 跟其他节点的 BIRD 建立 BGP session,交换路由信息
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;
}几个关键点:
merge paths on– 让 BIRD 支持 ECMP。如果到同一个前缀有多条等价路径,BIRD 会把所有路径都写入内核路由表,内核就可以做多路径负载均衡。graceful restart– BGP Graceful Restart(RFC 4724)。当 BIRD 重启时,对端不会立即撤回路由,而是等一段时间让 BIRD 恢复。这对 Calico 升级时保持网络连通性很重要。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。
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 邻居分成两类:
- Client(客户端):普通节点,只跟 RR 建立 BGP session
- Non-client:其他 RR 或非客户端节点
RR 的转发规则:
- 从 Client 学到的路由 -> 转发给所有 Client 和 Non-client
- 从 Non-client 学到的路由 -> 只转发给 Client
- 从 EBGP 学到的路由 -> 转发给所有 Client 和 Non-client
N 个节点 + 1 个 RR = N 条 session,从 O(n^2) 降到 O(n)。
防环机制
RR 打破了”不转发给 IBGP 邻居”的规则,那怎么防止路由环路?靠两个属性:
- ORIGINATOR_ID:RR 在反射路由时添加,记录路由的原始通告者。如果一个节点收到 ORIGINATOR_ID 是自己的路由,就丢弃
- CLUSTER_LIST:类似 AS_PATH,但用于 RR 集群。RR 反射路由时把自己的 CLUSTER_ID 加进去。如果收到的路由的 CLUSTER_LIST 包含自己的 CLUSTER_ID,就丢弃
在 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。
生产建议
- 至少 2 个 RR,分布在不同机架或故障域
- RR 本身也可以跑 workload,但超大集群建议专用节点
- 分层 RR:超大集群(数千节点)可以部署多层 – 机架级 RR 聚合本机架路由,上报给核心 RR
- 监控 BGP session 数量和收敛时间,Prometheus + node_exporter 可以暴露 BIRD 的 metrics
七、BGP 与 EVPN:数据中心的统一控制平面
传统数据中心的问题
传统数据中心网络有两种路子:
Underlay(纯路由):基于 BGP 的 IP Fabric,Leaf-Spine 架构。问题是不支持 L2 扩展 – VM/容器迁移到另一个 Leaf 下面,IP 要变。
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 ipv4 或
channel 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/24 – 10.1.12.2/24 -
router-1 – router-3: 10.1.13.1/24 –
10.1.13.3/24 - router-2 – router-3:
10.1.23.2/24 – 10.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 vtyrouter-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 tableBFD 加速故障检测
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: 120sBGP 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 点的工作原理。
参考资料
- RFC 4271: “A Border Gateway Protocol 4 (BGP-4)”
- RFC 4456: “BGP Route Reflection: An Alternative to Full Mesh IBGP”
- RFC 4760: “Multiprotocol Extensions for BGP-4”
- RFC 4724: “Graceful Restart Mechanism for BGP”
- RFC 7432: “BGP MPLS-Based Ethernet VPN”
- RFC 2385: “Protection of BGP Sessions via the TCP MD5 Signature Option”
- RFC 6811: “BGP Prefix Origin Validation”
- BIRD 官方文档: https://bird.network.cz/
- Calico 官方文档: “Configure BGP peering”, “Route reflectors”
- containerlab: https://containerlab.dev/
- FRRouting: https://frrouting.org/