はじめに
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]。
その一方、オンプレミスで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をインストールしていきます。kubeProxyReplacement
とl2announcements.enabled
をtrue
にする必要があります。また、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テーブルにキャッシュとして残っている間は、アクセスすることができます。
以上のように、CiliumLoadBalancerIPPoolとCiliumL2AnnouncementPolicyを定義することで、オンプレミスなKubernetesクラスタであってもEXTERNAL-IPが付与され、クラスタ外からロードバランスされてアクセスすることができます。つまり、MetalLB L2モードで実現していたことが可能になります。
仕組み
L2 Announcementの処理は、以下の3つから成ります。
- Serviceに付与されたEXTERNAL-IPをアナウンスするノードを選択するリーダ選挙
- GARP(Gratuitous ARP)を送って、クライアントのARPテーブルを上書き GARPとはARPヘッダのTarget IPに自身のIPアドレスをセットした特別なARPになります[6]。
- クライアントからの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で確認することができます。
オレンジが新たに追加したもの、青色が既存で変更されていないもの、緑色が既存で変更したもの、灰色が将来的に追加する予定のものを表しています。
まず、pkg/l2announcer/l2announcer.go の run() がL2-Announcer Cellに該当し、CRDリソースやServicesリソースからイベントを受け取ります。リーダー選挙など様々な処理をしたのちに、recalculateL2EntriesTableEntries() でDesired state DBを更新します。
次に、pkg/datapath/l2responder/l2responder.go の cycle() がL2 Responderに該当し、Desired state DBの変更を監視して処理を行います。新しいエントリを追加する場合には、garpOnNewEntry() を呼び出しており、最終的に /datapath/garp/garp.go の send() で、GARPパケットを送信します。これは、上記の図のARP senderに該当します。
その後、pkg/maps/l2respondermap/l2_responder_map4.go の Create() がL2 responder Map Cellに該当し、L2 responder map v4に新しいエントリを追加します。L2 responder map v4はBPF mapであり、bpf/bpf_host.c の handle_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を検討してみるのもいかがでしょうか。
参照
- Google Cloud. GKEのロードバランスについて理解する
https://cloud.google.com/kubernetes-engine/docs/concepts/service-networking?hl=ja#understanding-load-balancing - Cilium 1.14 Release
https://isovalent.com/blog/post/cilium-release-114/ - ISOVALENT Migrating from MetalLB to Cilium
https://isovalent.com/blog/post/migrating-from-metallb-to-cilium/ - Cilium LB IPAM
https://docs.cilium.io/en/stable/network/lb-ipam/#lb-ipam - Cilium L2 Announcement
https://docs.cilium.io/en/stable/network/l2-announcements/ - GARPとは(Gratuitous-ARP 機能・動作と利用場面)
https://infrastructure-engineer.com/arp-gratuitous-001/#index_id3 - The Linux Kernel Archives ip-sysctl.txt
https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt - Linux kernel BPF map
https://www.kernel.org/doc/html/latest/bpf/maps.html