Cilium L2 Announcement を使ってみる

Sreake事業部

2024.8.23

はじめに

Sreake事業部でインターンをしている小林です。

本記事では、Cilium v1.14で追加されたCilium L2 Announcementを検証しました。

Kubernetes External Load Balancer

Kubernetesクラスタ外からPodに通信するための方法の1つとして、LoadBalancerタイプのServiceを作成する方法があります。マネージドKubernetesサービスでこのようなServiceを作成した場合には、Cloud Controller Managerが管理するService ControllerからクラウドプロバイダのAPIにリクエストを送ることで、実際にロードバランサを作成しています。GKEであれば、Cloud Load Balancing APIにリクエストを送り、Cloud Load Balancingがデプロイされ、クライアントからのリクエストは複数ノードにロードバランスされて、最終的にPodに到達します[1]

https://kubernetes.io/images/docs/components-of-kubernetes.svg

その一方、オンプレミスでKubernetesクラスタを構築した場合には、物理的なロードバランサが存在しないため、LoadBalancerタイプのServiceを作成しても永遠に”pending”となり続けます。

これを可能な限り問題なく機能するように作られたプロダクトとして、MetalLBがあり、オンプレミスでKubernetesクラスタを作成して、LoadBalancerタイプのServiceを使いたい場合には、Metal LBを使う方法が広く普及しています。

Cilium L2 Announcement

Cilium L2 Announcementは Cilium v1.14からβ版として機能が提供されています[2]

Cilium v1.13でLoadBalancer IPアドレス管理(LB-IPAM)機能が追加されており、この時点でCiliumがLoadBalancerタイプのServiceにEXTERNAL-IPを割り当て、そのIPアドレスをBGPでアドバタイズできるようになっていました。つまり、v1.13でMetal LBのBGPモードを置き換えることは可能でした。

しかし、BGPを使用したくない、またはローカルネットワークでアクセスできるようにしたいといった要望に応えるためにL2 Announcementが実装されました。L2 Announcementでは、Serviceに割り当てられたEXTERNAL-IPをアナウンスするノードを1台選択して、そのノードのみクライアントからのARP requestに対してARP replyを返すことで、クラスタ外にいるクライアントからのリクエストもPodまで到達するようになります。また、Podが動作するノードがダウンした時には別のノードのPodにリクエストが到達するようになるといったフェイルオーバーの仕組みも兼ね備えています。この機能を使うことで、会社や学内、家のネットワークにKubernetesクラスタを構築した場合にもLoadBalancerタイプのServiceを作成することができます。これにより、Metal LBのLayer2モードも置き換えることが可能になります。

この機能を使用すると、Podが動作しているNodeのいずれかがExternal-IPのARPリクエストに応答するようになり、結果として、クライアントはPodにアクセスすることができます。NodePort を使用する場合、宛先のサーバを決定するのはクライアントであり、そのサーバがダウンするとIP+ポートの組み合わせは使用できなくなります。L2 Announcementでは、Serviceの仮想IP(VIP) は別のサーバに移行するだけで、引き続き機能します。

MetalLBからCiIiumへ

L2 Announcementにより、MetalLBで行っていたことを実現できるようになります[3]。そのため、すでにCNIとしてCiliumを使用している場合、MetalLBを使わなくても、Cilium単体で済むようになるといったメリットがあります。

検証

実際にKubernetesクラスタを作成して、L2 Announcementを試してみます。L2 Announcementを使用するには、kube-proxy replacementを有効にする必要があります。

環境構築

今回は、マスターノード1台、ワーカーノード3台の4台構成にしており、kube-proxy replacementを有効にしたCiliumを使うために、デフォルトのCNIとkube-proxyも無効にしてクラスタを作成します。

$ cat <<EOF | kind create cluster --config -
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker
- role: worker
networking:
  disableDefaultCNI: true
  kubeProxyMode: none
EOF

Helmを使って、Ciliumをインストールしていきます。kubeProxyReplacementl2announcements.enabledtrueにする必要があります。また、L2 Announcementを使用するとAPIリクエスト数が増加するため、k8sClientRateLimitを調整する必要があるかもしれません。k8sClientRateLimitについては、「クライアントレート制限のサイズ設定」で説明しています。

