オンプレK8sで使えるGoogle製External Load Balancer: MetalLB

背景

GCPやAWS上のK8sではtype LoadBalancerとか書けばいい感じに外部からアクセスできるIPがアサインされるが、これはcloud-controllerという仕組みで(K8s内部ではなく)そのクラウドサービス上のLBaaSと連携して実現されている。
そのため、自分でオンプレなどにK8sを構築しようとすると普通LBaaSがないので、外部からK8sのアクセスをどうしようという悩みがよく起きる。

MetalLBとは

Googleが作ったベアメタルK8s環境でもつかえるExternal Load Balancer実装。

クラウド上のK8sと同じ間隔でオンプレでもtype LoadBalancerを使うことができる。

インストール方法

すでにK8s環境があれば一行で入る。

kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.5.0/manifests/metallb.yaml

コンフィグ

MetalLBにはBGPモードとL2モードがある。

BGPモード

BGPモードではK8sクラスタの上流にBGPが喋れるルータが必要になる。
そのルータのピア情報と払い出すExternal IPのプールの設定を入れる。

bgp.yml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    peers:
    - my-asn: 64501
      peer-asn: 64500
      peer-address: 172.30.8.35
    address-pools:
    - name: my-ip-space
      protocol: bgp
      avoid-buggy-ips: true
      addresses:
      - 198.51.100.0/24
kubectl apply -f bgp.yml

L2モード

l2.yml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 192.168.1.240/28
kubectl apply -f l2.yml

使ってみる

nginx.yml
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1
        ports:
        - name: http
          containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  type: LoadBalancer
kubectl apply -f nginx.yml
$ kubectl get service
NAME         TYPE           CLUSTER-IP   EXTERNAL-IP    PORT(S)        AGE
nginx        LoadBalancer   10.99.4.87   198.51.100.1   80:31263/TCP   1h

EXTERNAL-IPがついてる、すごい。

アーキテクチャ

BGPモード


MetalLBをインストールするとDaemonSetを使用して、すべてのノードでspeakerというコンテナがホストネットワークモードで動き出す。
これはtype LoadBalancerなServiceがデプロイされた際にそのExternal IPをピアを張っているルータにnext-hopが自分であるように広告する。

同時にkube-proxyが各ノードのiptablesにreplicaに応じた確率でExternal IPからコンテナIPにNATされるように設定を書き込む。

結果、ルーターはExternal IPへの経路をノードの数だけもち、ECMPでルータ→各ノード間はロードバランシングされる。
そしてExternal IPをもらった各ノードはiptablesでさらにロードバランスされる。

一見、2段のロードバランスは無駄に見えるが、これはノード:コンテナが必ずしも1:1でないため。
デフォルトでは、上図のように偏った状態でも各コンテナに均等にロードバランスするために2段構成になっている。

2段目のノード間をまたぐ通信をやめたい場合にはTrafficPolicyをLocalにすることでiptablesからのロードバランス先を自分のノード上にあるコンテナのみに制限することができるが、その場合均等にロードバランスされなくなる場合が出てくることになる。(上図だと左のコンテナから33.3%, 33.3%, 16.7%, 16.7%のようになる)

この構成のいいところはBGPの喋れるルータがあるだけで使え、フォワーディング性能の限界まで水平にスケールアウトできることである。
しかも全ノードをACTとして使え、ノード障害時には自動的にBGPのピアから外れてルーティングされなくなるため、DNSのレコードから消すような操作もいらない。

注意点

コンテナネットワークをオーバレイで作ってる場合は問題ないが、同じくBGPを使うCalicoで構築している場合はコンフリクトするため、いくつかワークアラウンドが紹介されている。
https://metallb.universe.tf/configuration/calico/

L2モード


L2モードではLeaderに選定されたノードのspeakerがExternal IPのARP要求に対して、自分のノードのMACアドレスを応答する。

あとはBGPモードと同様。
見ての通りトラフィックがLeaderノードに集中してしまい、LB自体のスケールはできないが、手軽に構築できる。
障害時にはLeaderが再選出され、Gratuitous ARPでL2のARPテーブルを更新する。

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.