Calico 作为一种常用的 Kubernetes 网络插件,使用 BGP 协议对各节点的容器网络进行路由交换。本文是《Calico BGP 功能介绍》系列的第一篇,介绍 Calico 所使用的 BGP 软件路由器——BIRD。
关于 BGP 协议,网上资料众多,在这里不再做介绍。另外,推荐《BGP in the datacenter》作为 BGP 应用的进阶阅读,另有中文翻译版本[1]
BIRD
BIRD 实际上是 BIRD Internet Routing Daemon 的缩写(禁止套娃),是一款可运行在 Linux 和其他类 Unix 系统上的路由软件,它实现了多种路由协议,比如 BGP、OSPF、RIP 等。
概念
BIRD 会在内存中维护许多路由表,路由表根据不同的协议,通过与各种“其他事物”交换路由信息,来更新路由规则。这里说的“其他事物”可能是其他的路由表,也可能是外部的路由器,还可以是内核的某些 API。
路由表
路由表(Routing tables)是 BIRD 的核心,一个路由表是内存中一组路由规则的集合,BIRD 根据网络类型的不同会有多种路由表。默认情况下,BIRD 有master4
和master6
两个默认的路由表,分别保存 IPv4 和 IPv6 路由规则。除此外,你也可以创建其他的路由表,比如在配置文件bird.conf
中添加如下配置,创建一个 IPv4 的路由表my_table
。
ipv4 table my_table;
要注意的是,BIRD 的路由表仅仅是一个表,并没有转发的功能,真正的转发控制,是内核的 FIB(Forwarding Information Base)。而 BIRD 的kernel
协议,可以将 BIRD 路由表与 FIB 进行同步,后面会介绍。
路由规则中包含了各种路由属性(Route attributes),网络类型不同的路由表,其路由属性也不太一样,比如常见的 IPv4 和 IPv6 的路由表,会包括两个路由属性:
- 路由目的地
- 路由下一跳
而 VPN 路由表还会包含路由属性:路由标识符(Route distinguisher)。
BIRD 的每种表都会将一个或一组路由属性作为主键,类似于 SQL 数据库。当多个来源都提供了相同主键的路由条目时,BIRD 会根据一定的规则选择最优路由。例如 IPv4 和 IPv6 类型的路由表,将“路由目的地”作为主键。
协议与通道
协议(Protocols)将路由表和“其他事物”连接起来。“其他事物”可以是一个 Socket 对象,连接了外部的路由器,例如 BGP 路由协议;也可以是修改 FIB 的内核 API,例如kernel
协议;也可以是空,比如静态路由static
协议。一个协议可以实例化为多个对象,例如创建多个 BGP 协议的实例,以表示多个 BGP 邻居。
协议也会提供一些路由属性,根据协议的不同路由属性也不同,比如使用 BGP 协议时,会有bgp_path
属性。
协议可能包含一些通道(Channels),通道是在协议和路由表之间,配置了路由规则在导入(import))、导出(export)两个方向上的行为,导入导出是针对路由表来说的,路由规则经过通道后,或是被接收(Accept),或是被拒绝(Reject),或是被修改。不同的协议可拥有的通道也不一样,例如 BGP 协议可以同时拥有IPv4
和IPv6
通道,RIP 只能拥有 IPv4 或 IPv6 一种协议,而 BFD 则没有通道。
下面是根据官网样例修改而来的配置,实例化了一个名为peer_one
的 BGP 协议,并且设置了ipv4
和ipv6
两个通道,两个通道都未指明连接的路由表,则使用默认的master4
与master6
路由表。其中在ipv4
通道中,导入方向配置为全部接收,导出方向上只导出静态路由,同时还会对路由规则的 BGP 信息进行修改:修改 bgp community,修改 bgp path;在ipv6
通道上,则直接使用默认配置。
protocol bgp peer_one {
local 198.51.100.14 as 65000; # Use a private AS number
neighbor 198.51.100.130 as 64496; # Our neighbor ...
ipv4 {
export filter { # We use non-trivial export rules
if source = RTS_STATIC then { # Export only static routes
# Assign our community
bgp_community.add((65000,64501));
# Artificially increase path length
# by advertising local AS number twice
if bgp_path ~ [= 65000 =] then
bgp_path.prepend(65000);
accept;
}
reject;
};
import all;
};
ipv6;
}
主要功能
模板
在 BIRD 中,可以定义模板(template),通过模板来创建一个协议的多个实例。模板在使用 BGP 协议时非常好用,因为 BGP 通常都会设置多个 BGP Peer。例如下面配置,通过模板提取出共用的配置,然后利用模板创建多个 BGP 邻居。
代码语言:javascript复制template bgp foo {
local 198.51.100.14 as 65000;
ipv4 {
table mytable4;
import filter { ... };
export none;
};
ipv6 {
table mytable6;
import filter { ... };
export none;
};
}
protocol bgp bgp1 from foo {
neighbor 198.51.100.130 as 64496;
}
protocol bgp bgp2 from foo {
neighbor 198.51.100.131 as 64496;
}
过滤器
过滤器(Filters)在上面的用例中已经出现了多次,通过在通道中添加过滤器,可以灵活地控制路由规则的交换。在过滤器中,你可以像访问变量一样直接使用各种路由属性,来编写各种判断条件,以决定对路由规则是 ACCEPT 还是 REJECT,或是直接对路由属性进行修改。
为了便于复用,还可以以函数的形式定义一个过滤器,使用时在相应的通道中直接调用。需要注意的是,编写过滤器需要使用 BIRD 提供的专门的编程语言,它提供了一些例如if
、switch
的简单控制结构,但不允许有循环出现。同时,除了int
、string
这些基础的数据结构外,它还提供了例如bgppatch
、bgpmask
等这种表示路由规则中某些信息的数据结构。例如下面定义了一个名为not_too_far
的过滤器,丢弃掉rip_metric
大于 10 的路由规则,可以通过import filter not_too_far
直接调用此函数。
filter not_too_far
int var;
{
if defined( rip_metric ) then
var = rip_metric;
else {
var = 1;
rip_metric = 1;
}
if rip_metric > 10 then
reject "RIP metric is too big";
else
accept "ok";
}
常见协议
这里只介绍 Calico 中使用到的几种协议,以及用到的协议属性。
device
准确来说,device
并不算是一个协议,它不产生任何路由,也不支持通道,而是被用来从内核中获取网卡设备的信息。每个bird.conf
的配置文件中,都应定义一个device
。
protocol device {
scan time 10; # Scan the interfaces often
interface "eth0" {
preferred 192.168.1.1;
preferred 2001:db8:1:10::1;
};
}
上面配置定义了 BIRD 每 10s 扫描一遍eth0
网卡,同时定义了首选的 IP 地址。
kernel
kernel
也不算真正的协议,它负责同步路由表与内核。如果内核支持多个内核路由表,那么可以创建多个kernel
实例,否则只需要创建一个kernel
实例。kernel
协议有两个限制:
- 不能将多个
kernel
实例都连接到同一个路由表上 - 不能修改导出(export)路由规则的目标地址
一些主要的参数包括:
learn switch
,开启后路由表可以从内核中学习到非内核生成(其他方式添加)的路由。“内核生成的路由”指的是由于本机网络的配置而产生的路由,比如eth0
在被分配192.168.1.2/24
后,会自动产生一条目的地为192.168.1.0/24
,下一跳为eth0
的路由。需要注意的是,即使是开启learn
,kernel
也不会将这些路由从内核导入(import)路由表,这种路由的传递需要使用到direct
协议。(switch
表示on
和off
两种值,下面相同)persist switch
,BIRD 退出时,在内核保留同步的路由(即不会进行 clean up 操作)。scan time number
,同步间隔,单位秒
# 同步master4、master6路由表与主FIB,并在退出后保持同步的路由
protocol kernel {
learn;
persist;
}
direct
如上面所述,direct
用于将内核生成的路由规则从内核导入到 BIRD 路由表中,可用的参数包括:
interface pattern [, ...]
,用于指定传递由哪些网卡生成的路由规则,默认是全部网卡check link switch
,开启后会考虑 link 的状态,当 link 状态为 up 时,传递路由,否则,撤销传递的路由
# 同步除了eth0以外的其他网卡
protocol direct {
interface -"eth0", "*";
}
BGP
每一个 BGP 协议的实例,代表了一个 BGP Peer 连接。需要注意的是,部分参数的默认值对 IBGP 与 EBGP 并不相同,例如aigp
默认在 IBGP 中是开启的,默认在 EBGP 是关闭的。协议的主要参数包括:
local [ip] [port number] [as number]
,可以用来指定 BGP 的源 IP 地址以及本地的 AS。multihop [number]
,表示多跳的 BGP,后面的number
可以用来设置TTL
的值,IBGP 默认开启;相反的,还有个参数为direct
,表示与 BGP 邻居直连,EBGP 默认开启。source address ip
,用来指定本端使用的 BGP 源地址。add paths switch|rx|tx
,开启时,会将 BGP 配置为向同一目标通告多个路径,否则 BGP 仅通告活动路径。password string
,使用设置的密码进行 BGP 的身份验证。rr client
,开启 RR 模式(Route Reflector)。rr cluster id IPv4 address
,设置 RR 的cluster id
,以防止路由环路。默认情况下,会直接使用 BGP 的router id
(一般是 ipv4 地址)作为cluster id
,当有多个 RR 时,需要使用此参数设置相同的cluster id
。bfd switch|graceful
,使用 BFD 作为 BGP 协议心跳机制。passive switch
,被动模式,不主动初始化连接,而是等待其他 BGP 邻居发起连接。
以上是协议一层的配置参数,在 BGP 协议中,通道也会有额外的参数,例如:
gateway direct|recursive
,用来控制如何计算路由的 gw 属性。当设置为direct
时,如果路由中的bgp_next_hop
是和本机中的某个地址同一子网(同一个二层),则 gw 直接为bgp_next_hop
,否则为对端 BGP Peer 的 IP 地址;当设置为recursive
时,会从 IGP 路由表中查询bgp_next_hop
来作为 gw。next hop keep switch|ibgp|ebgp
,开启后,BGP 不再将next hop
属性修改为自身,而是直接通告原始的next hop
,这个参数在多跳的 EBGP 场景或 BGP 路由反射中会使用到。
参考文档
BIRD 官方手册[2]
脚注
[1]
中文翻译版本: https://cshihong.github.io/2020/04/18/BGP-in-the-datacenter-数据中心的BGP-数据中心网络架构-Clos网络架构/
[2]
BIRD 官方手册: https://bird.network.cz/?get_doc&v=20&f=bird.html#toc5
原文链接:https://maao.cloud/2021/01/26/Calico-BGP功能介绍:BIRD简介/