使用 BGP
6 分钟阅读
简要概述
为什么需要 BGP
1. Service IP 宣告 (LoadBalancer 类型服务)
当在 Kubernetes 中创建一个 LoadBalancer 类型的 Service 资源时,Cilium 可以通过 BGP 将分配给该资源的 “External IP” 宣告给 ToR 交换机。这样,外部网络就知道如何将流量路由到这个 Service。
2. Pod IP 宣告 (当非 HTTP 协议需对外,如数据库实例等)
在某些网络架构中(需为了避免 NAT,实现 Pod IP 的直接可达性,提供效率),Cilium 可以将分配给每个节点的 Pod CIDR 或者甚至单个 Pod IP 宣告给外部网络。这使得外部网络可以直接路由到 Pod IP,而不需要经过 NAT 的转换。
3. 集群间路由(多集群组建为大规模 Mesh)
如果你有多个 Kubernetes 集群,BGP 可以用来在它们之间宣告路由,实现跨集群通信。
BGP 实现方式
Cilium 通过集成 gobgp 这样的外部 BGP Speaker 实现。
在组件 cilium-agent 中代码 “github.com/cilium/cilium/pkg/bgpv1/gobgp” 实现了 bgp 服务。
在宿主上
以下配置基于 “cilium/v1.14.17”、“cilium/v1.16.12” 分析。
确认开启 bgp 特性
确认已开启 cilium bgp 控制平面:
# cilium config view | grep bgp
enable-bgp-control-plane true
#
使用 crd 配置 bgp,在 cilium 中存在两种类型,分别为 CiliumBGPPeeringPolicy(v1.16 版本之前,现标记废弃)、CiliumBGPClusterConfig(v1.16 及之后版本)。
禁止同时使用两者资源,若两个资源同时存在且 cilium 通过节点选择器同时匹配到两者时,系统将优先采用 CiliumBGPPeeringPolicy 的配置。
配置 CiliumBGPPeeringPolicy
在 cilium v1.15 及以下版本使用。
创建 BGP Peer
apiVersion: "cilium.io/v2alpha1"
kind: CiliumBGPPeeringPolicy
metadata:
name: cilium-bgp
spec:
# 在哪些节点上开启与 peer 建立连接
nodeSelector:
matchLabels:
bgp: test1
virtualRouters:
- localASN: 65415
# 通告 pod 网段
exportPodCIDR: true
# 通告哪些 service 路由
serviceSelector:
matchLabels:
cilium.io/announce-bgp-service: "true"
neighbors:
- peerAddress: '192.168.17.252/32'
peerASN: 65415
对节点存在标签 “bgp=test1” 则与 bgp peer 地址为 “192.168.17.252” 且 “as=65415” 建立连接。
- 对特定接口打标签
kubectl label node 192.168.17.142 bgp=test1
对节点绑定标签以满足 bgp peer 建立连接,注意一个节点不能同时匹配多个 CiliumBGPPeeringPolicy 资源,否则无法启用。
配置 CiliumBGPClusterConfig
在 cilium v1.16 及以上版本新增 CiliumBGPClusterConfig 资源,同时废弃以上资源。
资源定义
| CRD | 用途 |
|---|---|
| CiliumBGPClusterConfig | 定义 bgp 实例,通过 name 唯一标识 |
| CiliumBGPPeerConfig | 每个 bgp 实例中关联 peer 的个性配置 |
| CiliumBGPAdvertisement | 通告路由的规则 |
| CiliumBGPNodeConfig | - |
| CiliumBGPNodeConfigOverride | - |
创建 bgp 实例
---
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPClusterConfig
metadata:
name: cilium-bgp
spec:
nodeSelector:
matchLabels:
rack: rack0
bgpInstances:
- name: "instance-65000"
localASN: 65000
peers:
- name: "peer-65000-tor1"
peerASN: 65000
peerAddress: 192.168.0.149
peerConfigRef:
name: "cilium-peer"
- name: "peer-65000-tor2"
peerASN: 65000
peerAddress: 192.168.0.121
peerConfigRef:
name: "cilium-peer"
与两个 peer “192.168.0.149”, “192.168.0.121” 建立连接,各 peer 配置见资源 “cilium-peer”
BGP Peer 配置
---
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeerConfig
metadata:
name: cilium-peer
spec:
timers:
holdTimeSeconds: 9
keepAliveTimeSeconds: 3
#authSecretRef: bgp-auth-secret
ebgpMultihop: 4
gracefulRestart:
enabled: true
restartTimeSeconds: 15
families:
- afi: ipv4
safi: unicast
advertisements:
# 匹配 CiliumBGPAdvertisement 的标签
matchLabels:
advertise: "bgp"
transport:
peerPort: 179
比如认证、超时、通告 ip 地址族等个性化配置。
BGP 通告
---
apiVersion: cilium.io/v2alpha1
kind: CiliumBGPAdvertisement
metadata:
name: bgp-advertisements
labels:
advertise: bgp
spec:
advertisements:
# 通告 Service VIP
- advertisementType: "Service"
service:
addresses:
- ClusterIP
- LoadBalancerIP
# 通告 POD 网段
- advertisementType: "PodCIDR"
attributes:
communities:
standard: [ "65000:99" ]
localPreference: 99
- 通告 Service
当类型为 “LoadBalancerIP” 时,需确保 k8s 集群能分配 IP,这里也使用 cilium LB IPAM 特性。
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
name: "default"
spec:
blocks:
- cidr: "192.168.253.0/24"
应用场景
配置 Router ID
如果集群运行的是 ipv4 或 ipv4/ipv6 双栈,cilium 会自动使用宿主 ipv4 上的 IP 作为 Router ID。
如果仅运行 ipv6 或者需要手工配置的话,需要添加注解:
kubectl annotate node 192.168.17.142 cilium.io/bgp-virtual-router.65415="router-id=192.168.17.142"
配置宿主监听端口
宿主通过随机端口与远端 179 通讯:
kubectl logs -f cilium-nlkxs -n kube-system --tail=10
level=info msg="type:STATE peer:{conf:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415} state:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415 session_state:OPENSENT router_id:\"<nil>\"} transport:{local_address:\"192.168.17.140\" local_port:50409 remote_port:179}}"
level=info msg="type:STATE peer:{conf:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415} state:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415 session_state:IDLE router_id:\"<nil>\"} transport:{local_address:\"192.168.17.140\" local_port:50409 remote_port:179}}"
level=info msg="type:STATE peer:{conf:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415} state:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415 session_state:ACTIVE router_id:\"<nil>\"} transport:{local_address:\"192.168.17.140\" local_port:50409 remote_port:179}}"
level=info msg="type:STATE peer:{conf:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415} state:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415 session_state:OPENSENT router_id:\"<nil>\"} transport:{local_address:\"192.168.17.140\" local_port:47671 remote_port:179}}"
level=info msg="Peer Up" Key=192.168.17.252 State=BGP_FSM_OPENCONFIRM Topic=Peer asn=65415 component=gobgp.BgpServerInstance subsys=bgp-control-plane
level=info msg="type:STATE peer:{conf:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415} state:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415 session_state:OPENCONFIRM router_id:\"192.168.17.252\"} transport:{local_address:\"192.168.17.140\" local_port:47671 remote_port:179}}"
level=info msg="type:STATE peer:{conf:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415} state:{local_asn:65415 neighbor_address:\"192.168.17.252\" peer_asn:65415 session_state:ESTABLISHED router_id:\"192.168.17.252\"} transport:{local_address:\"192.168.17.140\" local_port:47671 remote_port:179}}"
通过主动标记设置本地端口:
kubectl annotate node 192.168.17.142 cilium.io/bgp-virtual-router.65415="local-port=61179"
通告 LoadBalancer IP
以下配置基于 “cilium/v1.14.17” 分析。
1. 创建示例服务
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: registry.cn-hangzhou.aliyuncs.com/opsaid/nginx:1.23-debian-11
name: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
runAsNonRoot: true
securityContext:
seccompProfile:
type: RuntimeDefault
创建 “nginx” 应用默认在 “8080” 端口监听。
2. 创建 “LoadBalancer” 类型 “Service”
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
# 与 CiliumBGPPeeringPolicy 联动
cilium.io/announce-bgp-service: "true"
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 8080
# 前提条件
externalTrafficPolicy: Cluster
# 如果存在多个提供方,明确使用 cilium bgp 分配
loadBalancerClass: "io.cilium/bgp-control-plane"
type: LoadBalancer
对 “default” 空间下标签为 “app=nginx” 创建 “LoadBalancer”,此时访问 “Service” 对外的端口为 “80” 会自动转发至应用 “8080” 服务。
# kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.233.46.209 <pending> 80:31624/TCP 2m40s
#
创建成功后,因为集群本身不支持自动分配 IP,所以 “EXTERNAL-IP” 会一直处于 “pending” 状态。
3. 记录当前 TOR 交换机 BGP 路由
>display bgp routing-table
BGP Local router ID is 192.168.17.252
Status codes: * - valid, > - best, d - damped, a - add path,
h - history, i - internal, s - suppressed, S - Stale
Origin : i - IGP, e - EGP, ? - incomplete
Total Number of Routes: 4
Network NextHop MED LocPrf PrefVal Path/Ogn
*>i 192.168.200.0/24 192.168.17.140 100 0 i
*>i 192.168.201.0/24 192.168.17.141 100 0 i
*>i 192.168.202.0/24 192.168.17.142 100 0 i
*>i 192.168.203.0/24 192.168.17.143 100 0 i
>
仅到集群 POD CIDR 路由。
3. 手工更改 “Service” 中 “status” 地址
在 “cilium v1.14” 版本下需要更改,“v1.16” 版本会自动分配 IP。
cat <<EOF > patch.json
{
"status": {
"loadBalancer": {
"ingress": [
{ "ip": "192.168.253.1" }
]
}
}
}
EOF
kubectl patch service nginx \
--namespace default \
--type merge \
--patch-file patch.json \
--subresource status
# kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx LoadBalancer 10.233.46.209 192.168.253.1 80:31624/TCP 8m9s
# ^C
这里为什么需要手工更改 “status” 信息?
4. 再次验证 TOR 是否学到路由
>display bgp routing-table
BGP Local router ID is 192.168.17.252
Status codes: * - valid, > - best, d - damped, a - add path,
h - history, i - internal, s - suppressed, S - Stale
Origin : i - IGP, e - EGP, ? - incomplete
Total Number of Routes: 8
Network NextHop MED LocPrf PrefVal Path/Ogn
*>i 192.168.200.0/24 192.168.17.140 100 0 i
*>i 192.168.201.0/24 192.168.17.141 100 0 i
*>i 192.168.202.0/24 192.168.17.142 100 0 i
*>i 192.168.203.0/24 192.168.17.143 100 0 i
*>i 192.168.253.1/32 192.168.17.140 100 0 i
* i 192.168.17.141 100 0 i
* i 192.168.17.142 100 0 i
* i 192.168.17.143 100 0 i
>
可以观察到 TOR 交换机学习到对端通告的 “192.168.253.1/32” 路由并标记 “192.168.17.140” 为下一跳最优。
5. 通告 LoadBalancer IP 的前提条件
前提条件:
- 需 “externalTrafficPolicy” 为 “Cluster” 类型;
- 需 “LoadBalancer” 存在 “status.loadBalancer.ingress[].ip” 值;
- 如果存在多个负载均衡器,需明确 “loadBalancerClass” 值;
当集群本身不支持自动分配 LoadBalancer 类型 Service IP 时,需要手工添加。
此版本 “cilium/v1.14.17” 关键判断代码如下:
github.com/cilium/cilium/pkg/bgpv1/manager/reconcile.go
......
// Ignore externalTrafficPolicy == Local && no local endpoints
if svc.Spec.ExternalTrafficPolicy == slim_corev1.ServiceExternalTrafficPolicyLocal &&
!hasLocalEndpoints(svc, ls) {
return nil, nil
}
var desiredRoutes []netip.Prefix
for _, ingress := range svc.Status.LoadBalancer.Ingress {
if ingress.IP == "" {
continue
}
addr, err := netip.ParseAddr(ingress.IP)
if err != nil {
continue
}
desiredRoutes = append(desiredRoutes, netip.PrefixFrom(addr, addr.BitLen()))
}
......
分配 LoadBalancer IP
在 通告 LoadBalancer IP 实验中可知,创建 LoadBalancer 资源后,会等待集群分配 “EXTERNAL-IP” 如果持续未分配则无法通告路由至上联 TOR 交换机。
LB IPAM 是一项允许 Cilium 为 LoadBalancer 类型的 Service 分配 IP 地址的功能。在公有云环境中,此类操作通常由云服务商自动处理;但在私有云部署场景下,往往缺乏现成的 IP 分配机制。
这里我们利用 cilium 特性,通告创建一个 CIDR 网络,允许自动分配 IP 地址:
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
name: "default"
spec:
# 在 cilium/1.14 下参数配置
cidrs:
- cidr: "192.168.253.0/24"
# 在 cilium/1.16 下参数配置
#blocks:
#- cidr: "192.168.253.0/24"
注意:
- 更新IP地址池可能导致IP地址重新分配,进而引发服务IP变更;
- 如果删除该资源则对已分配的 IP 也会同时回收,也就是删除已通告的 BGP 路由;
- 默认情况下,LB-IPAM 会使用给定 CIDR 中的所有IP地址,所以分配是记得保留。