$ helm install cilium cilium/cilium --version 1.16.0 \
		--namespace kube-system \
		--set l2announcements.enabled=true \
		--set k8sClientRateLimit.qps=10 \
    --set k8sClientRateLimit.burst=20 \
    --set kubeProxyReplacement=true \
    --set k8sServiceHost=kind-control-plane \
    --set k8sServicePort=6443

Ciliumがインストールされると、以下のようにすべてのPodが正しく動いていることが確認できます。

$ kubectl get pods -A
NAMESPACE            NAME                                         READY   STATUS    RESTARTS   AGE
kube-system          cilium-8zs5c                                 1/1     Running   0          60s
kube-system          cilium-ds55l                                 1/1     Running   0          60s
kube-system          cilium-envoy-4pctr                           1/1     Running   0          60s
kube-system          cilium-envoy-5wprx                           1/1     Running   0          60s
kube-system          cilium-envoy-q8kgn                           1/1     Running   0          60s
kube-system          cilium-envoy-xbphd                           1/1     Running   0          60s
kube-system          cilium-ffvs9                                 1/1     Running   0          60s
kube-system          cilium-jthgk                                 1/1     Running   0          60s
kube-system          cilium-operator-5999b49dd4-9hwfz             1/1     Running   0          60s
kube-system          cilium-operator-5999b49dd4-9sh82             1/1     Running   0          60s
kube-system          coredns-7db6d8ff4d-7mv7b                     1/1     Running   0          85s
kube-system          coredns-7db6d8ff4d-k4mzk                     1/1     Running   0          85s
kube-system          etcd-kind-control-plane                      1/1     Running   0          102s
kube-system          kube-apiserver-kind-control-plane            1/1     Running   0          103s
kube-system          kube-controller-manager-kind-control-plane   1/1     Running   0          102s
kube-system          kube-scheduler-kind-control-plane            1/1     Running   0          102s
local-path-storage   local-path-provisioner-988d74bc-svjp2        1/1     Running   0          85s

クラスタで各Podが正しく動いていることを確認したら、nginxのDeploymentとLoadBalancerタイプのServiceを作成します。本検証では、nginxのPodを3台用意し、各ワーカーノードで動作するようにしています。

$ kubectl apply -f - <<EOF
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx-pod
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx-pod
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.27.0
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx-deployment
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx-pod
  type: LoadBalancer
EOF

この時点でServiceを確認すると、nginx-serviceのEXTERNAL-IPがpendingになっていることが確認できます。

$ kubectl get service
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes      ClusterIP      10.96.0.1       <none>        443/TCP        2m34s
nginx-service   LoadBalancer   10.96.167.240   <pending>     80:32493/TCP   19s

EXTERNAL-IPの設定を行うために、まず、kindのネットワークを確認します。kindは、Dockerコンテナ上にKubernetesクラスタを構築するため、以下のようにしてコンテナのIPアドレスを確認します。本来であればオフィスや家のネットワークアドレスや接続しているPCやサーバといったノードのIPアドレスを確認します。

$ docker network inspect kind
[
    {
        "Name": "kind",
        "IPAM": {
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                },
            ]
        },
        "Containers": {
            "4b03fda298dd4e2dae664e2379eedc05d75f6bf2716930e56c33bdaad40bd026": {
                "Name": "kind-control-plane",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
            },
            "bd63eb3baa3542705468456df8d3dd20008a8c25ab67ba2d37d292fa4dcc94b3": {
                "Name": "kind-worker2",
                "MacAddress": "02:42:ac:12:00:05",
                "IPv4Address": "172.18.0.5/16",
            },
            "bff5c8d88d10f1569649735c54c039783f36462ab5c68deef04702ffd36faa4e": {
                "Name": "kind-worker",
                "MacAddress": "02:42:ac:12:00:06",
                "IPv4Address": "172.18.0.6/16",
            },
            "cebe5d9969dc6e9f6a36e1eeaa21b692a6ae38c366de8feea5365a4675e155d9": {
                "Name": "kind-worker3",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
            }
        },
    }
]

kindで作成したKubernetesクラスタのネットワーク構成を図で示すと、以下のようになります。

CiliumLoadBalancerIPPool

CiliumLoadBalancerIPPoolとは、LoadBalancerタイプのServiceに対して、CiliumがIPアドレスを割り当てて、それを管理する機能(LB IPAM)を使用するためのリソースです[4]

