使用 BGP

简要概述

为什么需要 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 的前提条件

前提条件:

  1. 需 “externalTrafficPolicy” 为 “Cluster” 类型;
  2. 需 “LoadBalancer” 存在 “status.loadBalancer.ingress[].ip” 值;
  3. 如果存在多个负载均衡器,需明确 “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"

注意:

  1. 更新IP地址池可能导致IP地址重新分配,进而引发服务IP变更;
  2. 如果删除该资源则对已分配的 IP 也会同时回收,也就是删除已通告的 BGP 路由;
  3. 默认情况下,LB-IPAM 会使用给定 CIDR 中的所有IP地址,所以分配是记得保留。

其他参考

ToR 交换机架构




最后修改 2025.08.21: chore: update xx (3dd905e)