オンプレミスKubernetesにおける、あの「<pending>」状態の悩み
ベアメタルKubernetesクラスターの構築に何時間も費やしたとしましょう。ラックに積まれたDell PowerEdgeのスタックであろうと、デスク上の3台のRaspberry Pi 5であろうと、セットアップは完璧に思えます。最初のNginxアプリをデプロイし、LoadBalancerサービスを作成して、kubectl get svcを実行します。そして待ちます。しかし、EXTERNAL-IPカラムは頑なに<pending>を表示したままです。5分が経過し、10分が過ぎても、一向に変わる気配がありません。
これは、すべてのエンジニアが「ベアメタルKubernetesはクラウドとは挙動が異なる」と痛感する瞬間です。AWS EKSやGoogle GKEでは、クラウドプロバイダーが自動的にNetwork Load Balancer (NLB)を立ち上げ、その分の料金が請求されます。しかし、セルフマネージドな環境では、そのような自動化は存在しません。Kubernetesには、ベアメタル用のネットワークロードバランサーは標準搭載されていないのです。外部コントローラーがなければ、クラスターはローカルルーターと通信してIPアドレスを取得する術を持ちません。
このネットワークのギャップを克服することは、本番グレードのオンプレミス環境を構築する上で不可欠です。NodePortのような回避策に頼ると、インフラが脆弱に感じられるだけでなく、80番や443番といった標準ポートを期待するエンドユーザーにとっても不自然に見えてしまいます。
根本的な原因:クラウドコントローラーの不在
この問題は、**Cloud Controller Manager (CCM)**が欠けていることに起因します。マネージド環境では、CCMがKubernetes APIとプロバイダーのハードウェアの橋渡しをします。LoadBalancerをリクエストすると、CCMがAPIを呼び出し、パブリックIPを取得して、トラフィックをノードに送信するようにプロバイダーのルーターを設定します。
ベアメタルには、呼び出すべき基盤APIが存在しません。Kubernetesは、ARP (Address Resolution Protocol) や BGP (Border Gateway Protocol) を介して、ネットワークに対して自分自身を「通知」する方法を知りません。クラスターがホームルーターやオフィスのスイッチに対して、特定のIPを「所有している」と伝えられないため、サービスコントローラーは届くはずのない信号を待ち続けることになるのです。
なぜ一般的な回避策では不十分なのか
適切な修正方法を実装する前に、なぜ他の手法がプロフェッショナルな環境において不十分なのかを見ていきましょう。
1. NodePort:不格好な解決策
NodePortは、すべてのノードで高い番号のポート(30000〜32767)を開放します。動作はしますが、スマートではありません。https://myapp.com:31045のようなURLにアクセスしたいユーザーはいないでしょう。さらに、DNSが指している特定のノードがダウンすると、クラスターの他の部分が正常であっても、サービス全体が利用不能になってしまいます。
2. ExternalIPs:手動管理の悪夢
サービスにexternalIPsフィールドを手動で割り当てることができます。これにより、トラフィックがノードに到達した場合にそのIPを受け入れるようKubernetesに指示できます。しかし、そのトラフィックをルーティングするために外部ネットワークを手動で設定する必要があります。これは、サービスが増えるにつれてスケールしにくく、メンテナンスの負担が大きくなります。
3. MetalLB:業界標準の解決策
MetalLBは、ベアメタル用に特別に設計されたネットワークロードバランサーです。標準的なルーティングプロトコルを使用したソフトウェアベースの実装を提供します。定義したプールからIPを「通知」することで、クラウドのロードバランサーの動作を効果的に模倣します。これは、ローカルサービスで実際のIPアドレスを取得するための最も堅牢な方法です。
MetalLBの実装:ステップバイステップのアプローチ
MetalLBはKubernetesのライフサイクルとネイティブに統合されます。タイプがLoadBalancerであるサービスを監視し、事前設定された範囲から即座にIPを割り当てます。
ステップ1:Strict ARPモードを有効にする
パフォーマンス上の理由から10ノード以上のクラスターで標準的な**kube-proxy**のIPVSモードを使用している場合、strict ARPを有効にする必要があります。これにより、MetalLBがARPリクエストに正しく応答できるようになります。次のコマンドで設定を編集します:
kubectl edit configmap -n kube-system kube-proxy
YAML構造内の strictARP を true に設定します:
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
strictARP: true
ステップ2:MetalLB Operatorのインストール
バージョン管理にはHelmが優れていますが、公式のマニフェストを使用するのが最も手っ取り早い方法です。バージョン0.14.3時点では、次の1つのコマンドでコントローラーとスピーカーコンポーネントをデプロイできます:
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.3/config/manifests/metallb-native.yaml
controllerがIPの割り当てを処理し、speaker(DaemonSet)が各ノードでの実際のネットワークプロトコルを処理します。
ステップ3:IPプールの定義
MetalLBには専用のIP範囲が必要です。これらはローカルサブネット内にあり、かつルーターのDHCP範囲外である必要があります。例えば、ルーターが .2 から .150 までのIPを割り当てている場合、Kubernetes用には .200 から .250 を使用するとよいでしょう。
metallb-config.yaml を作成します:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: main-static-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.200-192.168.1.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: l2-adv
namespace: metallb-system
spec:
ipAddressPools:
- main-static-pool
適用します: kubectl apply -f metallb-config.yaml
ステップ4:結果の確認
テストサービスをデプロイして、動作を確認しましょう。Nginxのデプロイメントが準備できている場合は、以下を実行します:
kubectl expose deployment nginx --port=80 --type=LoadBalancer
ステータスを確認します: kubectl get svc。数秒以内に <pending> ステータスが消え、192.168.1.200 のようなIPに置き換わるはずです。これで、ネットワーク内の任意のブラウザからそのIPにアクセスできるようになります。
レイヤー2 vs BGP:適切なモードの選択
選択は、お使いのハードウェアと予想されるトラフィック量によって決まります。
レイヤー2モード(ホームラボに最適)
レイヤー2モードでは、1つのノードが「選挙」に勝ち、サービスのIPをホストします。そのアドレスに対するARPリクエストに応答します。シンプルで、一般的なスイッチやルーター(TP-LinkやAsusなど)で動作します。欠点は、特定のサービスへのすべてのトラフィックが最初に1つのノードに集中することです。小規模なオフィスには最適ですが、高帯域幅のアプリではマイナーなボトルネックになる可能性があります。
BGPモード(本番環境での選択肢)
BGPは「プロ」の選択肢です。各ノードがネットワークルーターとのピアリングセッションを確立します。サービスが開始されると、すべてのノードがトラフィックを処理できることをルーターに伝えます。その後、ルーターは等コストマルチパス(ECMP)を使用して、ハードウェアレベルで全ノードにトラフィックを分散させます。これには、Ubiquiti EdgeRouter、MikroTik、pfSenseなどのBGPをサポートするルーターが必要です。
現場からの教訓
さまざまなデータセンターでMetalLBをデプロイしてきた経験から、従うべきいくつかの重要なルールを見つけました:
- IPをDHCPから除外する: ルーターがラップトップに
192.168.1.201を割り当てている一方で、MetalLBがそれをIngressに割り当ててしまうと、接続が断続的に切断されるようになります。これはデバッグにおいて悪夢です。必ずルーターの設定で手動の除外設定を行ってください。 - ノードには固定IPを使用する: DHCPによってノードのプライマリIPが変わると、スイッチ上のARPテーブルが破損する可能性があります。Kubernetesのワーカーノードには必ず固定IPを割り当ててください。
- IPリソースは有限: 50個のマイクロサービスに50個の異なるLoadBalancer IPを割り当てないでください。MetalLBを使用して、Ingress Controller(Nginx IngressやTraefikなど)に1つのIPを割り当てます。その後、ホスト名(例:
api.example.com)を使用して内部的にトラフィックをルーティングします。 - ARPタイムアウトに注意: レイヤー2モードでは、ノードが故障すると別のノードがIPを引き継ぎます。安価なコンシューマー向けルーターでは、ARPキャッシュの更新に30〜60秒かかることがあり、フェイルオーバー中に短時間の通信断が発生する可能性があります。
まとめ
ベアメタルへの移行が、利便性の低下を感じさせるものであってはなりません。MetalLBは物理ハードウェアとKubernetes APIの間のギャップを埋め、クラウドのようなシームレスな体験を提供します。<pending> 状態を脱却すれば、オンプレミスのクラスターはようやくプロフェッショナルな環境として機能し始めます。ベアメタルの制御性と、クラウドの自動化を同時に手に入れることができるのです。