LB IPAMはCiliumをインストールするだけで、常に有効になっており、追加の設定は必要ありません。IPプールが空の場合は、アイドル状態になっており、IPプールが追加されるとコントローラが起動します。

今回作成した環境は、”172.18.0.0/16”のネットワークであるため、クラスタに存在するノードと重複しないIPアドレスである”172.18.0.100”~”172.18.0.200”をEXTERNAL-IPとしてServiceに割り当てるように設定していきます。以下のようにして、CiliumLoadBalancerIPPoolを作成します。

$ kubectl apply -f - <<EOF
apiVersion: "cilium.io/v2alpha1"
kind: CiliumLoadBalancerIPPool
metadata:
  name: "pool"
spec:
  blocks:
  - start: "172.18.0.100"
    stop: "172.18.0.200"
EOF

各フィールドについて

  • blocks CIDR表記(例: 172.18.0.0/16)または、開始IP・終了IPで指定
  • serviceSelector どのサービスがどのプールからEXTERNAL-IPを取得できるかを制限 serviceSelectorが未指定の場合、任意のサービスにプールのIPアドレスを割り当てられる ServiceのLabel, Name, Namespaceで指定可能

※ IPプールにコンフリクトが発生した場合、最後に追加されたプールはConflicting としてマークされ、そのプールからのIPアドレスの割り当ては行われません。そのため、クラスタ管理者は、LB-IPAMを変更した場合には、すべてのプールのステータスを確認する必要があります。

CiliumLoadBalancerIPPoolを作成した後に、Serviceを見ると、”172.18.0.100”というEXTERNAL-IPが割り当てられていることが確認できます。

$ kubectl get service
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
kubernetes      ClusterIP      10.96.0.1       <none>         443/TCP        8m46s
nginx-service   LoadBalancer   10.96.167.240   172.18.0.100   80:32493/TCP   6m31s

これにより、クラスタ内のノードからは、”172.18.0.100”でnginxのPodにアクセスすることができます。

$ docker exec -it kind-worker /bin/bash
root@kind-worker:/# curl 172.18.0.100 -o /dev/null -w '%{http_code}\n' -s
200

次に、クラスタ外からアクセスできるか確認してみます。Dockerでテスト用のコンテナをkindネットワークに作成して、アクセスします。構成は、下の図のようになります。

クラスタ外であるtest-containerからは、EXTERNAL-IPにアクセスできません。クラスタ外からアクセスするためには、次節で説明するCiliumL2AnnouncementPolicyの設定が必要となります。

$ docker run -it --name test-container --net kind ubuntu:24.04 /bin/bash
root@3c88b33fff10:/# curl 172.18.0.100
curl: (7) Failed to connect to 172.18.0.100 port 80 after 3096 ms: Couldn't connect to server

CiliumL2AnnouncementPolicy

CiliumL2AnnouncementPolicyとは、どのサービスをどのノードのどのインターフェースからアナウンスするのかを制御するリソースです[5]

今回は、すべてのサービスをcontrol-plane以外のノードのeth0からアナウンスするように設定するために、以下のようにしてCiliumL2AnnouncementPolicyを作成します。

$ kubectl apply -f - <<EOF
apiVersion: "cilium.io/v2alpha1"
kind: CiliumL2AnnouncementPolicy
metadata:
  name: policy1
spec:
  nodeSelector:
    matchExpressions:
      - key: node-role.kubernetes.io/control-plane
        operator: DoesNotExist
  interfaces:
  - ^eth[0-9]+
  externalIPs: true
  loadBalancerIPs: true
EOF

各フィールドについて

  • serviceSelector
    ポリシーを適用するサービスを指定
    未指定の場合、すべてのサービスが選択される
  • nodeSelector
    ロードバランスする候補となるノードの指定
  • interfaces
    インターフェイスの指定(正規表現で記述)
    未指定の場合、すべてのインターフェイスが選択される
  • externalIPs
    .spec.externalIPsのIPアドレスをアナウンスする
  • loadBalancerIPs
    .status.loadbalancer.ingress のIPアドレスをアナウンスする

ciliuml2announcementpolicies を見ると、policy1が作られていることが確認できます。

$ kubectl get ciliuml2announcementpolicies.cilium.io
NAME      AGE
policy1   9s

