Cilium Cilium

使用 BGP 通告 Service IP

没有云负载均衡器?Cilium 可以通过 BGP 将 Service IP 直接通告到你的物理网络。

15 min 高级

云服务商的 LoadBalancer Service 类型之所以可用,是因为云平台有 API 可以按需创建外部负载均衡器。在裸金属或任何没有这类 API 的环境中,LoadBalancer Service 会一直停留在 Pending 状态,除非有别的组件来承担这个角色。Cilium 的 BGP 控制平面就是一种方案:通过集群节点直接与上游路由器建立 BGP 会话,把 Service 的 IP 路由进你的物理网络。本指南介绍配置它所需的四种资源,以及如何从两端验证 BGP 会话确实已建立。

BGP 何时是合适的工具

从集群节点向上游路由器建立 BGP 对等关系,是一次真正的网络集成。它需要与管理物理(或虚拟)网络的人协调,需要对方路由器上的配置与你在 Kubernetes 一侧的对等配置相匹配,也需要真正熟悉 BGP 的概念,例如自治系统和路由通告。对于需要让 Service IP 能被网络其他部分真正路由到的本地或裸金属集群,这是合适的工具,效果与云上的 LoadBalancer 相同。对于较简单的需求,Cilium 的 L2 announcements 功能(通过本地网段上的 ARP/NDP 通告 Service 的 IP,不涉及路由协议)是更轻量的替代方案,如果你的集群位于单一网段,且不需要在网段或数据中心之间进行三层路由,值得优先考虑。

四种资源

四个 CiliumBGP* 自定义资源协同工作:

  • CiliumLoadBalancerIPPool 定义 Cilium 可以分配给 LoadBalancer Service 的 IP 地址范围。
  • CiliumBGPAdvertisement 定义通过 BGP 通告什么内容,这里是取自该地址池的 Service IP。
  • CiliumBGPPeerConfig 定义如何使用 BGP:定时器、graceful restart 行为、地址族。
  • CiliumBGPClusterConfig 将它们串联起来:哪些节点参与(通过标签选择器)、它们的本地 AS 号,以及它们连接到哪些上游对等体(通过 IP 和 AS 号)。

定义 IP 池

apiVersion: cilium.io/v2alpha1
kind: CiliumLoadBalancerIPPool
metadata:
  name: apps-pool
spec:
  allowFirstLastIPs: "No"
  blocks:
    - cidr: "10.10.211.0/24"

allowFirstLastIPs: "No" 会把网络地址和广播地址(.0 和 .255)排除在可分配范围之外,这是沿用传统子网分配的常见约定。请把 CIDR 调整为网络团队实际分配给你的范围;从他们的角度看,它必须是真实、可路由的地址空间,而不是你未经协调随意挑选的私有范围。

定义要通告的内容

apiVersion: cilium.io/v2alpha1
kind: CiliumBGPAdvertisement
metadata:
  name: bgp-advertisements
  labels:
    advertise: bgp
spec:
  advertisements:
    - advertisementType: Service
      service:
        addresses:
          - LoadBalancerIP
      selector:
        matchExpressions:
          - key: io.kubernetes.service.namespace
            operator: Exists

这会通告匹配 Service 的 LoadBalancerIP。这里的 labels 字段(advertise: bgp)是下面的 peer config 用来关联两者的依据,而不是 Service 本身上的标签。

定义对等体行为

apiVersion: cilium.io/v2alpha1
kind: CiliumBGPPeerConfig
metadata:
  name: bgp-peer-config
spec:
  timers:
    holdTimeSeconds: 9
    keepAliveTimeSeconds: 3
  gracefulRestart:
    enabled: true
    restartTimeSeconds: 15
  families:
    - afi: ipv4
      safi: unicast
      advertisements:
        matchLabels:
          advertise: bgp

这里的 families.advertisements.matchLabels 通过匹配上面定义的通告资源的标签,把这个对等配置与通告资源连接起来。

串联起来:集群配置与节点选择

apiVersion: cilium.io/v2alpha1
kind: CiliumBGPClusterConfig
metadata:
  name: bgp-cluster-config
spec:
  nodeSelector:
    matchLabels:
      role: bgp-node
  bgpInstances:
    - name: instance-1
      localASN: 65011
      peers:
        - name: leaf-switch-1a
          peerASN: 65002
          peerAddress: "10.10.210.2"
          peerConfigRef:
            name: bgp-peer-config
        - name: leaf-switch-1b
          peerASN: 65002
          peerAddress: "10.10.210.3"
          peerConfigRef:
            name: bgp-peer-config

nodeSelector 把 BGP 对等关系限制在有意选定的节点子集上,即带有 role: bgp-node 标签的节点,而不是让集群中每个节点都去和上游路由器建立对等:

kubectl label node worker-4 worker-5 worker-6 role=bgp-node

合理的范围是两到八个对等节点:既足以避免单点故障,又不会要求网络团队为一个可能有几十甚至上百个节点的集群,针对每个节点都配置和维护 BGP 会话。具体多少合适取决于集群的物理布局;如果分布在多个机架或机排,通常应当让每个位置的节点都参与对等,而不是把所有 BGP 会话集中在一处。

应用这四个资源,然后从 Kubernetes 一侧检查:

kubectl apply -f bgp-pool.yaml -f bgp-advertisement.yaml -f bgp-peer-config.yaml -f bgp-cluster-config.yaml

验证会话确实建立

从节点的角度,在带有 BGP 标签的节点上运行的 Cilium Pod 内,使用 Cilium 自带的调试 CLI:

kubectl -n kube-system exec <cilium-pod-on-bgp-node> -- cilium-dbg bgp peers

查找会话状态为 established。其他任何状态(idle、connect、active)都意味着会话尚未真正建立,最常见的原因是两端 AS 号不匹配、对等地址不可达,或者上游路由器根本还没有配置为接受这个对等体;在断定 Kubernetes 一侧的配置有误之前,请先确认网络一侧的对等配置与你这里声明的完全一致。

确认地址确实被通告

创建一个从该池中取地址的 LoadBalancer Service:

kubectl get svc -o wide

它的 EXTERNAL-IP 应该是该池 CIDR 内的地址。在上游路由器上(或其后的任何设备,如防火墙、核心路由器),检查这个地址是否作为通过 BGP 学到的 /32 路由出现,并由对等节点的地址通告。看到这条具体的 /32 出现,并且能够通过它成功访问 Service,才是对 IP 池分配、BGP 通告以及物理网络自身路由三者都正确连接的真正端到端确认,而不仅仅是 Kubernetes 一侧的资源应用时没有报错。

热门指南