[Kubernetes]LoadBalancerサービスの動作を確認する

#はじめに
これまでServiceとして、ClusterIPやExternalIP、NodePortの動作を確認してきました。
クラスタの外部と通信できるサービスにはExternalIP、NodePortがありましたが、どちらもある1つのクラスタノードが外部との通信の入口となるため、ノード障害があった場合、通信できなくなります。
それを防ぐために、外部のLoadBalancerを使用した「LoadBalancerサービス」があります。使用できる外部のLoadBalancerはAWSなどのクラウドプロバイダーの機能を利用するのが一般的です。
利用できるクラウドプロバイダーの注意事項などはマニュアルに記載されています。IBM Cloudも使えると思いますが、書かれてないですね。

>LoadBalancerタイプ
>[kubernetes.io](https://kubernetes.io/ja/docs/concepts/services-networking/service/#loadbalancer)

#LoadBalancerの実装
上記のように「LoadBalancerサービス」を使用するには、外部のLoadBalancerが必要になります。が、今回の検証環境ではありません。
そもそも、1台のPCでVM切って作ってますからね。
そこで、今回はオンプレ上にLoadBalancerを実装できる「MetalLB」を使用しました。

##MetalLBのインストール
ちゃんとログを取ってなかったので、概要だけご紹介します。
特に難しいことはなく、マニュアルの通りにできました。

[MetalLB Installation](https://metallb.universe.tf/installation/)

>```
>kubectl edit configmap -n kube-system kube-proxy
>```
>and set:
>
>```
>apiVersion: kubeproxy.config.k8s.io/v1alpha1
>kind: KubeProxyConfiguration
>mode: "ipvs"
>ipvs:
>  strictARP: true
>```
>
>To install MetalLB, apply the manifest:
>
>```
>kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.9.3/manifests/namespace.yaml
>kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.9.3/manifests/metallb.yaml
># On first install only
>kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
>```

##ConfigMapの作成

[Layer 2 configuration](https://metallb.universe.tf/configuration/#layer-2-configuration)

```l2.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: my-ip-space
      protocol: layer2
      addresses:
      - 10.20.30.150-10.20.30.200
```
addressesは環境に合わせて指定します。

##実装結果の確認
上記の結果、以下のように設定されました。これでLoadBalancerサービスの動作を確認できます。

```
$ kubectl -n metallb-system get all -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE           NOMINATED NODE   READINESS GATES
pod/controller-5c9894b5cd-59tmn   1/1     Running   2          24h   192.168.69.198   k8s-worker02   <none>           <none>
pod/speaker-l99nt                 1/1     Running   3          24h   10.20.30.10      k8s-master     <none>           <none>
pod/speaker-scqps                 1/1     Running   4          24h   10.20.30.20      k8s-worker01   <none>           <none>
pod/speaker-vdh69                 1/1     Running   4          24h   10.20.30.30      k8s-worker02   <none>           <none>

NAME                     DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                 AGE   CONTAINERS   IMAGES                   SELECTOR
daemonset.apps/speaker   3         3         3       3            3           beta.kubernetes.io/os=linux   24h   speaker      metallb/speaker:v0.9.3   app=metallb,component=speaker

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES                      SELECTOR
deployment.apps/controller   1/1     1            1           24h   controller   metallb/controller:v0.9.3   app=metallb,component=controller

NAME                                    DESIRED   CURRENT   READY   AGE   CONTAINERS   IMAGES                      SELECTOR
replicaset.apps/controller-5c9894b5cd   1         1         1       24h   controller   metallb/controller:v0.9.3   app=metallb,component=controller,pod-template-hash=5c9894b5cd
```

#LoadBalancerサービスの作成
以下のマニフェストを作成しました。spec.typeが「LoadBalancer」である以外は、NodePortと同じですね。

```sample-lb.yaml
apiVersion: v1
kind: Service
metadata:
  name: load-balancer
spec:
  ports:
  - name: load-balancer
    port: 8080
    protocol: TCP
    targetPort: 80
    nodePort: 30002
  selector:
    app: nginx-dep
  type: LoadBalancer
```

このマニフェストをapplyします。

```
$ kubectl apply -f sample-lb.yaml
service/load-balancer created
$ kubectl get svc
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
kubernetes      ClusterIP      10.96.0.1       <none>         443/TCP          45d
load-balancer   LoadBalancer   10.102.190.16   10.20.30.150   8080:30002/TCP   4s
$ kubectl describe svc load-balancer
Name:                     load-balancer
Namespace:                default
Labels:                   <none>
Annotations:              kubectl.kubernetes.io/last-applied-configuration:
                            {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"load-balancer","namespace":"default"},"spec":{"ports":[{"name":"l...
Selector:                 app=nginx-dep
Type:                     LoadBalancer
IP:                       10.102.190.16
LoadBalancer Ingress:     10.20.30.150
Port:                     load-balancer  8080/TCP
TargetPort:               80/TCP
NodePort:                 load-balancer  30002/TCP
Endpoints:                192.168.69.251:80,192.168.79.110:80
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason        Age   From                Message
  ----    ------        ----  ----                -------
  Normal  IPAllocated   18s   metallb-controller  Assigned IP "10.20.30.150"
  Normal  nodeAssigned  18s   metallb-speaker     announcing from node "k8s-worker02"
```
LoadBalancerサービスのEXTERNAL-IPに外部LoadBalancerからIPアドレスがアサインされてますね。なお、外部LoadBalancerがない場合、サービスの作成はできますが、EXTERNAL-IPが\<pending>になったままになります。

#動作確認
LoadBalancerサービスを作成すると、NodePortとClusterIPも作成されます。ここでは、各レイヤー（Service）の動作を確認したいと思います。

##LoadBalancerサービス
まずは、LoadBalancerの動作を確認してみたいと思います。
これまでと同様に、gatewayから疎通を確認します。

```
[gateway ~]$ for i in 1 2 3 4 5; do curl -s http://10.20.30.150:8080 | grep pod; sleep 5; done
pod1
pod2
pod1
pod2
pod1
```
疎通の確認とバランシングされていることがわかりますね。

##NodePort
今度はNodePortとしての動作も確認してみたいと思います。

```
[gateway ~]$ for i in 1 2 3 4 5; do curl -s http://k8s-worker01:30002 | grep pod; sleep 5; done
pod1
pod2
pod1
pod2
pod1
[gateway ~]$ for i in 1 2 3 4 5; do curl -s http://k8s-worker02:30002 | grep pod; sleep 5; done
pod1
pod2
pod1
pod2
pod1
```
NodePortとしても動作していますね。

##ClusterIP
次にClusterIPとしての動作も確認します。
以下のマニフェストのPodをデプロイして、logを確認します。

```test_pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
    - name: centos
      image: centos:latest
      command:
      - sh
      - -c
      args:
      - for i in 1 2 3 4 5 ; do curl -s http://load-balancer.default.svc.cluster.local:8080 ; sleep 5; done ;exit 0
```

```
$ kubectl apply -f test_pod.yaml
pod/test-pod created
$ kubectl logs test-pod | grep pod
pod1
pod2
pod1
pod2
pod1
```
ClusterIPとしての動作も確認できます。

#まとめ
今回はLoadBalancerの動作を確認しました。LoadBalancerサービスを作成すると、NodePortとClusterIPも同時に作成されますので、3層構造のような感じですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/492530/2e865df4-0355-a715-1a73-baaa96f752cc.png)

LoadBalancerは各workerノードにバランシングして、クラスタ内はClusterIPがバランシングするような動作だとわかりやすいです。
ただ、マニュアルには以下のように記載されていますので、この理解は厳密には違うのかも知れません。

>[LoadBalancerタイプ](https://kubernetes.io/ja/docs/concepts/services-networking/service/#loadbalancer)
>外部のロードバランサーからのトラフィックはバックエンドのPodに直接転送されます。

これまでClusterIP、NodePort、LoadBalancerと順を追って動作を確認してきましたので、理解しやすかったです。
また、オンプレ環境でもLoadBalancerを使えることが確認できましたので、開発環境はクラウドを使わずにPCで開発してもよいですね。

#補足
その他のパラメータについて、以下に記載します。

##externalTrafficPolicy
NodePortと同じ動作です。詳細はこちらをご覧ください。
[[Kubernetes]NodePortサービスの動作を確認する 2](https://qiita.com/dingtianhongjie/items/61899c80a9b4356b14cc)

##loadBalancerIP
LoadBalancerサービスに付与されるIPアドレスは、特に指定しなければ自動でアサインされます。
しかし、「spec.loadBalancerIP」パラメータを設定することで、IPアドレスを指定することができます。
指定できるIPアドレスや指定方法は、クラウドプロバイダーによって異なりますので、各クラウドごとに確認する必要があります。

##loadBalancerSourceRanges
外部LoadBalancerのFirewall機能を利用することで、LoadBalancerサービスでのアクセス制御が可能となります。
「spec.loadBalancerSourceRanges」に接続を許可する送信元IPアドレスの範囲を指定します。未指定の場合のデフォルトは、「0.0.0.0/0」なので、全ての通信を許可することになります。
なお、今回の検証環境で動作を試してみましたが、範囲外からの通信を遮断することはできませんでした。外部LoadBalancerのFirewall機能を利用する必要がありますので、今回構築した「MetalLB」に機能がないのか、設定できてないかだと思います。