kubectl describe ciliuml2announcementpolicies.cilium.io
Name:         policy1
Namespace:
Labels:       <none>
Annotations:  <none>
API Version:  cilium.io/v2alpha1
Kind:         CiliumL2AnnouncementPolicy
Metadata:
  Creation Timestamp:  2024-08-06T07:14:43Z
  Generation:          1
  Resource Version:    3154
  UID:                 1b552650-b3ad-4a97-bca5-f46216ffa937
Spec:
  External I Ps:  true
  Interfaces:
    ^eth[0-9]+
  Load Balancer I Ps:  true
  Node Selector:
    Match Expressions:
      Key:       node-role.kubernetes.io/control-plane
      Operator:  DoesNotExist
Events:          <none>

これにより、クラスタ外からでもEXTERNAL-IPである”172.18.0.100”にアクセスできます。

$ docker run -it --name test-container --net kind ubuntu:24.04 /bin/bash
root@3c88b33fff10:/# curl 172.18.0.100 -o /dev/null -w '%{http_code}\n' -s
200

※ CiliumL2AnnouncementPolicyを削除すれば、すぐにアクセスできなくなるかというとそうではありません。クライアントのARPテーブルにキャッシュとして残っている間は、アクセスすることができます。

以上のように、CiliumLoadBalancerIPPoolCiliumL2AnnouncementPolicyを定義することで、オンプレミスなKubernetesクラスタであってもEXTERNAL-IPが付与され、クラスタ外からロードバランスされてアクセスすることができます。つまり、MetalLB L2モードで実現していたことが可能になります。

仕組み

L2 Announcementの処理は、以下の3つから成ります。

  1. Serviceに付与されたEXTERNAL-IPをアナウンスするノードを選択するリーダ選挙
  2. GARP(Gratuitous ARP)を送って、クライアントのARPテーブルを上書き GARPとはARPヘッダのTarget IPに自身のIPアドレスをセットした特別なARPになります[6]
  3. クライアントからのARP requestに対してARP replyを返す

リーダー選挙

ネットワークにおいて、IPアドレスとMACアドレスは一対一で紐づけられており、クライアント側も1つのIPアドレスに対して、1つのMACアドレスのみを保存します。つまり、特定のIPに対するARP requestに応答するのはクラスタ内の1つのノードのみでなければなりません。

そこで、同じPodが複数ノードで動いている場合には、1つのノードをリーダーとして選出します。DaemonSetとしてデプロイされたCiliumエージェントは、ノードでどのサービスが動いているかを解決し、サービスごとのリーダー選挙に参加し始めます。リーダー選挙は、KubernetesのLeaseを使用します。Lease Holder(リーダー)が決定すると、ARP requestに応答し始めます。

Leaseを確認すると、”kind-worker2”がリーダーとなり、nginx-serviceへのアクセスに応答することが確認できます。

$ kubectl -n kube-system get lease
NAME                                      HOLDER
apiserver-c7uylvfxlbqccnk6myfkwetzze      apiserver-c7uylvfxlbqccnk6myfkwetzze_a2e7f639-97ce-4dba-9ac6-40eb24ce6258
cilium-l2announce-default-nginx-service   kind-worker2
cilium-operator-resource-lock             kind-worker2-lxfh9rm5zg
kube-controller-manager                   kind-control-plane_4bbba870-d4cb-46d3-b292-3d9e4a663b0c
kube-scheduler                            kind-control-plane_f87bb460-8aa0-4995-a325-b02e2a867452

Lease情報を確認すると、spec.acquireTime にリーダーがLeaseを取得した時間、spec.holderIdentity にリーダーとなるノード、spec.leaseDurationSeconds の間にLeaseを更新しない場合には新しいリーダーが選択され、spec.renewTime でリーダーが最後にLeaseを更新した時間がわかります。

$ kubectl -n kube-system get lease/cilium-l2announce-default-nginx-service -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2024-08-06T07:14:43Z"
  name: cilium-l2announce-default-nginx-service
  namespace: kube-system
  resourceVersion: "4446"
  uid: eadd68c9-0989-409d-93d4-17898cda10c0
spec:
  acquireTime: "2024-08-06T07:14:43.068939Z"
  holderIdentity: kind-worker2
  leaseDurationSeconds: 15
  leaseTransitions: 0
  renewTime: "2024-08-06T07:22:59.405420Z"

