zaki work log

作業ログやら生活ログやらなんやら

[Kubernetes] オンプレK8sでもtype:LoadBalancer Serviceが使えるようになるMetalLBを入れてみた

クラウドサービスのマネージドK8sだと気軽に使ってしまうtype:LoadBalancer Service、kubeadmとかで作ったオンプレK8sだとそのままだと使えないんだけど、このツイートで「MetalLB」というのを知って(過去にも聞いてたことはあったかもしれないけど、、いろいろ認識が足りてなかったかも)試してみました。


7.26追記:
firewalldを有効にしている状態でクラスタにMetalLBを導入すると、ノードOSをリブートした後に手元の環境ではLoadBalancer Serviceが機能しなくなりました。
構築後でもfirewalldを無効にすれば動作するので、検証環境なのでひとまずfirewalldをオフにして使用しています。

github.com


7.31追記:
↑の問題は、そもそも「IPVSを使っていないのにIPVSを使ってる場合に必要な設定を行っていたから」でした。
不要な設定を行わなければ(設定済みの場合は元に戻せば)大丈夫です。
ちなみに設定内容は「Installation冒頭のPreparation」で、ConfigMapkube-proxyの変更です。

環境

Rookのときと同じく、ESXi上にkubeadmで構築したmaster 1台、worker 2台のKubernetes v1.18クラスタです。(全ノード 4CPU / RAM8GB)
というか、Rook入れたK8s環境に追加構築しています。

[zaki@cloud-dev ~]$ kc get node
NAME                        STATUS   ROLES    AGE    VERSION
k8s-master01.esxi.jp-z.jp   Ready    master   6d5h   v1.18.5
k8s-worker01.esxi.jp-z.jp   Ready    <none>   6d5h   v1.18.5
k8s-worker02.esxi.jp-z.jp   Ready    <none>   6d5h   v1.18.5
[zaki@cloud-dev ~]$ kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoV
ersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoV
ersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}

それからMetalLBは2020.07.10時点のv0.9.3を使用。

図にするとこんな感じ。

f:id:zaki-hmkc:20200711114803p:plain

本記事内でkubectlを実行している操作用の"cloud-dev"はK8sのノードではないがノードと同じネットワークにいる。
通常はpodへ直接通信することはできず、type:LoadBalancer Serviceを使わない場合はtype:NodePort Serviceを使うか、kubectl port-forwardを使うなどしてアクセスする必要がある。

要件

公式サイトより。

metallb.universe.tf

  • A Kubernetes cluster, running Kubernetes 1.13.0 or later, that does not already have network load-balancing functionality.
  • A cluster network configuration that can coexist with MetalLB.
  • Some IPv4 addresses for MetalLB to hand out.
  • Depending on the operating mode, you may need one or more routers capable of speaking BGP.

「BGPが使えるルーター」に心当たりがないけど、「モードによっては」なので、違うモードなら大丈夫なのかな。。
ちょっと不安はあるけど、とにかくやってみよう。

cluster network configuration

MetalLB, bare metal load-balancer for Kubernetes

ちなみにうちはCalicoを使ってるんだけど、既知の問題はあるらしい。

なるほどわからん
とにかくやってみよう。

installation

MetalLB, bare metal load-balancer for Kubernetes

preparation

7/31追記: 「Preparation」の手順はIPVSモードでkube-proxyを動かしている場合のみ必要な設定です

If you’re using kube-proxy in IPVS mode, since Kubernetes v1.14.2 you have to enable strict ARP mode.

特に心当たりがない場合はここのConfigMapの設定は不要です。(設定するとノードをリブートした後に通信できなくなりますが、設定を元にもどせばOKです)


$ kubectl edit configmap -n kube-system kube-proxy

config.conf配下のデータ部分のkind: KubeProxyConfigurationの記述を探し、以下の内容を変更する。

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
ipvs:
  strictARP: true

手元の環境だと以下の2か所だった。

-    mode: ""
+    mode: "ipvs"
---
-      strictARP: false
+      strictARP: true
[zaki@cloud-dev ~]$ kubectl edit configmap -n kube-system kube-proxy
configmap/kube-proxy edited

kubeadm-configに設定追加もOKらしい。
そっち先に見ればよかったけどとりあえず前述の通りConfigMap変更版で。。

Installation By Manifest

MetalLB, bare metal load-balancer for Kubernetes

下記3コマンドを実行する。
(3つ目のみ「初回インストール時のみ」とある。残り2つは何度も実行する?ここはよくわからなかった。)

