MicroK8s+MetalLBでロードバランサの動きを理解する

## まえがき

前回の記事で作成した3ノードのK8sクラスタをより実運用に向けとするため、 MetalLB を導入し、負荷分散と外部アクセス環境の構築を行う
構築の手順、構築してみて解ったことなどをまとめる

MetalLBには `Layer 2 mode` と `BGP mode` の2つのモードがあるが、 **BGP mode** で構築を行う
※BGP mode を使用するには、上流にBGPを喋れるルータが必要

https://qiita.com/syamamura/items/ab31ddfe4133dcf298bf

何番煎じかはわからないが、MetalLB, BGP の仕組みも交えて解説をする


![IMG_5029.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149815/82da0c32-9bbf-7b69-31f8-dfa07ea2b4ad.jpeg)

*検証に使用する3台の物理マシン(白いタワーPC)*


## MetalLB (BGP mode) の仕組み

### 1. 前提条件 - 構成

前回クラスタ構築完了時点では、K8sノードはClientと同じセグメントに属していたが、新たにVLANを切り、別のネットワークセグメントへと切り出し、図の構成となった

![bgp-nexthop-構成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149815/1cd36674-b987-9c94-e29f-f323a23fdea9.png)


### 2. 経路情報の広告

MetalLB (BGP mode) では、[BGP (Border Gateway Protocol)](https://ja.wikipedia.org/wiki/Border_Gateway_Protocol) を使用して、`metallb:controller` が払い出す ExternalIP セグメントを `metallb:speaker` が BGP Router へ広告することで動的なルーティングを実現するミドルウェアである
BGPをルーティングに使用することで特別な機器を必要とせず、L3のみでロードバランサのような振る舞いをさせることができる

#### 経路広告の流れは以下の通りである

1. controller が Service へ ExternalIP の払い出しを行う
1. controller が speaker へ 払い出しを通知
1. speaker が 上流の BGP Router へ 経路情報を広告

![bgp-nexthop-bgp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149815/743c0536-d364-0afe-7476-e0b34fd939d8.png)


### 3. next hop による負荷分散

経路情報が広告され、ExternalIP への接続要求があるとルータは BGP table (next hop) に記載されたノードへパケットを通す
論理的には青い点線で描かれた、 `ipvs` へとセッションが張られる
ipvs は自ノードもしくは他ノードで Listen している pod へパケットを転送する

2段構えのロードバランサとなっているが、この仕組みがあることで、 ノードレベルでの負荷分散とノードと BGP 間の経路情報を単純化している
ルータは next hup に指定された対向へパケットを通すだけなので、接続を受けたノードに pod が居ない可能性は十分に考えられる。そこで、 ipvs が接続を受け、クラスタ内の適切な pod へパケットを転送することで、pod レベルの負荷分散と障害耐性を実現する

#### クライアントからの通信の流れは以下の通りである

1. クライアントが service (`172.16.x.x`) へ接続要求
1. BGP ルータが next hop に指示されたIPノードへパケットを通す
1. ipvs がセッションを受け、いずれかのノードで動く pod へ転送
1. 元の経路をたどり、 pod がクライアントへ接続を返す

![bgp-nexthop-next hop.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149815/246fb15c-f4c6-dda3-8224-fc8e700fbf08.png)


## MetalLB 導入

MicroK8sの場合、 いずれかのノードで add-on を有効にするだけで使用可能な状態となる

```shell
$ sudo microk8s.enable metallb
```

## 設定を行う

### ルータ側設定

ルータには Sophos XG Firewall を使用している
VyOS を使用した構築例[^1]や EdgerouterX を使用した構築例[^2] なども参考とした

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149815/9c1fc24f-9834-bffb-ceb9-ea68dd30badc.png" width="480px">

ルータID にルータのIPアドレス、ローカルAS には `64512 - 65534` [^3] から採番し入力 `64512` とした。
ネイバーには対向(ノード)のIPアドレスとルータ同様、AS番号の採番を行うが ルータから1インクリメントした `64513` とした。


### K8s側設定

[MetalLB 公式ドキュメント](https://metallb.universe.tf/configuration/)の例をベースに、設定ファイルを作成した。

最小構成としては、以下の内容で機能する

```yaml:metallb-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    peers:
    - peer-address: 10.227.0.1
      peer-asn: 64512
      my-asn: 64513
    address-pools:
    - name: default
      protocol: bgp
      addresses:
      - 172.16.192.0/20
```

適用は `kubectl apply` で行う

```shell
$ kubectl apply -f metallb-config.yaml
```

#### yaml の内容について

- `data.config.peers.peer-address` は 対向(上流ルータ)のIPアドレス
- `data.config.peers.peer-asn` は 対向(上流ルータ)のAS番号
- `data.config.peers.my-asn` は 自分(ノード)に割り当てるAS番号
- `data.config.address-pools.protocol` は ロードバランシングに使用するプロトコル `"layer2"` または `"bgp"` を指定
- `data.config.address-pools.address` は service に割り当てる IPアドレスのプール範囲指定


## 動作確認

### BGP セッションの確認

XG Firewall に限らず、ルータにはBGPの状態確認ができるコマンドがある
設定が正常に完了すると以下のような出力がされる

```
BGP router identifier 10.227.0.1, local AS number 64512
RIB entries 7, using 448 bytes of memory
Peers 3, using 7452 bytes of memory

Neighbor        V    AS MsgRcvd MsgSent   TblVer  InQ OutQ Up/Down  State/PfxRcd
192.168.11.11   4 64513    7352    7365        0    0    0 2d13h04m        3
192.168.11.12   4 64513    6145    6174        0    0    0 16:34:39        3
192.168.11.13   4 64513    7352    7368        0    0    0 2d13h04m        3

Total number of neighbors 3
```

`Up/Down` や `never` や `down`、 `MsgRcvd`, `MsgSent` が `0` となっている場合は、BGPセッションに失敗している
AS番号やIPアドレスの確認、ゾーンポリシーやファイアウォールなどの確認を行う

小生もルータ側のゾーンポリシー設定で、 dynamic routing を Allow にしておらず小一時間ハマった。
(ノード側のサブネットも間違っていた…)


#### 確認用サービス (nginx) を立ち上げる

唐突だが、metallb:speaker の経路広告がルータの BGP Table に反映されるか、Clientからの疎通確認を行うために、nginx のサービスを立ち上げる

https://metallb.universe.tf/usage/

```yaml:nginx.yaml
apiVersion: apps/v1
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
```

`spec.replicas` は適宜書き換える

適用は `kubectl apply` で行う

```shell
$ kubectl apply -f nginx.yaml
```

service と pods の状態は以下のコマンドで確認できる

```shell
$ kubectl get svc,po

NAME                    TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
service/kubernetes      ClusterIP      10.152.183.1     <none>         443/TCP        3d13h
service/nginx           LoadBalancer   10.152.183.53    172.16.192.1   80:31778/TCP   3d11h

NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-8c9df995d-j7w8t   1/1     Running   4          3d11h
pod/nginx-8c9df995d-d9djn   1/1     Running   4          3d11h
pod/nginx-8c9df995d-fwvbd   1/1     Running   6          3d11h
```

ExternalIP には `172.16.192.1` が割り当てられ、`80/tcp` で Listen している
pods も `replicas` に設定した3つが立ち上がっている



### BGP table (next hop) の確認

`service/nginx` に ExternalIP が割り当てられる頃には、ルータのBGPテーブルに経路情報が反映されているはず
ルータによって確認方法や出力は異なるが、 `172.16.192.1/32` に対し、`Next Hop` が割り当てられていることが確認できる

`kubectl get svc` で ExternalIP が割り当てられているにも関わらず、BGPテーブルへの反映が行われていない場合は、speaker pod の状態確認や、そもそも BGP セッション がコケている可能性が高いので確認して欲しい

```
BGP table version is 0, local router ID is 10.227.0.1
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
              r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 10.227.0.0/20    0.0.0.0                  0         32768 i
*  172.16.192.1/32  192.168.11.12                          0 64513 ?
*                   192.168.11.11                          0 64513 ?
*>                  192.168.11.13                          0 64513 ?

Total number of prefixes 4
```

### クライアントからの疎通確認

ブラウザから `http://172.16.192.1/ `へアクセスして、おなじみの **Welcome to nginx!** が表示されれば動作確認は完了となる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149815/44a89384-91fb-7907-074e-0bd38328bc02.png)

アクセスできない場合は、ルータ, ノードへ ping が通るかを確認し、 `traceroute` で経路を追ってどこに原因があるかを探ると良い


## まとめ

目的と手段が入れ替わっていそうな気がするが、ロードバランサが利用可能となり Kubernetes を使用した Homebridge の実運用へ一歩前進した。BGP とはなんぞや状態だったが、実際に構築して触れてみることで概要だけでも理解することが出来た。
次回は、PersistentVolume の構築を予定しているが、NFSマウントするだけな気もするので、Ceph を使用した、分散ストレージ環境の構築と合わせて記事を公開する予定(予定)

その頃には脱ESXiと脱XG Firewall計画が進んでるはず。

ちゃんと理解している方が読むと、ちゃんと理解していないのがバレそうな記事だが、お気づきの点があればコメントで指摘お願いします。
(開発エンジニアなので細かい部分は目を瞑って欲しい…。)


## 参考資料

- [MetalLB, bare metal load-balancer for Kubernetes](https://metallb.universe.tf/configuration/)
- [KubernetesロードバランサーのMetalLBを導入した話(Necoプロジェクト体験入部)](https://blog.cybozu.io/entry/2019/03/25/093000)
- [BGP とは | 「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word12234.html)

[^1]: [K8s on Vagrant, MetalLB BGP ECMP を利用したロードバランサーの検証](https://qiita.com/MahoTakara/items/a33c169b210fae2e8ec9)
[^2]: [オンプレ Kubernetes に MetalLB を入れて EdgerouterX と BGP mode で接続し LoadBalancer 構築](https://qiita.com/suzuyui/items/c83554dd055ef0f4253a)
[^3]: [AS番号 【Autonomous System number】 ASN - IT用語辞典 e-Words](https://e-words.jp/w/AS%E7%95%AA%E5%8F%B7.html)