クラウドサービスのマネージドK8sだと気軽に使ってしまうtype:LoadBalancer Service、kubeadmとかで作ったオンプレK8sだとそのままだと使えないんだけど、このツイートで「MetalLB」というのを知って(過去にも聞いてたことはあったかもしれないけど、、いろいろ認識が足りてなかったかも)試してみました。
2年近く前にこのようなことを呟いているが、今でもこのおすすめセットは変わりません
— junichi yoshise (@jyoshise) 2020年6月2日
いろいろ試す上でPVCは絶対必要になるし、Ingress使おうがIstio使おうがサービスにクラスタ外からアクセスしようとするとType:LoadBalancerはあると便利なので、まずはこのセットで下回り作ってからいろいろ試そう https://t.co/0IhFK6keH9
7.26追記:
firewalldを有効にしている状態でクラスタにMetalLBを導入すると、ノードOSをリブートした後に手元の環境ではLoadBalancer Serviceが機能しなくなりました。
構築後でもfirewalldを無効にすれば動作するので、検証環境なのでひとまずfirewalldをオフにして使用しています。
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を使用。
図にするとこんな感じ。
本記事内でkubectl
を実行している操作用の"cloud-dev"はK8sのノードではないがノードと同じネットワークにいる。
通常はpodへ直接通信することはできず、type:LoadBalancer Serviceを使わない場合はtype:NodePort Serviceを使うか、kubectl port-forward
を使うなどしてアクセスする必要がある。
要件
公式サイトより。
- 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追記:
この時の状態を図にするとこんな感じ。
作成したConfigMapに定義しているアドレスレンジから払い出されたExternal IPを持つtype:LoadBalancer Serviceがネットワーク上に作成される形となり、外部(この場合ノードOSと同じネットワーク上にあるクラスタ外のホスト)からのアクセスをpodへ転送する。
dev-cloudからpodへ直接アクセスできない状態には変わりないが、podへのトラフィックをロードバランスするServiceへアクセスすることで、podへのネットワークアクセスが可能となる。