kubernetes
kubespray
MetalLB

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を導入することで、簡単にサービスを公開することができると思われます。

参考資料

作業の流れ

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

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

2018/05/09時点では、下記のようにv0.6.2を指定した方法が紹介されていたので、そのまま利用しています。

$ 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定義を変更して、teyp:に指定されている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が作成されていないことに気がつきました。

$ 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

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

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

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

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

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

$ 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のデプロイで悩んだら

前回は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ファイルを作成します。

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関連の証明書周りの説明は省略して、このファイルを適用します。

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

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

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

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

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

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

$ 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について書かれていました。

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

$ 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のテストに使ってみます。

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)で保存して、適用します。

$ kubectl apply -f ingress-kubeweb01.yaml

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

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

dashboardを動かす方法は、参考資料にあるので、これを適用すると、同じVirtualHostの下で動きます。

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

以上