Leaseに関連する時間は、Helmオプションで指定することができます。

  • l2announcements.leaseDuration
    リーダーとなるノードがダウンしていると判断する時間(デフォルト: 15s)
  • l2announcements.leaseRenewDeadline
    リーダーがLeaseを更新する時間間隔(デフォルト: 5s)
  • l2announcements.leaseRetryPeriod
    Leaseの更新に失敗した場合にCilium エージェントが何秒後に再試行するか(デフォルト: 2s)

すべてデフォルトの設定では、フェイルオーバーは10秒から20秒の間に発生します。

ノードがダウンしていると判断する時間を短くすると、高速に障害検出を行うことができますが、CPUやネットワークのオーバーヘッドが発生するという欠点もあります。クラスタによって、これらのパラメータを調整する必要があります。

クライアントレート制限のサイズ設定

リーダーを選出するにあたり、kube-apiserverに対するAPIリクエスト数が増加します。Ciliumエージェントが発行するAPIリクエストのデフォルトのレート制限は5QPSで、最大10QPSまでのバーストが許可されていますが、L2 Announcementを使用するとすぐにレートを超えてしまいます。そのため、クライアントレート制限の値を調整する必要があります。

QPS(Query Per Secounds)は、Serviceの数とleaseRenewDeadline(リーダーがLeaseを更新する時間間隔)から計算することができます。デフォルトの設定で25個のServiceを動かすとすると、QPSは5になります。Serviceの数が25を超えるか、leaseRenewDeadline をデフォルト値である5sよりも短くする場合には、Helmでk8sClientRateLimit を調整する必要がありそうです。

QPS = #services * (1 / leaseRenewDeadline)
// #services=25, leaseRenewDeadline=5(default)
QPS = 25 * (1 / 5) = 5 QPS

フェイルオーバー

リーダー選挙に参加しているノードは、リーダーが指定されたleaseDurationSeconds の間、Leaseを更新しなかったことを検出すると、API サーバーに新しいリーダーになるように要求します。この要求のうち、APIサーバに最初に要求が到達したノードが次のリーダーとなり、残りの要求はすべて拒否されます。

“kind-worker2”を停止させてみます。すると、spec.holderIdentity が”kind-worker3”になっており、”kind-worker3”がnginx-serviceへのアクセスに応答するようになります。test-containerのARPテーブルも変わっていることが確認できます。

# "kind-worker2"を停止する
$ docker pause kind-worker2

$ kubectl get nodes
NAME                 STATUS     ROLES           AGE   VERSION
kind-control-plane   Ready      control-plane   47m   v1.30.0
kind-worker          Ready      <none>          47m   v1.30.0
kind-worker2         NotReady   <none>          47m   v1.30.0
kind-worker3         Ready      <none>          47m   v1.30.0

$ kubectl -n kube-system get lease/cilium-l2announce-default-nginx-service -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2024-08-06T07:14:43Z"
  name: cilium-l2announce-default-nginx-service
  namespace: kube-system
  resourceVersion: "7867"
  uid: eadd68c9-0989-409d-93d4-17898cda10c0
spec:
  acquireTime: "2024-08-06T07:43:57.057020Z"
  holderIdentity: kind-worker3
  leaseDurationSeconds: 15
  leaseTransitions: 1
  renewTime: "2024-08-06T07:44:55.312089Z"

# "test-container"でARPテーブルを確認する
# MACアドレスについては、環境構築のネットワーク図を参照
# kind-worker2 02:42:1c:12:00:05, kind-worker3 02:42:ac:12:00:04
$ docker run -it --name test-container --net kind ubuntu:24.04 /bin/bash

# kind-worker2停止前
root@3c88b33fff10:/# arp -a | grep 172.18.0.100 
? (172.18.0.100) at 02:42:ac:12:00:05 [ether] on eth0

# kind-worker2停止後
root@3c88b33fff10:/# arp -a | grep 172.18.0.100
? (172.18.0.100) at 02:42:ac:12:00:04 [ether] on eth0 

GARPの送信

あるノードがリーダーになると、設定されているすべてのインターフェイスに Gratuitous ARP(GARP)をブロードキャストで送信し、クライアントはARPテーブルを更新して、次からのPodへのhttpリクエストは新しいノードに送信されます。

Gratuitous ARPは ARPスプーフィングに使用される可能性があるため、すべてのクライアントがこれを受け入れるわけではありません。GARPを受け入れないクライアントはARPテーブルのTTLに達した場合にのみARPリクエストを実行するため、Leaseで設定されているよりも長いダウンタイムが発生する可能性があります。

