Edited at

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

More than 1 year has passed since last update.


背景

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テーブルを更新する。


Version 7.0.x以降



v7.0.x以降では基本的なアーキテクチャは同じだが、クラスタ全体でのLeaderの選出をやめ、ServiceごとにLeaderを選出するようになった。



上記の変更により、複数Serviceを建てている場合に多少スケールするようになった。

また、L2 modeでもexternalTrafficPolicyをLocalにすることができるようになった。(Leaderは該当ServiceのPodが動いているノードから選出される)


参考