2026年6月4日

2026年6月4日

MetalLB の構築手順|オンプレミスのKubernetesでLoadBalancerにIPを割り当てる

オンプレミスや自宅ラボの Kubernetes で type: LoadBalancer の Service を作ると、EXTERNAL-IP がいつまでも <pending> のままになります。クラウドのようにロードバランサーを払い出す仕組みが無いためで、MetalLB はその役割を担うコンポーネントです。この記事では、MetalLB をインストールし、IP アドレスプールを定義して、L2 モードで Service に外部 IP が付くところまでを、つまずきやすい点と確認方法を添えて説明します。

MetalLB が解決すること

Kubernetes 本体は「LoadBalancer 型の Service には外部 IP を付ける」という約束だけを定め、実際に IP を用意する処理はクラウドプロバイダーの実装に任せています。物理サーバーや VM で組んだクラスターにはその実装が無いので、外部 IP が決まりません。

MetalLB は次の 2 つを受け持ちます。

  • アドレスの割り当て: 管理者があらかじめ決めた IP の範囲から、Service に外部 IP を払い出します(controller が担当)。
  • アドレスの広報: その IP 宛ての通信がクラスターのノードに届くよう、ネットワークに知らせます(各ノードの speaker が担当)。

広報の方式は主に 2 つです。

  • L2 モード: ARP(IPv6 では NDP)で「この IP は自分のノードにいる」と応答します。ルーター側の設定が不要で、最初の構築に向いています。ただし、ある IP の通信は 1 台のノードがまとめて受けるため、ノード間で負荷が分散されるわけではありません。分散されるのは、そのノードから先の Pod への振り分けです。
  • BGP モード: 上流ルーターと BGP ピアを張り、経路を広報します。ルーターが ECMP に対応していれば複数ノードへ分散できますが、ネットワーク側の設計と調整が必要です。

なお、AWS や GCP などのパブリッククラウドでは、ARP 応答による IP の引き継ぎがネットワーク側で受け付けられないことが多く、MetalLB は基本的に向きません。クラウドではそのクラウドのロードバランサーを使ってください。

構築前に確認すること

使える IP 範囲を決める

L2 モードでは、ノードと同じセグメント(同じ L2 ネットワーク)にある、どこにも使われていない IP 範囲が必要です。DHCP サーバーの払い出し範囲と重なると、別の機器と IP が衝突して通信が不安定になります。ルーターや DHCP サーバーの設定画面で払い出し範囲を確かめ、その外側を MetalLB 用に確保してください。

kube-proxy のモードを確認する

kube-proxy を IPVS モードで動かしている場合は、strictARP を有効にする必要があります。無効のままだと、各ノードが仮想 IP に対する ARP に応答してしまい、どのノードに通信が届くか定まりません。iptables モードなら変更は不要です。

# 現在のモードを確認(mode: "ipvs" なら要対応)
kubectl get configmap kube-proxy -n kube-system -o yaml | grep -E 'mode|strictARP'

# 編集して ipvs セクションの strictARP を true にする
kubectl edit configmap kube-proxy -n kube-system

設定を変えたら kube-proxy の Pod を再作成して反映させます。kubeadm 以外で構築したクラスターでは kube-proxy の設定場所が異なることがあるので、使っているディストリビューションの説明を確認してください。

既存のロードバランサー実装と競合させない

k3s には ServiceLB(klipper-lb)が標準で入っています。MetalLB と同時に動かすと同じ Service を両方が扱おうとするため、k3s の起動オプションで ServiceLB を無効にしてから MetalLB を入れます。ほかのディストリビューションでも、同じ役割のアドオンが有効になっていないか確かめてください。

MetalLB をインストールする

導入方法は、公式のマニフェストを適用する方法と Helm を使う方法があります。バージョンは頻繁に更新されるため、公式サイトまたは GitHub のリリース一覧で最新の安定版を確認し、下の vX.Y.Z を置き換えてください。手順は v0.13 以降の CRD による設定方式を前提にしています(2026年10月現在)。

# マニフェストで導入する場合
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/vX.Y.Z/config/manifests/metallb-native.yaml

# Helm で導入する場合
helm repo add metallb https://metallb.github.io/metallb
helm repo update
helm install metallb metallb/metallb -n metallb-system --create-namespace

導入後、Pod が起動しているかを確認します。

kubectl get pods -n metallb-system -o wide

controller が 1 つ、speaker がノードの数だけ Running になっていれば正常です。speaker はホストのネットワークを直接使うため、Pod Security Admission を厳しく設定しているクラスターでは起動が拒否されることがあります。その場合は metallb-system 名前空間に必要な許可が付いているかを確認してください。

