kindとMulticluster-SchedulerでつくるマルチK8sクラスター
※本記事はブログからの移行記事のため、一部内容が古くなっている場合があります(また時間があるときに改稿するかもしれません)
つい先日kind(Kubernetes in Docker)のv0.8.0がリリースされました。
以下は一例ですが、今バージョンでいくつかの機能追加や改修が行われています。
- 「kind」という名前のユーザー定義ネットワークがデフォルトでアタッチ
- Kubernetes 1.18対応
- Podmanの試験的サポート開始[1]
- kind delete clusters --allの導入でクラスター整理が楽に
個人的には特に最初の
- 「kind」という名前のユーザー定義ネットワークがデフォルトでアタッチ
という点に注目しました。
理由としては、元々デフォルトのDockerのブリッジネットワークを利用している場合、
以下のようにノード間で名前解決はできなかったため、kindクラスター間を跨いだ検証が面倒だったためです。
# コンテナ名で名前解決できない
root@kind-control-plane:/# ping -c 1 kind2-control-plane
ping: kind2-control-plane: Name or service not known
# ネットワーク疎通自体は取れる
root@kind-control-plane:/# ping -c 1 172.17.0.3
PING 172.17.0.3 (172.17.0.3) 56(84) bytes of data.
64 bytes from 172.17.0.3: icmp_seq=1 ttl=64 time=0.221 ms
--- 172.17.0.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.221/0.221/0.221/0.000 ms
その点はユーザー定義ネットワークを利用すれば、Dockerエンジン内蔵のDNSサーバーによってディスカバリできるようになるのですが、
v0.8.0未満のkindに対応する機能がないのがネックでした[2]。
今バージョンからその点は先述のように改善されているので、いちいちdocker inspectしてIPを確認しなくても、比較的楽にノードやクラスターを横断した検証ができるようになります。
よって、今回はぼんやり触りたいと思っていたMulticluster-Schedulerを絡めて、ローカル環境でマルチクラスター構築をしていきます。
Multicluster-Scheduler
Multicluster-Schedulerとは、Virtual Kubeletを応用したOSSのマルチクラスター用スケジューラーです。
こちらを利用すると、Source ClusterにデプロイしたPodが
Target Clusterの各ノードにもスケジュールされるようになるので、クラスターレベルの高い冗長性を得ることができます。
公式にもありますが、導入の大まかな流れは以下となります。
- Source Cluster(デプロイ先のクラスター)、Target Cluster(実際にPodが稼働するクラスター)の両方にMulticluster-Schedulerをインストール
- Podに専用のアノテーションを付与しつつデプロイ
- multicluster.admiralty.io/elect=""
- アノテーションが付与されたPodは状態管理用のダミーPodにMutateされ、実際に稼働するPodはTarget Clusterに作成される
- 稼働した後はFeedback Loopによって定期的に監視され、稼働中のPodのステータスをダミーPodに反映し続ける
ドキュメントはまだ少ないのですが、詳細は以下の資料が参考になります。
- Argo Workflowとの応用例
- シーケンス図
今回のゴール
こんな感じの構成をkindとMulticluster-Schedulerを使って作っていきます。
なお、図中のProxy Pod、Delegate Podに関しては後述します。
検証
今回説明しないこと
- クラスターを横断したトラフィックのルーティング
- Cilium CNIのCluster Meshをkind上で実現する手順については、今現在以下のIssueで進行中なので、このドキュメントがリリースされ次第試していきます
環境
- MacOS: Catalina
- kind: v0.8.1
- Kubernetes: v1.18.2
- Multicluster-Scheduler: v0.8.0
- Helm: v3.0.3
- Multicluster-Schedulerの推奨はv3
構築の流れ
- kindで二系統のKubernetesクラスターを構築
c1,c2という名前でそれぞれ構築します。
$ kind create cluster --name c1
Creating cluster "c1" ...
✓ Ensuring node image (kindest/node:v1.18.2) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-c1"
You can now use your cluster with:
kubectl cluster-info --context kind-c1
Thanks for using kind! 😊
$ kind create cluster --name c2
Creating cluster "c2" ...
✓ Ensuring node image (kindest/node:v1.18.2) 🖼
✓ Preparing nodes 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
Set kubectl context to "kind-c2"
You can now use your cluster with:
kubectl cluster-info --context kind-c2
Not sure what to do next? 😅 Check out https://kind.sigs.k8s.io/docs/user/quick-start/
- クラスターのコンテキスト名をシェル変数に定義
$ CLUSTER1=kind-c1
$ CLUSTER2=kind-c2
- cert-manager(=>v0.11)をインストール
Multicluster-Schedulerの要件となっているので、淡々とHelmでインストールします。
$ helm repo add jetstack https://charts.jetstack.io
"jetstack" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "flagger" chart repository
...Successfully got an update from the "admiralty" chart repository
...Successfully got an update from the "jetstack" chart repository
Update Complete. ⎈ Happy Helming!⎈
$ for CONTEXT in $CLUSTER1 $CLUSTER2
> do
> kubectl --context $CONTEXT apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.12/deploy/manifests/00-crds.yaml
> kubectl --context $CONTEXT create namespace cert-manager
> helm install cert-manager \
> --kube-context $CONTEXT \
> --namespace cert-manager \
> --version v0.12.0 \
> --wait \
> jetstack/cert-manager
> done
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
namespace/cert-manager created
NAME: cert-manager
LAST DEPLOYED: Sun May 3 22:32:36 2020
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager has been deployed successfully!
In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).
More information on the different types of issuers and how to configure them
can be found in our documentation:
https://docs.cert-manager.io/en/latest/reference/issuers.html
For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:
https://docs.cert-manager.io/en/latest/reference/ingress-shim.html
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
namespace/cert-manager created
NAME: cert-manager
LAST DEPLOYED: Sun May 3 22:33:17 2020
NAMESPACE: cert-manager
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
cert-manager has been deployed successfully!
In order to begin issuing certificates, you will need to set up a ClusterIssuer
or Issuer resource (for example, by creating a 'letsencrypt-staging' issuer).
More information on the different types of issuers and how to configure them
can be found in our documentation:
https://docs.cert-manager.io/en/latest/reference/issuers.html
For information on how to configure cert-manager to automatically provision
Certificates for Ingress resources, take a look at the `ingress-shim`
documentation:
https://docs.cert-manager.io/en/latest/reference/ingress-shim.html
- Multicluster-Schedulerをインストール
$ helm repo add admiralty https://charts.admiralty.io
"admiralty" has been added to your repositories
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "admiralty" chart repository
...Successfully got an update from the "flagger" chart repository
...Successfully got an update from the "jetstack" chart repository
Update Complete. ⎈ Happy Helming!⎈
$ kubectl --context "$CLUSTER1" create namespace admiralty
namespace/admiralty created
$ helm install multicluster-scheduler admiralty/multicluster-scheduler \
> --kube-context "$CLUSTER1" \
> --namespace admiralty \
> --version 0.8.0 \
> --set clusterName=c1 \
> --set targetSelf=true \
> --set targets[0].name=c2
NAME: multicluster-scheduler
LAST DEPLOYED: Sun May 3 22:37:49 2020
NAMESPACE: admiralty
STATUS: deployed
REVISION: 1
TEST SUITE: None
$ kubectl --context "$CLUSTER2" create namespace admiralty
namespace/admiralty created
$ helm install multicluster-scheduler admiralty/multicluster-scheduler \
> --kube-context "$CLUSTER2" \
> --namespace admiralty \
> --version 0.8.0 \
> --set clusterName=c2
NAME: multicluster-scheduler
LAST DEPLOYED: Sun May 3 22:37:53 2020
NAMESPACE: admiralty
STATUS: deployed
REVISION: 1
TEST SUITE: None
- サービスアカウントの作成
Source ClusterからTarget Clusterに接続する際に使用するサービスアカウントを作成します。
普通に作成しても良いのですが、今回は公式ドキュメントにもあるklum(Kubernetes Lazy User Manager)で作成していきます。
本旨から少し外れますが、klumを使うと、(Cluster)RoleBindingの設定やサービスアカウントに対応したkubeconfigのダウンロードなどを通常よりも非常に楽に行うことができます。
[https://github.com/ibuildthecloud/klum:embed:cite]
# klumインストール
$ kubectl --context "$CLUSTER2" apply -f https://raw.githubusercontent.com/ibuildthecloud/klum/v0.0.1/deploy.yaml
namespace/klum created
deployment.apps/klum created
serviceaccount/klum created
clusterrolebinding.rbac.authorization.k8s.io/klum-cluster-admin created
# ユーザー作成
$ cat <<EOF | kubectl --context "$CLUSTER2" apply -f -
> kind: User
> apiVersion: klum.cattle.io/v1alpha1
> metadata:
> name: c1
> spec:
> clusterRoles:
> - multicluster-scheduler-source
> EOF
user.klum.cattle.io/c1 created
# 確認
$ kubectl --context "$CLUSTER2" get sa -A|grep c1
klum c1 1 9m46s
$ kubectl --context "$CLUSTER2" get clusterrolebinding -A|grep c1
klum-c1-multicluster-scheduler-source-406318a4 ClusterRole/multicluster-scheduler-source 9m57s
- kubemcsaのインストール
kubemcsaとは、Admiraltyが公開している別のOSSのMulticluster-Service-Account用のCLIツールです。
今回はMulticluster-Service-Accountには触れませんが、kubemcsaを利用すると、先ほどklumで作成したサービスアカウントへの接続情報を容易にSecretに書き出すことができます。
$ MCSA_RELEASE_URL=https://github.com/admiraltyio/multicluster-service-account/releases/download/v0.6.1
# linux,armの時は変える
$ OS=darwin
$ ARCH=amd64
$ curl -Lo kubemcsa "$MCSA_RELEASE_URL/kubemcsa-$OS-$ARCH"
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 634 100 634 0 0 1209 0 --:--:-- --:--:-- --:--:-- 1207
100 33.5M 100 33.5M 0 0 1356k 0 0:00:25 0:00:25 --:--:-- 4851k
$ chmod +x kubemcsa
$ mv kubemcsa /usr/local/bin/
# 確認
$ kubemcsa --version
0.6.1
- サービスアカウントのエクスポート
対向クラスターから見た時はもう一方への接続情報となるので、
Target Cluster(c2)から抜いたSecretを--as c2
しつつ保存します。
$ kubemcsa export --context "$CLUSTER2" -n klum c1 --as c2 >> c2.yaml
- エクスポートしたSecretを編集
通常のクラスターであればエクスポートしたSecretをそのままSource Cluster(c1)にapplyすればOKですが、
kindの場合だとアクセス元がDockerホストかDockerネットワーク内の別コンテナからなのかで疎通可能なkube-apiserverのエンドポイントが異なる[3]ので、
編集する必要があります。
具体的にはsecret.data.configとsecret.data.serverの2つを編集していきます。
# secret.data.config
$ echo '${元々のsecret.data.config}'|base64 -d >> c2-kubeconfig.yaml
c2-kubeconfig.yamlのconfig.clusters.cluster.serverを編集
- server: https://127.0.0.1:xxxxx
+ server: https://c2-control-plane:6443
$ cat c2-kubeconfig.yaml |base64
エンコードした結果をc2.yamlのsecret.data.configにペースト
# secret.data.server
$ echo 'https://c2-control-plane:6443'|base64
エンコードした結果をc2.yamlのsecret.data.serverにペースト
- c2.yamlの編集が終わったらSource Cluster(c1)にapply
$ kubectl --context "$CLUSTER1" -n admiralty apply -f c2.yaml
secret/c2 created
- Multicluster-Schedulerの各コンポーネントを再起動
Secretを読み直します。
$ kubectl --context "$CLUSTER1" -n admiralty rollout restart deploy/multicluster-scheduler-candidate-scheduler deploy/multicluster-scheduler-controller-manager deploy/multicluster-scheduler-proxy-scheduler
deployment.apps/multicluster-scheduler-candidate-scheduler restarted
deployment.apps/multicluster-scheduler-controller-manager restarted
deployment.apps/multicluster-scheduler-proxy-scheduler restarted
# Runningするまで待機
$ kubectl --context "$CLUSTER1" -n admiralty get po -w
NAME READY STATUS RESTARTS AGE
multicluster-scheduler-candidate-scheduler-dbb5bbbcc-pt4dx 1/1 Running 0 10s
multicluster-scheduler-controller-manager-665dcdb7cb-b8zvq 1/1 Running 0 9s
multicluster-scheduler-proxy-scheduler-6d95bcbbc8-btmhn 1/1 Running 0 9s
- Virtual Kubelet確認
以下のようにadmiralty-c1とc2がReadyしていればOKです。
$ kubectl --context "$CLUSTER1" get node
NAME STATUS ROLES AGE VERSION
admiralty-c1 Ready agent 94s
admiralty-c2 Ready agent 94s
c1-control-plane Ready master 155m v1.18.2
動作確認
- default namespaceでMulticluster-Schedulerを有効化
$ kubectl --context "$CLUSTER1" label namespace default multicluster-scheduler=enabled
namespace/default labeled
$ kubectl --context "$CLUSTER1" get ns --show-labels
NAME STATUS AGE LABELS
admiralty Active 137m <none>
cert-manager Active 142m <none>
default Active 159m multicluster-scheduler=enabled
kube-node-lease Active 159m <none>
kube-public Active 159m <none>
kube-system Active 159m <none>
local-path-storage Active 159m <none>
- アノテーションを付与したNginxをデプロイ
$ cat <<EOF | kubectl --context "$CLUSTER1" apply -f -
> apiVersion: apps/v1
> kind: Deployment
> metadata:
> name: nginx
> spec:
> replicas: 10
> selector:
> matchLabels:
> app: nginx
> template:
> metadata:
> labels:
> app: nginx
> annotations:
> multicluster.admiralty.io/elect: ""
> spec:
> containers:
> - name: nginx
> image: nginx
> resources:
> requests:
> cpu: 100m
> memory: 32Mi
> ports:
> - containerPort: 80
> EOF
deployment.apps/nginx created
- 確認
$ kubectl --context "$CLUSTER1" get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-5466f8ddf7-4knlk 1/1 Running 0 52s 10.244.0.15 admiralty-c1 <none> <none>
nginx-5466f8ddf7-4knlk-c7jxn 1/1 Running 0 50s 10.244.0.15 c1-control-plane <none> <none>
nginx-5466f8ddf7-4m589 1/1 Running 0 52s 10.244.0.18 admiralty-c1 <none> <none>
nginx-5466f8ddf7-4m589-vn2l2 1/1 Running 0 44s 10.244.0.18 c1-control-plane <none> <none>
nginx-5466f8ddf7-8wwhb 1/1 Running 0 52s 10.244.0.12 admiralty-c2 <none> <none>
nginx-5466f8ddf7-d29d2 1/1 Running 0 52s 10.244.0.17 admiralty-c1 <none> <none>
nginx-5466f8ddf7-d29d2-rtxtc 1/1 Running 0 47s 10.244.0.17 c1-control-plane <none> <none>
nginx-5466f8ddf7-gp2gl 1/1 Running 0 52s 10.244.0.16 admiralty-c2 <none> <none>
nginx-5466f8ddf7-lwj7k 1/1 Running 0 52s 10.244.0.14 admiralty-c2 <none> <none>
nginx-5466f8ddf7-msnc7 1/1 Running 0 52s 10.244.0.16 admiralty-c1 <none> <none>
nginx-5466f8ddf7-msnc7-6qb8p 1/1 Running 0 49s 10.244.0.16 c1-control-plane <none> <none>
nginx-5466f8ddf7-pc7qk 1/1 Running 0 52s 10.244.0.19 admiralty-c1 <none> <none>
nginx-5466f8ddf7-pc7qk-nksc2 1/1 Running 0 42s 10.244.0.19 c1-control-plane <none> <none>
nginx-5466f8ddf7-vqsgk 1/1 Running 0 52s 10.244.0.15 admiralty-c2 <none> <none>
nginx-5466f8ddf7-xrtvt 1/1 Running 0 52s 10.244.0.13 admiralty-c2 <none> <none>
$ kubectl --context "$CLUSTER2" get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-5466f8ddf7-8wwhb-6xrhs 1/1 Running 0 70s 10.244.0.12 c2-control-plane <none> <none>
nginx-5466f8ddf7-gp2gl-2fmzb 1/1 Running 0 59s 10.244.0.16 c2-control-plane <none> <none>
nginx-5466f8ddf7-lwj7k-5xx5b 1/1 Running 0 65s 10.244.0.14 c2-control-plane <none> <none>
nginx-5466f8ddf7-vqsgk-kbhdf 1/1 Running 0 64s 10.244.0.15 c2-control-plane <none> <none>
nginx-5466f8ddf7-xrtvt-r8zmk 1/1 Running 0 67s 10.244.0.13 c2-control-plane <none> <none>
上手く設定されていれば、上記のように計20個のPodが2つのクラスター上でRunningします。
ここで、replicasは10なのに何故20個?と思った方もいるかと思いますが、半分はProxy Pod、もう半分はDelegate Podです。
Proxy Podとはプレースホルダー的な状態管理用のPodのことで、Delegate Podとは実際に稼働するPodのことを指します。
例えば以下のように名前が似ているPodがそれぞれのクラスターで動作していますが、
この名前が短く、クラスターと対応したVirtual NodeにスケジュールされているのがProxy Podであり、
実Nodeにスケジュールされ、Suffixが付与されているのがDelegate Podです。
nginx-5466f8ddf7-vqsgk 1/1 Running 0 52s 10.244.0.15 admiralty-c2 <none> <none>
nginx-5466f8ddf7-vqsgk-kbhdf 1/1 Running 0 64s 10.244.0.15 c2-control-plane <none> <none>
このように、各Targetクラスターを抽象化した仮想ノードにダミーPodをスケジュールさせ、対応したTargetクラスターに実Podの作成をHookさせる形で、
マルチクラスターなスケジューリングを可能にしています。
まとめ
今回はkind上でクラスターワイドなスケジューリングを行うところまでを確認していきました。
もしリソースに余裕があれば、マルチノードのクラスターを複数系統たてて障害テストを行うと、より動きが分かって面白いかもしれません。
次回以降は複数クラスターへのトラフィックの振り分けをはじめ、
NodeSelectorやAffinity、クラスターレベルのdrainなどもまとめていく予定です。
※内容に誤りがあったらご指摘いただけたら嬉しいです
Discussion