Metal LBを試してみる

# はじめに
以下の記事の続き。「はじめてのデプロイ」ではあくまでデプロイすることに主眼を置いていたため、外部からのアクセスは NodePort を利用し、特定Worker ノードの持つアドレスへ Port 番号を使ってアクセスする、かなり強引なやり方だった。前段にMetal LBを置いて80/TCPとして通信を終端してそこから各Workerノードに通信を振り分ける仕組みを作ってみる。

https://qiita.com/Shakapon/items/436fd5d8e762cf50a253


## Metal LB 3つの実装
2025年5月現在、これまでの実装のほか、Metal LBは BGP部分にFRRを利用する FRR-Modeと、FRR-K8s Modeが存在する。このうち、FRR-K8s Modeはまだ experimental とのこと。まずは標準実装での検証を行う。


# Metal LB のインストール
当然ながら公式サイトの記述に沿って構築する。作業はすべて Master ノードで実施する。

https://metallb.io/installation/

インストールは Helm Chartを利用する方法と Manifest を利用する方法あるが、まずは Manifest でのインストールを試みる。

```console
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.14.9/config/manifests/metallb-native.yaml
```

:::note info
IPVSモード利用時の注意点
kube-proxyのIPVS (IP Virtual Server）モードとして利用している場合、Strict ARPをtrueにする必要があるとのこと。
今回試した環境ではデフォルトのiptables モード。iptables/IPVSどちらも、kube-proxyのTraffic 分散方式で、IPVS のほうがよりスケーラブル。今回の検証では、まずはデフォルトの iptables モードで進める。
:::



## 正常性確認
Metal LBは名前空間 **metallb-system** にインストールされ、controllerとspeaker podがそれぞれ存在する。
```console
ubuntu@k8s-master:~$ kubectl -n metallb-system get pod
NAME                         READY   STATUS    RESTARTS   AGE
controller-bb5f47665-jxcj2   1/1     Running   0          2m22s
speaker-g6mmh                1/1     Running   0          2m22s
speaker-kzhd6                1/1     Running   0          2m22s
speaker-qppgk                1/1     Running   0          2m22s
```
```console
ubuntu@k8s-master:~$ kubectl -n metallb-system get service
NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
metallb-webhook-service   ClusterIP   172.16.25.63   <none>        443/TCP   2m26s
```

:::note info
Controller ...IPアドレスのアサインを行うコントローラ
Speaker ... デーモンセット。ユーザが選択した Protocol を使用して外部と接続する。
:::

# L2モードでの MetalLB
1. Metal LB でのIPアドレス Pool を指定（`metallb-l2-IPAddressPool.yaml`）
2. Metal LB での広告設定（`metallb-l2-Advertisement.yaml`）
3. Metal LB を利用する Deployment 例（`nginx-metallb-l2-deployment.yaml`）
4. Metal LB を利用する Service設定例（` nginx-metallb-l2-service.yaml`）

## Metal LB でのIPアドレス Pool を指定
Pool アドレスレンジを指定する。Worker ノードが所属するセグメント（192.168.11.0/24）末尾で11アドレスを確保し、これをPoolアドレスとした。MetalLBを利用する場合、namespaceは基本的に`metallb-system`上に作る模様。Pool アドレスに名前（`first-Pool`）を付け、これをL2Advertisement 側から呼び出す。

```metallb-l2-IPAddressPool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.11.240-192.168.11.250
```
:::note info
2022年7月にリリースされた 0.13.2から Config方法が変更された（ConfigMap ⇒ CRD）。少し古めのサイトだと Config Map での実装例となっているため注意（ハマった）。

https://metallb.universe.tf/release-notes/#version-0-13-2
:::


## Metal LB での広告設定
`metallb-l2-IPAddressPool.yaml`で定義したIPアドレスPoolをここのSpec で呼び出す。
```metallb-l2-Advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: example
  namespace: metallb-system
spec:
  ipAddressPools:
    - first-pool
```

## Metal LB を利用する Deployment 例
``` nginx-metallb-l2-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-metallb-l2
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx-metallb-l2
  template:
    metadata:
      labels:
        app: nginx-metallb-l2
    spec:
      containers:
      - name: nginx
        image: nginx:stable
        command: ["/bin/sh", "-c", "/mnt/start.sh"]
        ports:
          - containerPort: 80
        volumeMounts:
        - mountPath: /mnt
          name: nfs-volume
      volumes:
      - name: nfs-volume
        persistentVolumeClaim:
          claimName: nfs-dynamic-pvc
```

## Metal LB を利用する Service設定例
```nginx-metallb-l2-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-metallb-l2-service
spec:
  selector:
    app: nginx-metallb-l2
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: LoadBalancer
  ```
## 設定投入
```console
kubectl apply -f metallb-l2-IPAddressPool.yaml
kubectl apply -f metallb-l2-Advertisement.yaml
kubectl apply -f nginx-metallb-l2-deployment.yaml
kubectl apply -f nginx-metallb-l2-service.yaml
```

## 正常性確認
```console
ubuntu@k8s-master:~$ kubectl get IPAddressPool -n metallb-system
NAME         AUTO ASSIGN   AVOID BUGGY IPS   ADDRESSES
first-pool   true          false             ["192.168.11.240-192.168.11.250"]
ubuntu@k8s-master:~$
```

```console
ubuntu@k8s-master:~$ kubectl get L2Advertisement -n metallb-system
NAME      IPADDRESSPOOLS   IPADDRESSPOOL SELECTORS   INTERFACES
example   ["first-pool"]
```

```console
ubuntu@k8s-master:~$ kubectl get pod -l app=nginx-metallb-l2
NAME                               READY   STATUS    RESTARTS   AGE
nginx-metallb-l2-d8b48657c-rqqp5   1/1     Running   0          3h16m
nginx-metallb-l2-d8b48657c-tlnq6   1/1     Running   0          3h16m
```

## External-IPの確認
`nginx-metallb-l2-service` の`EXTERNAL-IP`として、IPアドレスPoolに含まれる`192.168.11.240`が払いだされていることが確認できる。
```console
ubuntu@k8s-master:~$ kubectl get svc
NAME                       TYPE           CLUSTER-IP       EXTERNAL-IP      PORT(S)        AGE
(snip)
nginx-metallb-l2-service   LoadBalancer   172.16.102.250   192.168.11.240   80:32529/TCP   65m
```

## アクセス確認
`curl`からExternal IPに指定された`192.168.11.240`へアクセスすると、それぞれのPodから応答してくれることが確認できる。NodePort だとかなり限定的だが、ようやっと外部からまともに見えるようになる。
```console
ubuntu@k8s-master:~$ curl http://192.168.11.240
<html><body><h1>Hello from nginx-metallb-l2-d8b48657c-tlnq6</h1></body></html>
ubuntu@k8s-master:~$ curl http://192.168.11.240
<html><body><h1>Hello from nginx-metallb-l2-d8b48657c-rqqp5</h1></body></html>
```