Hosted Control Plane構成のKubernetesクラスタ構築
この記事はkubernetes Advent Calendar 2025 の 21 日目の記事です。
まとめ
自宅サーバーのKubernetes環境を、VM主体の構成からベアメタル主体へ移行するにあたり、コントロールプレーンを別クラスタ上に配置するHosted Control Plane構成を検証した。 Cluster APIを用い、k0smotronをBootstrap/Control Plane/Infrastructure Providerとして採用し、GKE AutopilotをManagement Cluster、VPN接続した自宅ベアメタルをワーカーノードとする構成を構築した。Provider間の依存関係により想定通り組み合わせられないケースに注意が必要だったり、PXE boot整備の手間などの課題はあるが、柔軟な構成を実現できることを実証できた。
経緯
自宅サーバー上のKubernetesクラスタについて、今までKVMなどで立ち上げた仮想マシンをノードとして構築していたが、 大抵のワークロードをKubernetesクラスタ上にデプロイするようになり、Kubernetesノードと関係のないVMを利用したいことが少なくなっているので、 思い切ってベアメタルKubernetesクラスタに移行してみようと思い立った。
その際、コントロールプレーンノードについてはクラウドサービス(KaaS)上に構築してみることにした。 自宅環境は停電などで全停止することも珍しくないため、コントロールプレーンだけでも外に出しておきたいと考えたのと、 コントロールプレーン用のノードを用意するには物理サーバーがもったいなかったことと、あとは単なる興味があったのが理由だ。
今回そのような方針で構築するにあたって、Hosted Control Planeと呼ばれる構成を取り入れてみた。
Hosted Control Plane
概要
Kubernetesクラスタの構成要素として、まずクラスタにデプロイされたコンテナを稼働させるためのKubeletがインストールされたノードが必要になる。 それに加えて、Kubernetes API、そのバックエンドストレージとなるetcd、Podをどのノードに配置するかを決定するスケジューラーなども必要となり、これらは総じてコントロールプレーンと呼ばれている。 自宅環境やオンプレミス環境でKubernetesクラスタを構築する場合、コントロールプレーンはそれぞれクラスタ内のノード上にPodとしてデプロイする構成がおそらく一般的だと思う。
しかし、これらのコントロールプレーンをクラスタを構成するノードの外で稼働させる構成を取ることもでき、これはHosted Control Planeと呼ばれている。 Kubernetesのドキュメントの中 でも触れられているように、 マネージドKubernetesサービスや、旧来の構成は同様にコントロールプレーンをクラスタのノード外に持つ構成となるが、 ここでは特に他のKubernetesクラスタ上でコントロールプレーンを動かす構成に注目する。
一般的な構成
Hosted Control Plane
対応するCluster API Provider
Cluster API を利用すれば、Kubernetesクラスタの構築を自動化できるが、その中でHosted Control Plane構成を取ることもできる。 Cluster APIは様々な構成に対応できるよう、Providerと呼ばれるモジュールがいくつも存在しており、Hosted Control Plane構成に対応するControl Plane Providerもいくつかある。 例えば、以下が挙げられる。
他にもあるかもしれないが、簡単に調べた範囲ではこの3つが見つかった。ただし、3つとも検証したわけではない。
Cluster APIの注意点
Cluster APIのProviderは、役割に応じて以下の3つの種類がある。
- Bootstrap Provider: ノードをKubernetesクラスタに接続して使える状態にするために必要なデータを生成する
- Control Plane Provider: コントロールプレーンを構築する
- Infrastructure Provider: ノードのもととなる仮想マシンや物理サーバーを準備する
これらの詳細は、Cluster API の Contract で規定されており、 設計思想としては Bootstrap/Control Plane/Infrastructure Providerのそれぞれを独立して選び、組み合わせて使うことができる。
ただし、実際には完全に独立して組み合わせて使えるとは限らず、特定の組み合わせでないと動かないProviderも存在する。 例えば、Infrastructure ProviderのSideroは、ベアメタルサーバーをノードとして利用するために使えるProviderだが、 Talos Linuxと密接に依存しており、ControlPlane ProviderにもTalosを採用する必要がある。 (ノードがクラスタにjoinする際に、コントロールプレーンで稼働するtrustdというTalos固有のコンポーネントを利用するため、他のコントロールプレーンと組み合わせてもノードがクラスタに参加できない。)
今回最初にSideroを試してそうした点に気づいたが、事前に気づくのはなかなか難しく、構想中の構成が機能するかは実際に検証して判断したほうが良い点には注意が必要となる。
自宅環境のセットアップ例
構成
以下のCluster API Providerの組み合わせで構築してみた。
- Bootstrap Provider: k0smotron
- Control Plane Provider: k0smotron
- Infrastructure Provider: k0smotron (remote host)
k0smotronは、k0sというKubernetesディストリビューションを使ってKuberntesクラスタを構築する。 k0smotronはInfrastructure Providerも提供しており、これを使うと、ベアメタル含め好きなLinuxホストへのSSHの接続情報を含むカスタムリソースを作成することで、 そのホストをKubernetesノードとしてセットアップしてくれる。ベアメタルを扱うInfrastructure ProviderにはMetal3もあるが、 ドキュメントを見る限りではIPMIなどのBMCを前提にしているところがあり、BMCを持たないホストもある自宅環境にはマッチしないと判断した。
(ちなみに、k0smotronのInfrastructure Providerは、cloud-init用の形式で用意されたBootstrapConfigを、cloud-initにわたすのではなくProvider側で解釈し、 同等の操作をSSH経由で実行する実装になっているようで、cloud-initで指定できるうちの一部の設定だけに対応している様子だった。 そのため、他のBootstrap Providerと使う場合は相性問題が起こりやすいかもしれない。)
Cluster APIの稼働環境であり、構築するクラスタのコントロールプレーンのデプロイ先にもなるクラスタは、GKEのAutopilotクラスタにした。 自宅内のノードとなるサーバーがつながるネットワークとGKEのクラスタの間は、VPNを介して双方向に接続できるようにした。 また、構築が極力簡単になるよう、自宅内のサーバーはPXE bootを起点にiPXEを介してFlatcar Container Linuxが起動するように整え、 そのために必要なiPXEスクリプトやignitionファイルもGKEのクラスタ上にHTTPサーバーを立てて配布した。
構築手順
PXE bootの設定等に関する部分は本題ではないので、省略する。 GKEのクラスタがいわゆるManagement Clusterとして機能する形であり、そちらにCluster APIをセットアップしてからマニフェストを適用するだけなので、手順そのものはシンプルである。
まずManagement ClusterにCluster APIをインストールする。clusterctlを利用して実施しても良いが、ここではCluster API Operatorを利用した。
helm upgrade --install capi-operator capi-operator/cluster-api-operator --create-namespace -n capi-operator-system \
--set core.cluster-api.enabled=true --set core.cluster-api.version=v1.11.3 \
--set bootstrap.k0smotron.enabled=true --set bootstrap.k0smotron.version=v1.8.1 --set bootstrap.k0smotron.fetchConfig.url=https://github.com/k0sproject/k0smotron/releases/latest/bootstrap-components.yaml \
--set controlPlane.k0smotron.enabled=true --set controlPlane.k0smotron.version=v1.8.1 --set controlPlane.k0smotron.fetchConfig.url=https://github.com/k0sproject/k0smotron/releases/latest/control-plane-components.yaml \
--set infrastructure.k0smotron.enabled=true --set infrastructure.k0smotron.version=v1.8.1 --set infrastructure.k0smotron.fetchConfig.url=https://github.com/k0sproject/k0smotron/releases/latest/infrastructure-components.yaml
(これを試した時点では、cluster-api-operatorのChartの問題で、versionを指定せずにfetchConfigを指定するとHelmが生成するManifestが不正な形式になってしまったので、versionを直接指定している。)
その後、以下のようなManifestを適用することで、Managementクラスタ内にk0sによるコントロールプレーンがPodとして作成され、 指定したベアメタルサーバーにk0sがインストールされノードとして利用できるようになる。
apiVersion: v1
kind: Namespace
metadata:
name: my-cluster
---
apiVersion: cluster.x-k8s.io/v1beta2
kind: Cluster
metadata:
name: my-cluster
namespace: my-cluster
spec:
clusterNetwork:
pods:
cidrBlocks:
- 10.101.0.0/16
services:
cidrBlocks:
- 10.100.0.0/16
controlPlaneRef:
apiGroup: controlplane.cluster.x-k8s.io
kind: K0smotronControlPlane
name: my-cluster-cp
infrastructureRef:
apiGroup: infrastructure.cluster.x-k8s.io
kind: RemoteCluster
name: my-cluster-rc
---
apiVersion: controlplane.cluster.x-k8s.io/v1beta1
kind: K0smotronControlPlane
metadata:
name: my-cluster-cp
namespace: my-cluster
spec:
version: v1.34.2
persistence:
type: emptyDir
service:
type: LoadBalancer
annotations:
networking.gke.io/load-balancer-type: "Internal"
apiPort: 6443
konnectivityPort: 8132
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: RemoteCluster
metadata:
name: my-cluster-rc
namespace: my-cluster
spec:
---
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1
kind: K0sWorkerConfigTemplate
metadata:
name: my-cluster-worker
namespace: my-cluster
spec:
template:
spec:
version: v1.34.2+k0s.0
k0sInstallDir: /opt/k0s # Flatcar Container Linuxだと `/usr` がRead-onlyなため、パス変更が必要
---
apiVersion: cluster.x-k8s.io/v1beta2
kind: MachineDeployment
metadata:
name: my-cluster-workers
namespace: my-cluster
spec:
clusterName: my-cluster
replicas: 1
selector:
matchLabels: {}
template:
spec:
bootstrap:
configRef:
apiGroup: bootstrap.cluster.x-k8s.io
kind: K0sWorkerConfigTemplate
name: my-cluster-worker
clusterName: my-cluster
infrastructureRef:
apiGroup: infrastructure.cluster.x-k8s.io
kind: RemoteMachineTemplate
name: my-cluster-worker
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: RemoteMachineTemplate
metadata:
name: my-cluster-worker
namespace: my-cluster
spec:
template:
spec:
pool: default
---
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: PooledRemoteMachine
metadata:
name: my-cluster-worker-0
namespace: my-cluster
spec:
pool: default
machine:
address: 172.31.249.254 # ベアメタルサーバーのIPアドレス
port: 22
user: core
useSudo: true
sshKeyRef:
name: sshkey-my-cluster-worker-0
---
apiVersion: v1
kind: Secret
metadata:
name: sshkey-my-cluster-worker-0
namespace: my-cluster
type: Opaque
data:
value: LS0... # SSH秘密鍵をBase64エンコードしたもの
所感と課題
- Cluster APIを使いつつ、柔軟な構成が取れることを確かめられるとともに、Provider間の依存問題を知ることができた。
- Talosとの依存の都合でHosted Control Plane構成は取れず、今回断念したが、PXE bootなどのベアメタルサーバーセットアップの部分はSideroがよくできていた。
- PXE bootなどの整備をある程度自力でやることになったが、Sidero以外にInfrastructure Providerとしてそれらをサポートするものがないか、もう少し調べたい。
- 今回の構成はコントロールプレーンとノードがインターネットVPN経由での接続であり、それに伴う問題が起きる可能性もあるので、安定運用できるかはしばらく様子見したい。