$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml
$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml
$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"

以下、実行例

[zaki@cloud-dev ~]$ kc get ns
NAME              STATUS   AGE
default           Active   6d5h
helm-sample       Active   5d6h
kube-node-lease   Active   6d5h
kube-public       Active   6d5h
kube-system       Active   6d5h
rook-ceph         Active   5d9h
rook-example      Active   5d7h
sample-app        Active   5d10h

実行前のnamespace状態。
そしてapplyする。

[zaki@cloud-dev ~]$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml
namespace/metallb-system created

実行後のnamespace状態。

[zaki@cloud-dev ~]$ kc get ns
NAME              STATUS   AGE
default           Active   6d5h
helm-sample       Active   5d6h
kube-node-lease   Active   6d5h
kube-public       Active   6d5h
kube-system       Active   6d5h
metallb-system    Active   2s
rook-ceph         Active   5d9h
rook-example      Active   5d7h
sample-app        Active   5d10h

metallb-systemネームスペースが作成される。
describeした感じだと、labelの設定がされている。

[zaki@cloud-dev sample]$ kc describe ns metallb-system 
Name:         metallb-system
Labels:       app=metallb
Annotations:  Status:  Active

No resource quota.

No LimitRange resource.

次のマニフェストファイルをapplyする。

[zaki@cloud-dev ~]$ kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml
podsecuritypolicy.policy/controller created
podsecuritypolicy.policy/speaker created
serviceaccount/controller created
serviceaccount/speaker created
clusterrole.rbac.authorization.k8s.io/metallb-system:controller created
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker created
role.rbac.authorization.k8s.io/config-watcher created
role.rbac.authorization.k8s.io/pod-lister created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller created
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker created
rolebinding.rbac.authorization.k8s.io/config-watcher created
rolebinding.rbac.authorization.k8s.io/pod-lister created
daemonset.apps/speaker created
deployment.apps/controller created
[zaki@cloud-dev ~]$ kc get pod -n metallb-system 
NAME                          READY   STATUS              RESTARTS   AGE
controller-57f648cb96-tjr9h   0/1     ContainerCreating   0          13s
speaker-5brl8                 0/1     ContainerCreating   0          13s
speaker-7bw54                 0/1     ContainerCreating   0          13s
speaker-m7qmh                 0/1     ContainerCreating   0          13s

ServieAccountやRBACの定義、それからpodもデプロイされる。

[zaki@cloud-dev ~]$ kc get pod -n metallb-system 
NAME                          READY   STATUS                       RESTARTS   AGE
controller-57f648cb96-tjr9h   1/1     Running                      0          31s
speaker-5brl8                 0/1     CreateContainerConfigError   0          31s
speaker-7bw54                 0/1     CreateContainerConfigError   0          31s
speaker-m7qmh                 0/1     CreateContainerConfigError   0          31s

エラーになってるけど「ConfigError」なので、次の手順の「On first install only」のsecret作成を実施。

[zaki@cloud-dev ~]$ kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)"
secret/memberlist created
[zaki@cloud-dev ~]$ kc get pod -n metallb-system -o wide
NAME                          READY   STATUS    RESTARTS   AGE   IP               NODE                        NOMINATED NODE   READINESS GATES
controller-57f648cb96-tjr9h   1/1     Running   0          92s   10.244.127.105   k8s-worker01.esxi.jp-z.jp   <none>           <none>
speaker-5brl8                 1/1     Running   0          92s   192.168.0.125    k8s-worker01.esxi.jp-z.jp   <none>           <none>
speaker-7bw54                 1/1     Running   0          92s   192.168.0.121    k8s-master01.esxi.jp-z.jp   <none>           <none>
speaker-m7qmh                 1/1     Running   0          92s   192.168.0.126    k8s-worker02.esxi.jp-z.jp   <none>           <none>

なるほど、secretを作成するとRunningになった。

ちなみにcontrollerはDeployment、speakerはDaemonSetから作成されている。

daemonset.apps/speaker created
deployment.apps/controller created
[zaki@cloud-dev ~]$ kc get ds -n metallb-system 
NAME      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR                 AGE
speaker   3         3         3       3            3           beta.kubernetes.io/os=linux   2m56s

そして次の手順は

The installation manifest does not include a configuration file. MetalLB’s components will still start, but will remain idle until you define and deploy a configmap.

ということなので、ConfigMapを作ろう。

configuration

MetalLB, bare metal load-balancer for Kubernetes

