KubernetesのLoadBalancer serviceをMetalLBを使ってデプロイしてみる



こんにちは。
株式会社クラスアクト インフラストラクチャ事業部の大塚です。

この記事ではmicrok8sで構築したKuberenetesクラスタ環境にMetalLBとLoadBalancer serviceをデプロイし、LB経由でpod内のコンテナにWebブラウザでアクセスしてみようと思います。

# 環境

k8sのクラスタ環境は以下となっております。nodeは3台。192.168.2.30のIPを持つnodeがMaster node。それ以外の31,32のIPを持つnodeがworker nodeとして稼働しています。
k8sクラスタではpodやservice等稼働させていません。（正確にいうと稼働していますが気にしなくていいです。）

![kubernetes-ページ13.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/9e95426c-9f62-eb85-fd48-4edda07119c1.png)

何も起動していないこと及びnodeが3つあることを示す、コマンドのlogになります。
serviceが稼働していますが気にしなくて大丈夫です。またほかのnamespaceでも色々起動していますが、同様に気にしなくていいです。デフォルトで作られる奴です。
では早速構築していきましょう。

```
root@k8s-master:~/yaml# kubectl get all -o wide
NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   24h   <none>


root@k8s-master:~/yaml# kubectl get node -o wide
NAME           STATUS   ROLES    AGE     VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
k8s-worker01   Ready    <none>   7h32m   v1.26.4   192.168.2.31   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
k8s-master     Ready    <none>   24h     v1.26.4   192.168.2.30   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
k8s-worker02   Ready    <none>   7h31m   v1.26.4   192.168.2.32   <none>        Ubuntu 22.04.2 LTS   5.15.0-71-generic   containerd://1.6.15
```
# 用語
構築の前に、今回出てくるk8s関連の用語を軽くまとめておきます。

deployment
---

>Deploymentは、PodやReplicaSetの更新を宣言的に行うものです。
>
>Deploymentに必要な状態を記述すると、Deployment Controllerは実際の状態を制御された速度で必要な状態に変更します。Deploymentを定義して新しいReplicaSetを作成したり、既存のDeploymentを削除してそのリソースをすべて新しいDeploymentで採用することができます。

https://kubernetes.io/docs/concepts/workloads/controllers/deployment/


replicaset
---

>ReplicaSetの目的は、任意の時間に実行されているレプリカPodのセットを安定的に維持することです。そのため、指定された数の同一のPodの可用性を保証するために使用されることがよくあります。

https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/


MetalLB
---

>MetalLBとはベアメタル環境で使用できるKubernetesのExternal Load Balancerの実装の一種です。 Googleによって開発されたシンプルなロードバランサーで、LoadBalancerタイプのServiceに対する公開用IPアドレス(External IP)の割り当てと、 External IPに対する経路情報の広報、といった2つの機能を持ちます。

https://blog.cybozu.io/entry/2019/03/25/093000


LoadBalancer service
---

>クラスター外のクライアントから、Podグループにアクセスするために利用されます。NodePortと異なり外部のロードバランサーを利用してServiceを公開するため、ノードのIPアドレスやポート番号を意識する必要はなく、外部ロードバランサーに構成された仮想IPアドレスでアクセスすることが可能です。ロードバランサーに登録される負荷分散対象は、利用するCNIによって異なります。

https://www.netone.co.jp/knowledge-center/netone-blog/20210715-01/


# Deploymentでpodをデプロイする

用意したyamlファイルは以下です。
deploymentの名前をnginx-httpd-deployment。常時展開するpod数を3としています。
podの中身は以前のqiitaのものを流用しております。つまり1つのpodの中にnginxコンテナとapache2コンテナを稼動させているものを使っているということになります。

https://qiita.com/ohtsuka-shota/items/38f74968ada4484a9c91


```
root@k8s-master:~/yaml# cat web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-httpd-deployment
  labels:
    app: web-app-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
        - name: web-httpd
          image: shotaohtsuka/my-httpd-image
          ports:
            - name: web-httpd
              containerPort: 90
              protocol: TCP
        - name: web-nginx
          image: nginx
          ports:
            - name: web-nginx
              containerPort: 80
              protocol: TCP
```

このyamlファイルを元にデプロイを実施。
get allコマンドの出力結果から、それぞれのnodeに1つずつpodが作成されていることが分かります。また、deploymentとdeploymentに内包されているreplicasも出力されていますね。
```
root@k8s-master:~/yaml# kubectl create -f web-deployment.yaml
deployment.apps/nginx-httpd-deployment created

root@k8s-master:~/yaml# kubectl get all -o wide
NAME                                        READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES
pod/nginx-httpd-deployment-759b9b5f-vq8rp   2/2     Running   0          7s    10.1.235.209   k8s-master     <none>           <none>
pod/nginx-httpd-deployment-759b9b5f-rsp86   2/2     Running   0          7s    10.1.79.68     k8s-worker01   <none>           <none>
pod/nginx-httpd-deployment-759b9b5f-h8k67   2/2     Running   0          7s    10.1.69.197    k8s-worker02   <none>           <none>

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   SELECTOR
service/kubernetes   ClusterIP   10.152.183.1   <none>        443/TCP   24h   <none>

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS            IMAGES                              SELECTOR
deployment.apps/nginx-httpd-deployment   3/3     3            3           7s    web-httpd,web-nginx   shotaohtsuka/my-httpd-image,nginx   app=web-app

NAME                                              DESIRED   CURRENT   READY   AGE   CONTAINERS            IMAGES                              SELECTOR
replicaset.apps/nginx-httpd-deployment-759b9b5f   3         3         3       7s    web-httpd,web-nginx   shotaohtsuka/my-httpd-image,nginx   app=web-app,pod-template-hash=759b9b5f
```

