Liqoのvirtual-kubeletでKubernetesクラスタからk3sにPodをオフロードする
Thu, Dec 9, 2021
Kubernetes k3s virtual-kubelet liqoTable of Contents
KubernetesクラスタにAdmiraltyのLiqoでk3sクラスタを仮想ノードとしてつなげてみた話。
Liqoとは
Liqoは、前回の記事で使ったAdmiraltyと同様のvirtual-kubelet実装で、Kubernetesクラスタのノードとして別のKubernetesクラスタを接続してくれるもの。
Liqoに目を付けたのは、AdmiraltyがEKSではKubernetes APIのバージョンの不整合(これとかこれ)でうまく動かない問題があるのに対し、開発が停滞気味で修正されるかあやしかったため。
Liqoはかなり活発に開発されていて、機能的にはAdmirartyを超えているっぽくて、最新のKubernetes APIに追従してくれている。
Liqoの仕組み
LiqoはAdmirartyと同様に、Kubernetesクラスタのノードとして別のKubernetesクラスタを接続する。 前者はHomeクラスタ、後者はForeignクラスタ、HomeクラスタにLiqoが作るノードは仮想ノードと呼ばれ、仮想ノードにスケジューリングされたPodがForeignクラスタのいずれかのノードで実行される。
Admiraltyは、ソースクラスタで特殊なスケジューラを動かして、PodChaperonを介してターゲットクラスタにPodをデプロイすることで、オフロードするPodがより確実に実行されるようにスケジューリングを頑張っている感じだったけど、Liqoはそのあたりは割とシンプル。 単に、仮想ノードにスケジューリングされたPodに対応するPodを、ForeignクラスタにReplicaSet経由で作るだけ。 ForeignクラスタにそのPodを実際に実行できるノードがあるとは限らないので、仮想ノードへのスケジューリングが不適当な結果になることもあるかも。
代わりに(?)Liqoが頑張っているのはPod間ネットワーク。 ForeignクラスタとHomeクラスタをVPNでつなぎ、それらのClusterIP間で通信できるようにしてくれるゲートウェイ機能をもっている。
このVPNは、TunnelEndpointというCRにしたがってTunnel Operatorがセットアップする作りになっている。 Tunnel Operatorが使うVPN実装は、現時点のLiqo 0.3.2ではWireGuardだけだけど、VPN実装を入れ替えられるプラガブルなアーキテクチャになっているので、頑張ってドライバを書けば別の実装を使える。
Liqoのコンポーネント
- auth
- controller-manager
- crd-replicator
-
いくつかのオペレータを実行し、VPNトンネルの確立とかNATとかの役割をする。
network-manager
route
webhook
Liqoをためす
二つのクラスタをLiqoでつなげて、Nginxを実行してみる。
環境
ためす環境はAdmiraltyのと同じで、Kubernetes 1.21.2とk3s 1.21.2による以下のようなもの。
k3sクラスタのマスタ(i.e. k3s server)がKubernetesクラスタのPodで動いていて、別マシンがk3sクラスタのノードになっている。
KubernetesクラスタをHomeクラスタ、k3sクラスタをForeignクラスタとする。
HomeクラスタへのLiqoインストール
HomeクラスタへのLiqoをいれる。 いれるのはv0.3.2。
GitHubにあるHelm Chartを取得。
[root@vm-1 ~]# git clone https://github.com/liqotech/liqo.git
[root@vm-1 ~]# cd liqo/deployments/liqo/
[root@vm-1 liqo]# git checkout v0.3.2
Chartのパラメータを指定するファイルを作る。
[root@vm-1 multicluster-scheduler]# cat <<EOF > values_override.yaml
apiServer:
address: "https://192.168.1.200:6443"
gateway:
service:
type: NodePort
networkManager:
config:
podCIDR: 10.32.0.0/16
serviceCIDR: 10.0.0.0/16
discovery:
config:
clusterName: k8s
auth:
service:
type: NodePort
storage:
enable: false
EOF
[root@vm-1 multicluster-scheduler]# find crds/ -name "*.yaml" | xargs -I {} kubectl apply -f {}
公式の手順ではhelm installするんだけど、なんとなくhelm templateでマニフェストをレンダリングしてapplyしたいので、Chart Hookを消しておく。
[root@vm-1 multicluster-scheduler]# rm -rf templates/post-delete/
IssuerとCertificateのAPIバージョンが古いので直す。 あとVMのリソースの都合でレプリカ数を減らす。
[root@vm-1 multicluster-scheduler]# sed -i -e "s/v1alpha2/v1/" templates/issuer.yaml
[root@vm-1 multicluster-scheduler]# sed -i -e "s/v1alpha2/v1/" templates/cert.yaml
[root@vm-1 multicluster-scheduler]# sed -i -e "s/replicas: 2/replicas: 1/" values.yaml
apply。
[root@vm-1 multicluster-scheduler]# helm template --name-template hoge . | kubectl apply -f -
[root@vm-1 multicluster-scheduler]# kubectl get po
NAME READY STATUS RESTARTS AGE
hoge-multicluster-scheduler-candidate-scheduler-7994c546d8cnm29 1/1 Running 0 22m
hoge-multicluster-scheduler-controller-manager-788dc65475-c764x 1/1 Running 0 22m
hoge-multicluster-scheduler-proxy-scheduler-8457f5f655-tzt6n 1/1 Running 0 22m
hoge-multicluster-scheduler-restarter-544df48444-k5cwv 1/1 Running 0 22m
k3s-57d95d5c47-sdzpl 1/1 Running 0 45h
これでソースクラスタでAdmiraltyが動いた。 candidate-schedulerが無駄に動いているのと、scheduler-restarterってなんだっけというのはあるけど、気にしない。
ターゲットクラスタへのAdmiraltyインストール
次にターゲットクラスタであるk3sクラスタにAdmiraltyを入れる。
大抵はターゲットクラスタでもソースクラスタと全く同じことをすればいいんだけど、今回の環境ではk3sのマスタ(i.e. k3s server)がPodで動いていて、別マシンのノード上で動くPodへの疎通がないので、ちょっと工夫が必要。
つまり、cert-managerとAdmiraltyをk3sクラスタに入れると、それらのWebhookサーバのPodがVM-2上で動くわけだけど、k3sクラスタのkube-apiserverはVM-1のPod内で動いていて、Webhookサーバに割り当てられたCluster IPと通信できないのでWebhookを実行できない、という問題を回避する必要がある。
ここでよく考えると、上記「Admiraltyの仕組み」で見たように、AdmiraltyのMutating Admission Webhookはソースクラスタでだけ使われるものなので、k3s側にはそもそも要らない。 Admiraltyが動かすWebhookは他にも、カスタムリソースのValidating AdmissionWebhookとCustomResource Conversion Webhookがあるんだけど、前者はカスタムリソースの登録・変更時にバリデーションするだけなので無くてもいいし、後者もカスタムリソースのAPIバージョンを複数サポートしたい場合に使うものなので要らない。
cert-managerはAdmiraltyのWebhookの証明書を管理する用なので、Webhookが要らないならcert-manager自体要らない。
というわけでそれらを除いてインストールする。 k3sクラスタへは、VM-1上で動くk3s server Podの中からkubeconfigを取り出してkubectlに渡せば接続できる。
[root@vm-1 multicluster-scheduler]# kubectl exec -it k3s-57d95d5c47-sdzpl -- /var/lib/rancher/k3s/data/57d64d4b123cea8e276484f00ab3dfa7178a00a35368aa6b43df3e3bd8ce032d/bin/cat /etc/rancher/k3s/k3s.yaml > /tmp/k3s.yaml
[root@vm-1 multicluster-scheduler]# sed -e "s/127.0.0.1/$(hostname -i)/" -i /tmp/k3s.yaml
[root@vm-1 multicluster-scheduler]# kubectl --kubeconfig /tmp/k3s.yaml get node
NAME STATUS ROLES AGE VERSION
vm-2.local Ready <none> 45h v1.21.5+k3s2
cert-managerは要らないのでスキップ。
AdmiraltyのChartからは、cert-managerのCR(i.e. IssureとCertificate)と、WebhookConfigurationを消しておく。
[root@vm-1 multicluster-scheduler]# rm -f templates/cert.yaml templates/issuer.yaml templates/webhook.yaml
cert-managerがCertificateに対して作るSecretはAdmiraltyのWebhookサーバがマウントして使うんだけど、cert-managerを入れてないのでそのSecretが作られない一方、Webhookサーバはmulticluster-scheduler-controller-manager Pod(i.e. multicluster-scheduler-agentプロセス)に他の必要なコントローラとごった煮になってて止められないので、multicluster-scheduler-controller-managerがSecretのマウントに失敗して落ちないように適当なSecretを作っておく。
ソースクラスタからCertificateのSecretを持ってきて名前だけ変えればいい。
[root@vm-1 multicluster-scheduler]# kubectl get secret hoge-multicluster-scheduler-cert -o yaml > /tmp/cert.yaml
[root@vm-1 multicluster-scheduler]# sed -e 's/hoge-multicluster-scheduler-cert/foo-multicluster-scheduler-cert/' -i /tmp/cert.yaml
[root@vm-1 multicluster-scheduler]# kubectl --kubeconfig /tmp/k3s.yaml apply -f /tmp/cert.yaml
Admiraltyをインストール。
[root@vm-1 multicluster-scheduler]# find crds/ -name "*.yaml" | xargs -I {} kubectl --kubeconfig /tmp/k3s.yaml apply -f {}
[root@vm-1 multicluster-scheduler]# helm template --name-template foo . | kubectl --kubeconfig /tmp/k3s.yaml apply -f -
[root@vm-1 multicluster-scheduler]# kubectl --kubeconfig /tmp/k3s.yaml get po
NAME READY STATUS RESTARTS AGE
foo-multicluster-scheduler-controller-manager-788dc65475-lwgbc 1/1 Running 0 96s
foo-multicluster-scheduler-candidate-scheduler-7994c546d89dggc 1/1 Running 0 96s
foo-multicluster-scheduler-proxy-scheduler-8457f5f655-g6f7s 1/1 Running 0 96s
foo-multicluster-scheduler-restarter-544df48444-7dzpx 1/1 Running 0 96s
ターゲットクラスタでもAdmiraltyが動いた。
Admiraltyの設定
次に、Admiraltyの設定として、ソースクラスタにターゲットクラスタを、ターゲットクラスタにソースクラスタを登録する。 それぞれTarget、SourceというCRを使う。
Targetには、ターゲットクラスタに接続するためのkubeconfigをいれたSecretを指定する。 本当は権限を絞ったServiceAccountで接続する、専用のkubeconfigを作らないとセキュリティ的にダメなんだけど、簡便にさっきk3s server Podの中から取り出したkubeconfigを使う。
[root@vm-1 ~]# kubectl create secret generic k3s-kubeconfig --from-literal=config="$(cat /tmp/k3s.yaml)"
[root@vm-1 ~]# cat <<EOF | kubectl apply -f -
apiVersion: multicluster.admiralty.io/v1alpha1
kind: Target
metadata:
name: k3s
spec:
kubeconfigSecret:
name: k3s-kubeconfig
EOF
Sourceには、ソースクラスタからの接続を認可するServiceAccountを指定する。
k3s server Podの中から取り出したkubeconfigのServiceAccount名はdefault
なので、それを指定して登録する。
[root@vm-1 ~]# cat <<EOF | kubectl --kubeconfig /tmp/k3s.yaml apply -f -
apiVersion: multicluster.admiralty.io/v1alpha1
kind: Source
metadata:
name: k8s
spec:
serviceAccountName: default
EOF
[root@vm-1 ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
admiralty-default-k3s Ready cluster 163m
vm-1.local Ready <none> 125d v1.21.2
Admiraltyによる仮想ノードであるadmiralty-default-k3s
がソースクラスタに追加された。
最後の設定として、Admiraltyによる仮想ノードへのスケジューリングを有効にするnamespaceを指定するため、namespaceにmulticluster-scheduler=enabled
というラベルを付ける。
今回はdefault
。
[root@vm-1 ~]# kubectl label ns default multicluster-scheduler=enabled
仮想ノードへのPodのデプロイ
仮想ノードにPodをスケジューリングしてもらうには、multicluster-scheduler=enabled
を付けたnamespaceに、multicluster.admiralty.io/elect: ""
というアノテーションを付けたPodを作ればいい。
今回はJobを使って、echoしたあとsleepするPodを作る。
[root@vm-1 ~]# cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: test
spec:
template:
metadata:
annotations:
multicluster.admiralty.io/elect: ""
spec:
containers:
- name: c
image: busybox
command: ["sh", "-c", "echo hogeeeee && sleep 100"]
restartPolicy: Never
EOF
[root@vm-1 ~]# kubectl get po -o "custom-columns=NAME:.metadata.name,NODE:.spec.nodeName"
NAME NODE
hoge-multicluster-scheduler-candidate-scheduler-7994c546d8cnm29 vm-1.local
hoge-multicluster-scheduler-controller-manager-56b5fcf8db-5jrdn vm-1.local
hoge-multicluster-scheduler-proxy-scheduler-855d6654f-gkscp vm-1.local
hoge-multicluster-scheduler-restarter-544df48444-k5cwv vm-1.local
k3s-57d95d5c47-sdzpl vm-1.local
test-cz5mp admiralty-default-k3s
登録したJobから作られたPodのtest-cz5mp
が、仮想ノードのadmiralty-default-k3s
にスケジューリングされた。
[root@vm-1 ~]# ps aux | grep -e '[s]leep'
[root@vm-1 ~]#
ソースクラスタのノード(i.e. VM-1)上でsleepプロセスは動いていない。
[root@vm-1 ~]# kubectl get po --kubeconfig /tmp/k3s.yaml
NAME READY STATUS RESTARTS AGE
foo-multicluster-scheduler-candidate-scheduler-7994c546d89dggc 1/1 Running 2 7h11m
foo-multicluster-scheduler-proxy-scheduler-8457f5f655-g6f7s 1/1 Running 2 7h11m
foo-multicluster-scheduler-restarter-544df48444-7dzpx 1/1 Running 4 7h11m
foo-multicluster-scheduler-controller-manager-788dc65475-lwgbc 1/1 Running 4 7h11m
test-cz5mp 1/1 Running 0 55s
ターゲットクラスタでtest-cz5mp
が動いている。
[root@vm-2 ~]# ps aux | grep -e '[s]leep'
root 56450 1.4 0.0 1300 4 ? Ss 18:27 0:00 sleep 100
ターゲットクラスタのノード(i.e. VM-2)上でsleepプロセスがちゃんと動いている。
[root@vm-1 ~]# kubectl logs test-cz5mp
hogeeeee
ソースクラスタのtest-cz5mp
に対してkubectl logsもできることが確認できた。