冒頭に書いた通りBGP喋れるルーターに心当たりがないので、Layer 2 Configurationを実施。

Layer 2 Configuration

次のマニフェストを用意。
addressesに指定するアドレスレンジは、「ノードOSのネットワーク内のアドレスでかつ、使用されていないアドレス、さらにDHCPも使用している場合はDHCPのリース範囲でもない」IPアドレス
平たく言うとネットワーク内で未使用のアドレスで今後も使用されないアドレス。

apiVersion: v1
kind: ConfigMap
metadata:
  namespace: metallb-system
  name: config
data:
  config: |
    address-pools:
    - name: default
      protocol: layer2
      addresses:
      - 192.168.0.181-192.168.0.200
[zaki@cloud-dev metallb]$ kc apply -f l2-configuration.yaml 
configmap/config created

これでざっと準備OKのはず。

サンプルのpod・svcを作ってみる

確認用の適当なHTTP podをデプロイし、type:LoadBalancer Serviceも作成してみる。

[zaki@cloud-dev src]$ kc create ns lb-sample
namespace/lb-sample created
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: sample-http
  name: sample-http
spec:
  replicas: 2
  selector:
    matchLabels:
      app: sample-http
  template:
    metadata:
      labels:
        app: sample-http
    spec:
      containers:
      - image: httpd
        name: httpd
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: sample-http
  name: sample-http
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: sample-http
  type: LoadBalancer

このマニフェストapplyする。

[zaki@cloud-dev sample]$ kc apply -f sample-http.yaml -n lb-sample 
deployment.apps/sample-http created
service/sample-http created
[zaki@cloud-dev sample]$ kc get pod,svc -n lb-sample
NAME                               READY   STATUS    RESTARTS   AGE
pod/sample-http-744f56bdc6-kf6nn   1/1     Running   0          14s
pod/sample-http-744f56bdc6-sjkvq   1/1     Running   0          14s

NAME                  TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
service/sample-http   LoadBalancer   10.97.101.65   192.168.0.181   80:32448/TCP   14s

おぉーー!!
External-IPを持ったtype:LoadBalancer Serviceがちゃんとデプロイされている!

[zaki@cloud-dev sample]$ curl 192.168.0.181
<html><body><h1>It works!</h1></body></html>

おおお、、、

ちゃんと動いているか、コンテンツをイジって確認してみる。

[zaki@cloud-dev sample]$ kc get pod -n lb-sample 
NAME                           READY   STATUS    RESTARTS   AGE
sample-http-744f56bdc6-kf6nn   1/1     Running   0          2m23s
sample-http-744f56bdc6-sjkvq   1/1     Running   0          2m23s
[zaki@cloud-dev sample]$ kc exec -n lb-sample -it sample-http-744f56bdc6-kf6nn -- bash
root@sample-http-744f56bdc6-kf6nn:/usr/local/apache2# cat htdocs/index.html 
<html><body><h1>It works!</h1></body></html>
root@sample-http-744f56bdc6-kf6nn:/usr/local/apache2# echo "<html><body><h1>It don't want to work</h1></body></html>" > htdocs/index.html 
root@sample-http-744f56bdc6-kf6nn:/usr/local/apache2#

片方のpodのみ内容を変更してcurl実行してみる。

[zaki@cloud-dev sample]$ curl 192.168.0.181
<html><body><h1>It works!</h1></body></html>
[zaki@cloud-dev sample]$ curl 192.168.0.181
<html><body><h1>It works!</h1></body></html>
[zaki@cloud-dev sample]$ curl 192.168.0.181
<html><body><h1>It don't want to work</h1></body></html>
[zaki@cloud-dev sample]$ curl 192.168.0.181
<html><body><h1>It don't want to work</h1></body></html>
[zaki@cloud-dev sample]$ curl 192.168.0.181
<html><body><h1>It works!</h1></body></html>

ちゃんと動いてます。


7/11追記:

この時の状態を図にするとこんな感じ。

f:id:zaki-hmkc:20200711115008p:plain

作成したConfigMapに定義しているアドレスレンジから払い出されたExternal IPを持つtype:LoadBalancer Serviceがネットワーク上に作成される形となり、外部(この場合ノードOSと同じネットワーク上にあるクラスタ外のホスト)からのアクセスをpodへ転送する。
dev-cloudからpodへ直接アクセスできない状態には変わりないが、podへのトラフィックをロードバランスするServiceへアクセスすることで、podへのネットワークアクセスが可能となる。