この時のイメージは以下となります。
![kubernetes-ページ13.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/6e55bb46-e957-3d74-8b2b-473c389c02c4.png)

# LoadBalancer serviceをデプロイする
上記でデプロイしたpod群を外部公開する為にLoadBalancer serviceをデプロイしていきます。今回用意したyamlファイルは以下。

ザックリ解説すると、lb-service-httpd-nginxという名前のLoadBalancer serviceにしています。
LBは8080ポートと9090ポートでlistenし、nodeは30080ポートと30090ポートでlisten。podは80ポートと90ポートでlistenしています。
LBのIPに対して8080ポートで通信が飛んでくると各nodeの30080ポートに対して通信を投げ、各nodeの30080ポートに通信が来ると今度はNodePortが各podの80ポートに通信を投げ、結果pod内のnginxに接続されます。
同様にLBのIPに対して9090ポートで通信が飛んでくると各nodeの30090ポートに対して通信を投げ、各nodeの30090ポートに通信が来ると今度はNodePortが各podの90ポートに通信を投げ、結果pod内のapache2に接続されます。
私自身ごちゃごちゃしているので、後でイメージに落とし込みます。
```
root@k8s-master:~/yaml# cat deployment-service-httpd-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: lb-service-httpd-nginx
spec:
  type: LoadBalancer
  selector:
    app: web-app
  ports:
    - name: nginx-port
      port: 8080
      targetPort: 80
      nodePort: 30080
    - name: httpd-port
      port: 9090
      targetPort: 90
      nodePort: 30090
```

これをデプロイしていきます。
デプロイ自体は問題無いのですが、service/lb-service-httpd-nginxのEXTERNAL-IPがpendingとなっており、ここにLoadBalancer用のIPが欲しいです。
その為にMetalLBを有効化していきます。

```
root@k8s-master:~/yaml# kubectl get all -o wide
NAME                                        READY   STATUS    RESTARTS   AGE   IP             NODE           NOMINATED NODE   READINESS GATES
pod/nginx-httpd-deployment-759b9b5f-vq8rp   2/2     Running   0          16m   10.1.235.209   k8s-master     <none>           <none>
pod/nginx-httpd-deployment-759b9b5f-rsp86   2/2     Running   0          16m   10.1.79.68     k8s-worker01   <none>           <none>
pod/nginx-httpd-deployment-759b9b5f-h8k67   2/2     Running   0          16m   10.1.69.197    k8s-worker02   <none>           <none>

NAME                             TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                         AGE   SELECTOR
service/kubernetes               ClusterIP      10.152.183.1     <none>        443/TCP                         24h   <none>
service/lb-service-httpd-nginx   LoadBalancer   10.152.183.197   <pending>     8080:30080/TCP,9090:30090/TCP   6s    app=web-app

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS            IMAGES                              SELECTOR
deployment.apps/nginx-httpd-deployment   3/3     3            3           16m   web-httpd,web-nginx   shotaohtsuka/my-httpd-image,nginx   app=web-app

NAME                                              DESIRED   CURRENT   READY   AGE   CONTAINERS            IMAGES                              SELECTOR
replicaset.apps/nginx-httpd-deployment-759b9b5f   3         3         3       16m   web-httpd,web-nginx   shotaohtsuka/my-httpd-image,nginx   app=web-app,pod-template-hash=759b9b5f
```

