Kubesprayで準備したk8sにMetalLBをデプロイしてみる

目的
----

APU2とAPU1を利用して、合計4台のノードにkubesprayを使って、kubernetesをデプロイしたところから、いろいろと試行錯誤をして勉強してきました。

DockerHubに登録したイメージをある程度動かせるようになってきて、サービスを公開するためには、LoadBalancerが欲しくなってきます。

最初は慣れているkeepalivedをデプロイする方法を見つけてみたものの、そのままだと1.9でSEGVするという情報もあったため、MetalLBをデプロイしてみることにしました。

結論から書くと、type: LoadBalancerを指定したService毎に1つVIPを割り当てる、という作業は非常に簡単に行なえました。
IngressのServiceに割り当てることで、いろいろ便利になりそうです。
(2018/06/08加筆: ingress-nginx-controllerのserviceにexternal-ipを割り当てる必要があります。この内容については後半に加えました)

Kubespray等でBare-metal上にk8s環境を構築している場合は、MetalLBを導入することで、簡単にサービスを公開することができると思われます。

参考資料
-------

* [MetalLB公式ページ https://metallb.universe.tf/](https://metallb.universe.tf/)
* [GitHub MetalLBリポジトリ https://github.com/google/metallb](https://github.com/google/metallb)

# KubesprayによるMetalLBのデプロイメント (2021年3月30日追記)

最新のKubesprayでは、MetalLBの導入も同時に行なえます。
inventory/以下のgroups_vars/k8s-cluster/addons.yml ファイルの、最後の部分にMetalLBに関するパラメータの指定があります。

```yaml:addons.ymlのMetalLB関連箇所の抜粋
# MetalLB deployment
metallb_enabled: true
metallb_ip_range:
  - "192.168.10.110-192.168.10.198"
metallb_version: v0.9.5
metallb_protocol: "layer2"
# metallb_port: "7472"
# metallb_limits_cpu: "100m"
# metallb_limits_mem: "100Mi"
metallb_additional_address_pools:
  rabbitmq-pool:
    ip_range:
      - "192.168.10.199-192.168.10.199"
    protocol: "layer2"
    auto_assign: false
# metallb_protocol: "bgp"
# metallb_peers:
#   - peer_address: 192.0.2.1
#     peer_asn: 64512
#     my_asn: 4200000000
#   - peer_address: 192.0.2.2
#     peer_asn: 64513
#     my_asn: 4200000000
```

この設定はRabbitMQをデプロイした際に設定した固定IPの設定も含まれているので、metallb_additional_address_pools:の設定を無視すると、必要なのは先頭から6行目までの部分だけです。

必要な設定はaddons.ymlファイルだけでなく、公式ページに記載されているように、準備作業として**Strict ARPモード**を有効にする必要があります。

この設定は、addons.ymlファイルと同じディレクトリにあるk8s-cluster.ymlファイルの中で行えます。

```yaml:k8s-cluster.ymlで必要な設定変更箇所
# configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
# must be set to true for MetalLB to work
kube_proxy_strict_arp: true
```

Kubesprayをこれから利用する場合には、まずaddons.ymlファイルの内容を確認してください。

作業の流れ
---------

公式ドキュメントからCopy&Pasteするだけで、ほぼほぼ完了しました。

1. YAMLファイルの適用 (kubectl apply -f ...)
2. ConfligMap用のYAMLファイルを作成し、適用 (kubectl apply -f ...)

今回は特定のサブネット(192.168.10.0/24)内部で稼動すれば十分だったため、Layer2(IPレベルでのLoadBalance)での設定方法を利用しています。

2018/05/09時点では、下記のようにv0.6.2を指定した方法が紹介されていたので、そのまま利用しています。バージョンは頻繁に上がりますので公式サイトのInstallationセクションを確認してください。

    $ kubectl apply -f https://raw.githubusercontent.com/google/metallb/v0.6.2/manifests/metallb.yaml

チュートリアルにあるようなYAMLファイルを作成して、最下行のIPレンジだけ自分の環境に合うように変更しています。

    apiVersion: v1
    kind: ConfigMap
    metadata:
      namespace: metallb-system
      name: config
    data:
      config: |
        address-pools:
        - name: default
          protocol: layer2
          addresses:
          - 192.168.10.150-192.168.10.199

作成したファイルを指定して、```$ kubectl apply -f <filename>``` で反映させます。

ServiceにIPを割り当てる
=====================

最後にサンプルとしてdefaultのnamespaceにデプロイしているnginxの、Service定義を変更して、type:に指定されている*ClusterIP*の文字列を*LoadBalancer*に変更しています。

    $ kubectl edit svc/sample-nginx

変更が終ったら、ServiceのStatusを確認します。

    $ kubectl get svc

    NAME              TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
    sample-nginx      LoadBalancer   10.233.36.251   192.168.10.150  80:30715/TCP   2h

これで、```$ curl 192.168.10.150``` を実行して、サンプルのWebページが表示されることが確認できました。

ingress-nginxでの利用
--------------

Kubesprayでingress-nginxを有効にしているので、このServiceでもLoadBalancerを指定してみます。

    $ kubectl -n ingress-nginx edit svc/ingress-nginx-default-backend

具体的な利用はこれからですが、External-IPには無事にMetalLBからIPが割り当てられています。

2018/06/08追記: ここが間違っていたので、ここからingress-nginxを使うまでの記事を追記しています。

[加筆] kubesprayによるingress-nginxの利用について
===============================================

後から確認したところ、ingress-nginx-controllerのpodsが作成されていないことに気がつきました。

```bash
$ kubectl -n ingress-nginx get ds/ingress-nginx-controller 
NAME                       DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR                          AGE
ingress-nginx-controller   0         0         0         0            0           node-role.kubernetes.io/ingress=true   31d
```

似た事例は報告されていて、下記の報告が該当します。

* [Ingress DS doesnt create PODS after fresh install #2754](Ingress DS doesnt create PODS after fresh install #2754)

nodeSelectorが設定されているのに、該当するラベルがnodeに設定されていない状況です。

ansibleでは*kube-master*というhosts groupに、ingress-nginxが展開されるので、node1やnode2にラベルを追加してみます。

```bash
$ kubectl edit node/node1
$ kubectl edit node/node2
  ... 省略 ...
  labels:
    ... 省略 ...
    node-role.kubernetes.io/ingress: "true"
  name: node1
  ... 省略 ...
```

この後で、確認するingress-nginx-controllerのpodが動きだしています。

```bash
$ kubectl -n ingress-nginx get ds/ingress-nginx-controller
NAME                       DESIRED   CURRENT   READY     UP-TO-DATE   AVAILABLE   NODE SELECTOR                          AGE
ingress-nginx-controller   2         2         1         2            1           node-role.kubernetes.io/ingress=true   31d
```

ここまでで、controllerのpodsは動き始めました。

[加筆] 前回の失敗と、controller service の追加
===========================================

ここで参考にした資料はこちらです。
* [AKSのNGINX Ingress Controllerのデプロイで悩んだら](http://torumakabe.github.io/post/aks_ingress_quickdeploy/)

前回はdefault-backendに、MetalLBからIPを割り当てられて喜んでいましたが、default-backendは、controller serviceから最終的に振られる終端として機能するため、前段のcontroller serviceにMetalLBからIPを割り当てる必要があり、本質的に間違っていました。

kubesprayからingress-nginxを展開して遭遇した課題をまとめると、次の2点に集約されます。

1. 各ノード(kubectl get nodesの出力)に適切なラベルが割り当てられていないため、ingress-nginx-controllerのpodsが作成されない ← 前節で対応済み
2. ingress-nginx-controllerのpodsに対応するserviceが作成されていない ← 今回

controllerのdeploymentの情報を確認すると、"k8s-app: ingress-nginx"がラベルとして付いています。
これに対応するServiceを追加します。

適当な名前(ingress-nginx-controller-service.yaml)で下記のようなYAMLファイルを作成します。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
spec:
  ports:
  - name: http
    port: 80
    targetPort: http
  - name: https
    port: 443
    targetPort: https
  selector:
    k8s-app: ingress-nginx
```

参考にした資料では、*k8s-app: ingress-nginx-lb*が指定されていたので変更しています。
ポート443関連の証明書周りの説明は省略して、このファイルを適用します。

```bash
$ kubectl -n ingress-nginx apply -f ingress-nginx-controller-service.yaml
```

まず前回間違えて、default-backendに設定したtypeをNodePortに変更しておきます。

```bash
$ kubectl -n ingress-nginx edit svc ingress-nginx-default-backend
```

次にMetalLBからIPを割り当ててもらうために、type: LoadBalancer に変更します。

```bash
$ kubectl -n ingress-nginx edit svc ingress-nginx-controller
```

最終的には、次のような出力になっています。

```bash
$ kubectl -n ingress-nginx get svc 
NAME                            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller        LoadBalancer   10.233.45.75    192.168.10.151   80:31247/TCP,443:32258/TCP   9m
ingress-nginx-default-backend   NodePort       10.233.18.136   <none>        80:32316/TCP                 31d
```
ここまでで、default namespaceに作成したnginx serviceとingress定義によって、MetalLBから割り当てたEXTERNAL-IP(実際にはさらにDNSに登録したVirtualHost名)で反応するようになりました。

[加筆] 余談、テスト用のWebサーバーについて
======================================

いろいろ資料を探していると、minikubeの資料の中にechoserverについて書かれていました。

* [https://github.com/kubernetes/minikube](https://github.com/kubernetes/minikube)

default namespaceにWebサーバーを展開するには、次のようなコマンドで可能になります。

```bash
$ kubectl run hello-minikube --image=k8s.gcr.io/echoserver:1.4 --port=8080
$ kubectl expose deployment hello-minikube --type=NodePort
$ kubectl get svc
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
hello-minikube    NodePort    10.233.58.216   <none>        8080:31173/TCP   7s
kubernetes        ClusterIP   10.233.0.1      <none>        443/TCP          31d
service-mynginx   NodePort    10.233.16.224   <none>        80:31351/TCP     6m
```

自前のservice-mynginxとhello-minikubeが動くので、ingressのテストに使ってみます。

```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-kubeweb01
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: kubeweb01.example.com
      http:
        paths:
        - path: "/test"
          backend:
            serviceName: service-mynginx
            servicePort: 80
        - path: "/echo"
          backend:
            serviceName: hello-minikube
            servicePort: 8080
```

適当な名前(ingress-kubeweb01.yaml)で保存して、適用します。

```bash
$ kubectl apply -f ingress-kubeweb01.yaml
```

echoserverが入っているので、rewrite-targetの有無で、終端となるnginxのpodsに届くリクエストのURIが'/'に変換されるか、挙動の違いが分かると思います。

うまく動かない時には、namespaceが違うとうまくいかないのかなとか思っていましたが、そんな事はなく、ingress-nginx-controllerのserviceが動き始めてからは、どのnamespaceにingress定義を追加しても、ちゃんと反応してくれています。

dashboardを動かす方法は、[参考資料](https://github.com/kubernetes/ingress-nginx/issues/683)にあるので、これを適用すると、同じVirtualHostの下で動きます。

```yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-dashboard
  namespace: kube-system
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: "/"
    nginx.ingress.kubernetes.io/add-base-url: "true"
    nginx.ingress.kubernetes.io/secure-backends: "true"
    nginx.ingress.kubernetes.io/configuration-snippet: rewrite ^(/dashboard)$ $1/ permanent;
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
    - host: kubeweb01.example.com
      http:
        paths:
        - path: "/dashboard"
          backend:
            serviceName: kubernetes-dashboard
            servicePort: 443
```

以上