Cilium Cilium

BGP で Service の IP をアドバタイズする

クラウドのロードバランサーがない環境でも、Cilium は BGP を使って Service の IP を物理ネットワークに直接アナウンスできます。

15 min learn.level.上級

クラウドプロバイダーの LoadBalancer Service タイプが機能するのは、クラウドに外部ロードバランサーをオンデマンドでプロビジョニングする API があるからです。ベアメタルなど、そのような API がない環境では、別の何かがその役割を担わない限り、LoadBalancer Service は永遠に Pending のままです。Cilium の BGP コントロールプレーンは、その役割を果たす方法の 1 つで、クラスターノードから上流ルーターに直接 BGP を話すことで、Service の IP を物理ネットワークにルーティングします。このガイドでは、これを構成する 4 つのリソースと、BGP セッションが実際に両側から確立されていることを確認する方法を説明します。

BGP が適切なツールとなるケース

クラスターノードから上流ルーターへの BGP ピアリングは、本格的なネットワーク統合です。物理(または仮想)ネットワークを管理している担当者との調整が必要で、Kubernetes 側のピア設定に一致するルーター側の設定が必要です。また、自律システムや経路アドバタイズといった BGP の概念についての実運用上の知識も必要です。クラウドの LoadBalancer と同じように、Service の IP をネットワークの他の部分から実際にルーティング可能にする必要がある、オンプレミスまたはベアメタルのクラスターに適したツールです。より単純なニーズであれば、Cilium の L2 アナウンス機能(ルーティングプロトコルを使わず、ローカルネットワークセグメント上の ARP/NDP で Service の IP をアドバタイズする)のほうが軽量な代替手段であり、クラスターが単一のネットワークセグメントにあり、セグメント間やデータセンター間のレイヤー 3 ルーティングが不要であれば、まずこちらを検討する価値があります。

4 つのリソース

4 つの 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)は、以下のピア設定が両者を結びつけるために参照するものであり、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

妥当な範囲は 2 〜 8 ノードです。単一障害点を避けるには十分で、数十から数百ノードのクラスターのすべてのノードに対して、ネットワークチームが BGP セッションを設定して維持する必要もありません。適切な数はクラスターの物理的な配置によって異なります。複数のラックや列にまたがっている場合は、すべての BGP セッションを 1 か所に集中させるのではなく、それぞれからピアリングするのが一般的です。

4 つのリソースをすべて適用し、まず 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 に正常に到達できれば、Kubernetes 側のリソースがエラーなく適用されただけでなく、IP プールの割り当て、BGP アドバタイズ、物理ネットワーク自体のルーティングがすべて正しく連携していることを、エンドツーエンドで確認できたことになります。

人気のガイド