MetalLBを有効化します。
Enter each IP address range delimited by comma (e.g. '10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111'): でIPアドレスを入力していますが、これはk8s nodeと同じネットワーク帯で使っていないIPを使用すればいいはずです。今回は第四オクテッドが35-39をLoadBlancerの為に確保しています。
```
root@k8s-master:~/yaml# microk8s status
microk8s is running
high-availability: no
  datastore master nodes: 192.168.2.30:19001
  datastore standby nodes: none
addons:
  enabled:
    dashboard            # (core) The Kubernetes dashboard
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
  disabled:
    cert-manager         # (core) Cloud native certificate management
    community            # (core) The community addons repository
    gpu                  # (core) Automatic enablement of Nvidia CUDA
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    minio                # (core) MinIO object storage
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated


root@k8s-master:~/yaml# microk8s enable metallb
Infer repository core for addon metallb
Enabling MetalLB
Enter each IP address range delimited by comma (e.g. '10.64.140.43-10.64.140.49,192.168.0.105-192.168.0.111'): 192.168.2.35-192.168.2.39
Applying Metallb manifest
customresourcedefinition.apiextensions.k8s.io/addresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bfdprofiles.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgpadvertisements.metallb.io created
customresourcedefinition.apiextensions.k8s.io/bgppeers.metallb.io created
customresourcedefinition.apiextensions.k8s.io/communities.metallb.io created
customresourcedefinition.apiextensions.k8s.io/ipaddresspools.metallb.io created
customresourcedefinition.apiextensions.k8s.io/l2advertisements.metallb.io created
namespace/metallb-system created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker unchanged
role.rbac.authorization.k8s.io/controller created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker unchanged
rolebinding.rbac.authorization.k8s.io/controller created
secret/webhook-server-cert created
service/webhook-service created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
validatingwebhookconfiguration.admissionregistration.k8s.io/validating-webhook-configuration configured
Waiting for Metallb controller to be ready.
deployment.apps/controller condition met
ipaddresspool.metallb.io/default-addresspool created
l2advertisement.metallb.io/default-advertise-all-pools created
MetalLB is enabled


root@k8s-master:~/yaml# microk8s status
microk8s is running
high-availability: no
  datastore master nodes: 192.168.2.30:19001
  datastore standby nodes: none
addons:
  enabled:
    dashboard            # (core) The Kubernetes dashboard
    dns                  # (core) CoreDNS
    ha-cluster           # (core) Configure high availability on the current node
    helm                 # (core) Helm - the package manager for Kubernetes
    helm3                # (core) Helm 3 - the package manager for Kubernetes
    metallb              # (core) Loadbalancer for your Kubernetes cluster
    metrics-server       # (core) K8s Metrics Server for API access to service metrics
  disabled:
    cert-manager         # (core) Cloud native certificate management
    community            # (core) The community addons repository
    gpu                  # (core) Automatic enablement of Nvidia CUDA
    host-access          # (core) Allow Pods connecting to Host services smoothly
    hostpath-storage     # (core) Storage class; allocates storage from host directory
    ingress              # (core) Ingress controller for external access
    kube-ovn             # (core) An advanced network fabric for Kubernetes
    mayastor             # (core) OpenEBS MayaStor
    minio                # (core) MinIO object storage
    observability        # (core) A lightweight observability stack for logs, traces and metrics
    prometheus           # (core) Prometheus operator for monitoring and logging
    rbac                 # (core) Role-Based Access Control for authorisation
    registry             # (core) Private image registry exposed on localhost:32000
    storage              # (core) Alias to hostpath-storage add-on, deprecated
```

再度EXTERNAL-IPを確認します。
192.168.2.35が割り当てられていることが確認出来ますね。
```
root@k8s-master:~/yaml# kubectl get service -o wide
NAME                     TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                         AGE     SELECTOR
kubernetes               ClusterIP      10.152.183.1     <none>         443/TCP                         24h     <none>
lb-service-httpd-nginx   LoadBalancer   10.152.183.197   192.168.2.35   8080:30080/TCP,9090:30090/TCP   5m40s   app=web-app
```

ここまでのイメージは以下です。
![k8s-ページ29.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/1bacc75b-4d3a-8d94-fbc3-8e2f8cbba6f3.png)


通信フロー迄イメージに落とし込むと以下となります。
青矢印がnginxコンテナとの通信経路、赤矢印がapache2コンテナとの通信経路を示します。
podに接続して中身を確認したい時はLoadBalancerのIPアドレス192.168.2.35とポート番号を指定してアクセスする形になります。

![k8s-ページ28.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/f68807b3-e287-10d0-6a8c-242cf866a693.png)






# 実際にWebブラウザでpod内のコンテナにアクセスする

以下が結果です。
http://192.168.2.35:8080
![1921682358080.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/986179af-72e4-ad9c-d78c-f70047e268eb.png)

http://192.168.2.35:9090
![1921682359090.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/5b4f1712-ae97-779a-55f2-6cad2e28ffff.png)


# 独り言：【推測】なぜLoadBalancerが必要なのか。NodePortじゃダメなのか？

なぜLoadBalancerが必要なのか？
LBを使わないで本環境を構築する、つまりNodePortまでで実装しようとすると、手前にnginx等で構築したプロキシ等を実装する必要がありそう。。。
![kubernetes-ページ14.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/6dddb30d-7bd4-4680-263c-cae7adbcc69e.png)


ただ、この場合、例えばworker01がnodeごと死んでいた場合、nginxとworker01間でハートビート等で生存を確認していないと死んでいるノードに対してまで負荷分散を行い、データを取得出来ない可能性がある？
生存確認などの実装なども考慮しないといけなくなる気がするので、結構大変そう。LoadBalancer serviceを使えばそのあたりを良い感じにしてくれるんだろうな。。。

![kubernetes-ページ15.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/f60fd9a5-637e-d656-a9c3-83141b8dc30c.png)