IP アドレスプールと L2 広報を設定する

MetalLB v0.13 以降は、ConfigMap ではなくカスタムリソース(CRD)で設定します。古い記事にある ConfigMap 形式の設定は現在のバージョンでは使えないので注意してください。

# metallb-config.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: lan-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.10.200-192.168.10.220
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: lan-l2
  namespace: metallb-system
spec:
  ipAddressPools:
    - lan-pool
kubectl apply -f metallb-config.yaml
kubectl get ipaddresspools,l2advertisements -n metallb-system

IPAddressPool は「払い出してよい IP の範囲」、L2Advertisement は「そのプールを L2 モードで広報する」という指定です。プールを作っただけで L2Advertisement が無いと、IP は割り当てられるのに外から届かない、という状態になります。最初に起きやすいつまずきなので、両方がそろっているかを確認してください。

CRD の適用時に webhook 関連のエラーが出る場合は、controller の起動が終わっていないことがよくあります。少し待ってから再度適用してください。

動作を確認する

テスト用に nginx をデプロイし、LoadBalancer 型の Service で公開します。

kubectl create deployment web-test --image=nginx
kubectl expose deployment web-test --port=80 --type=LoadBalancer
kubectl get svc web-test

EXTERNAL-IP にプール内の IP が表示されたら、割り当ては成功です。続いて、クラスター外の同じセグメントにある端末からアクセスします。

curl -I http://192.168.10.200/

# どのノードが ARP に応答しているかを確認(Linux 端末の例)
arping -c 3 192.168.10.200

EXTERNAL-IP が付いているのに外から届かない場合は、speaker のログを確認します。どのノードがその IP を担当しているか、ARP への応答に失敗していないかが分かります。

kubectl logs -n metallb-system -l component=speaker --tail=100
kubectl describe svc web-test

kubectl describe svc の Events 欄には、IP の割り当てやどのノードから広報しているかが記録されます。原因の当たりを付けるには、まずここを見るのが早道です。

運用で気をつけること

クライアントの IP を残したい場合

既定の設定では、受けたノードから別ノードの Pod へ転送される際に送信元 IP が書き換わり、アプリ側のログにはノードの IP が残ります。WordPress のアクセスログやログイン試行の制限でクライアント IP を使う場合は、Service の externalTrafficPolicy: Local を検討してください。この設定では Pod がいるノードだけが応答するので送信元 IP は保たれますが、Pod の配置に偏りがあると負荷も偏ります。

特定の IP を固定したい場合

DNS に登録する IP は固定しておきたいものです。MetalLB は Service のアノテーションで使う IP を指定できますが、アノテーションの名前はバージョンによって案内が変わっているため、使うバージョンのドキュメントで確認してください。Kubernetes の spec.loadBalancerIP は非推奨になっているので、新しく書く設定では避けるのが無難です。

フェイルオーバーの挙動を把握する

L2 モードで担当ノードが落ちると、別のノードが IP を引き継ぎます。切り替わりには数秒程度かかることがあり、その間は通信が途切れます。さらに、クライアントや上流機器が古い ARP キャッシュを持ち続けると復旧が遅れます。メンテナンスでノードを止める前には、どのノードが IP を担当しているかを確認しておくと安心です。

Web サイトを公開するときの構成

複数のサイトや TLS 証明書を扱う場合、アプリごとに LoadBalancer を作るより、Ingress コントローラーの Service だけを MetalLB で公開し、ホスト名による振り分けと TLS 終端は Ingress 側で行う構成が管理しやすくなります。外部 IP は 1 つで済み、証明書の更新も一か所にまとまります。

本番環境で設定を変える前には、Service やカスタムリソースの YAML をバックアップし、まず検証用のクラスターで同じ手順を試してください。

まとめ

  • MetalLB は、オンプレミスの Kubernetes で LoadBalancer 型 Service に外部 IP を割り当て、ネットワークに広報する仕組みです。
  • 構築前に、DHCP と重ならない IP 範囲の確保、IPVS モード時の strictARP、既存のロードバランサー実装との競合を確認します。
  • v0.13 以降の設定は IPAddressPool と L2Advertisement(BGP の場合は BGPPeer など)の CRD で行い、両方がそろって初めて外から届きます。
  • 動作確認は kubectl get svc の EXTERNAL-IP、外部端末からの curl と arping、speaker のログと Service の Events の順に見ると原因を絞り込めます。
  • 送信元 IP を残したいときは externalTrafficPolicy: Local を検討し、Web サイトの公開は Ingress コントローラーと組み合わせると運用しやすくなります。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