Linuxでは、カーネルパラメータarp_accept によって設定でき[7]、デフォルトではGARPを受け取っても、ARPテーブルを変更しないようになっています。

$ docker run -it --name test-container --rm --net kind ubuntu:24.04 /bin/bash
root@311cd13c8bc6:/# sysctl -n net.ipv4.conf.eth0.arp_accept
0 # ARPテーブルを更新しない

クラスタを作成してすぐの状態では、以下のようにIPアドレス”172.18.0.100”は、”kind-worker2”になっていました。

その後、フェイルオーバーで説明したように、”kind-worker2”を停止すると、”172.18.0.100はkind-worker3です”というGratuitous ARPが送信され、kind-worker3にリクエストが到達するようになります。NodePortであれば、このようなフェイルオーバ機能はありません。

実装

L2 Announcementsの実装は、#25471のPull requestsで確認することができます。

オレンジが新たに追加したもの、青色が既存で変更されていないもの、緑色が既存で変更したもの、灰色が将来的に追加する予定のものを表しています。

L2 announcement #25471

まず、pkg/l2announcer/l2announcer.go run()L2-Announcer Cellに該当し、CRDリソースやServicesリソースからイベントを受け取ります。リーダー選挙など様々な処理をしたのちに、recalculateL2EntriesTableEntries()Desired state DBを更新します。

次に、pkg/datapath/l2responder/l2responder.gocycle() L2 Responderに該当し、Desired state DBの変更を監視して処理を行います。新しいエントリを追加する場合には、garpOnNewEntry() を呼び出しており、最終的に /datapath/garp/garp.gosend() で、GARPパケットを送信します。これは、上記の図のARP senderに該当します。

その後、pkg/maps/l2respondermap/l2_responder_map4.goCreate() L2 responder Map Cellに該当し、L2 responder map v4に新しいエントリを追加します。L2 responder map v4はBPF mapであり、bpf/bpf_host.chandle_l2_announcement() でL2_RESPONDER_MAP4からデータを取得してARP replyを返すようになっています。handle_l2_announcement() が上記のeBPF ARP responderに該当します。BPF mapとは、カーネル空間で処理を実行するeBPFにおいてカーネルとユーザー空間の間でデータを共有する仕組みであり[8]、L2 Announcementでいうと、ユーザー空間で動作するpkg/maps/l2respondermap/l2_responder_map4.go とカーネル空間で動作する bpf/bpf_host.c でデータを共有するために使用されています。

先ほどの”kind-worker2”がダウンしたことを例とすると、まず、Lease Client APIでイベントが発生し、次のリーダーとして”kind-worker3”が選ばれて、Desired state DBに追加され、最初に1度だけ”kind-worker3”からGARPパケットを送信して、2回目以降は”kind-worker3”のカーネル空間で動作するeBPFプログラムでARP replyを返すようになっています。

まとめ

本記事では、Cilium v1.14からβ機能として利用可能になったL2 Announcementを使うことで、MetalLBのLayer2モードを置き換え可能であることを紹介しました。

また、それを実現する仕組みについて取り上げました。これを利用することで、CNIにCiliumを使っている場合には、MetalLBを使う必要がなくなります。Cilium + MetalLBで環境を構築しているのであれば、L2 Announcementを検討してみるのもいかがでしょうか。

参照

  1. Google Cloud. GKEのロードバランスについて理解する
    https://cloud.google.com/kubernetes-engine/docs/concepts/service-networking?hl=ja#understanding-load-balancing
  2. Cilium 1.14 Release
    https://isovalent.com/blog/post/cilium-release-114/
  3. ISOVALENT Migrating from MetalLB to Cilium
    https://isovalent.com/blog/post/migrating-from-metallb-to-cilium/
  4. Cilium LB IPAM
    https://docs.cilium.io/en/stable/network/lb-ipam/#lb-ipam
  5. Cilium L2 Announcement
    https://docs.cilium.io/en/stable/network/l2-announcements/
  6. GARPとは(Gratuitous-ARP 機能・動作と利用場面)
    https://infrastructure-engineer.com/arp-gratuitous-001/#index_id3
  7. The Linux Kernel Archives ip-sysctl.txt
    https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt
  8. Linux kernel BPF map
    https://www.kernel.org/doc/html/latest/bpf/maps.html

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