🐢

CloudShellでお試しするCluster API

2023/02/21に公開

Google Cloud Japan の RyuSA です。

みなさんは Kubernetes を利用していますか?昨今では開発環境から本番環境、はたまた自宅環境に至るまで様々な場所で Kubernetes が動いています。またセルフマネージドで Kubernetes を利用している方や Google Kubernetes Engine などのマネージド Kubernetes を利用している方もいるかと思います。Kubernetes クラスターは様々な場所で、様々な形態で起動しており、その構築方法や管理も様々でしょう。

Cluster API は「 Kubernetes を Kubernetes で管理する」ための Kubernetes サブプロジェクトのひとつです。Cluster API を利用することで、宣言的に記述されたマニフェストを使って様々な環境に Kubernetes をデプロイでき、一元的にマルチクラスターを管理できます。本記事では、オープンソースコミュニティが管理している Google Cloud 向けの Cluster API 実装である cluster-api-provider-gcp と Cloud Shell から Cluster API に触れていきます。

Cluster APIとcluster-api-provider-gcp

Cluster API は Kubernetes のコミュニティのひとつである kubernetes/sig-cluster-lifecycle がメンテナンスしている「 Kubernetes を Kubernetes で管理する」ためのサブプロジェクトです。Cluster API は専用の CRD である Cluster MachineDeployment KubeadmControlPlane を提供しています。管理者はそれらのカスタム リソースを駆使してターゲット クラスターを定義し、 Cluster API は定義されたカスタムリソースを元に Kubernetes を構築します。

また Kubernetes を構築するには、ロードバランサーとサーバーが必要です。Cluster API はそれらのリソースを Cluster API から作成するための Infrastructure Provider Specification を公開しています。 Infrastructure Provider Specification に沿ったコントローラー(Provider)を実装することで Cluster API から好きなインフラストラクチャーの API を操作できるようになります。kubernetes-sigs/cluster-api-provider-gcp はオープンソースコミュニティがメンテナンスしている、Google Cloud 向けの Infrastructure Provider です。この Provider を利用することで、Cluster API を使って GCE と Cloud LoadBalancer を作成して Kubernetes をプロビジョニングできます。

Cluster API のフロー

Cluster API を使って Kubernetes を動かすには大きく分けて4つのフェーズが必要です。

  1. プロジェクトセットアップ
  2. マネジメント クラスターを構築する
    • kind create cluster
    • clusterctl init
  3. ターゲット クラスターをデプロイする
    • clusterctl generate cluster
    • kubectl apply
  4. 見守る
    • :eyes:
  5. (Optional) Pivotする

Cloud Shell で Cluster API に触れてみる

それでは実際に Cluster API を Google Cloud で動かして、Kubernetes を起動してみましょう。まず Google Cloud ダッシュボードから Cloud Shell をアクティベートしておき、プロジェクトのセットアップを行ってからマネジメントクラスターを作成します。

プロジェクトセットアップ

まずは Cluster API で Kubernetes を利用するためのプロジェクトを作成し、またサービス アカウントとネットワークをセットアップしていきます。詳細は公式のリポジトリを参照してください。次のコマンドを実行すると Cluster API が読み取る環境変数の定義とプロジェクトを作成できます。

# 環境変数を定義
$ export GCP_REGION=asia-northeast1
$ export GCP_PROJECT=<GCP_PROJECT>
$ export KUBERNETES_VERSION=1.23.3
$ export GCP_CONTROL_PLANE_MACHINE_TYPE=n1-standard-2
$ export GCP_NODE_MACHINE_TYPE=n1-standard-2
$ export GCP_NETWORK_NAME=default
$ export CLUSTER_NAME=capi-gcp-cluster
# 後でサービスアカウントキーを出力するファイルを指定します
$ export GCP_CREDENTIAL_PATH="<path/to/gcp-credentials.json>"
$ export IMAGE_ID=projects/k8s-staging-cluster-api-gcp/global/images/cluster-api-ubuntu-2004-v1-23-3-nightly
# プロジェクトを作成
$ gcloud projects create $GCP_PROJECT

また cluster-api-provider-gcp が作成するGCE インスタンスにはデフォルトで外部IPが付与されていません。そのため事前に default ネットワーク上に Cloud NAT を準備しておきます。もしすでに NAT 環境がセットアップされているネットワークを利用する場合はスキップしてください。次のコマンドを実行すると Cloud Router と Cloud NAT を指定したネットワーク上に作成できます。

$ gcloud compute routers create "${CLUSTER_NAME}-myrouter" --project=${GCP_PROJECT} --region=${GCP_REGION} --network=${GCP_NETWORK_NAME}
Creating router [capi-gcp-cluster-myrouter]...done.   
NAME: capi-gcp-cluster-myrouter
REGION: asia-northeast1
NETWORK: default
$ gcloud compute routers nats create "${CLUSTER_NAME}-mynat" --project="${GCP_PROJECT}" --router-region="${GCP_REGION}" --router="${CLUSTER_NAME}-myrouter" --nat-all-subnet-ip-ranges --auto-allocate-nat-external-ips
Creating NAT [capi-gcp-cluster-mynat] in router [capi-gcp-cluster-myrouter]...done.

最後にサービス アカウントのセットアップをします。このサービス アカウント capi-administrator はマネジメント クラスター内からGoogle Cloud プロジェクトの各種リソースを操作するために利用されます。次のコマンドを実行するとサービス アカウント capi-administrator が作成され、ロール roles/editor が作成したサービス アカウントに紐つけられます。

$ gcloud iam service-accounts create capi-administrator --project=${GCP_PROJECT}
Created service account [capi-administrator].
$ gcloud projects add-iam-policy-binding ${GCP_PROJECT} --member serviceAccount:"capi-administrator@${GCP_PROJECT}.iam.gserviceaccount.com"  --role "roles/editor"
Updated IAM policy for project <GCP_PROJECT>.
bindings:
  - members: ...
# サービスアカウント キーを事前に指定したパスに出力
$ gcloud iam service-accounts keys create ${GCP_CREDENTIAL_PATH} 

マネジメント クラスターを作成する

Google Cloud ダッシュボードから Cloud Shell をアクティベートしておきます。Cluster API を開始するためのマネジメント クラスターを Cloud Shell 上に作成します。

Cloud Shell には元々 Docker がインストールされていることもあり、今回は Docker コンテナ上に Kubernetes を構築できる kind をマネジメント クラスターとして利用することにします。Minikube でも動かせますので、お好みに合わせて Minikube を利用してください。

$ kind create cluster
Creating cluster "kind" ...
 ✓ Ensuring node image (kindest/node:v1.25.3) 🖼 
 ✓ Preparing nodes 📦  
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind"
You can now use your cluster with:

kubectl cluster-info --context kind-kind

Have a nice day! 👋

$ kubectl version –short
Client Version: v1.26.0
Kustomize Version: v4.5.7
Server Version: v1.25.3

次に Cluster API を操作するためのコマンドラインツール clusterctl をインストールします。

$ curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.3.3/clusterctl-linux-amd64 -o clusterctl
$ sudo install -o root -g root -m 0755 clusterctl /usr/local/bin/clusterctl
$ clusterctl version
clusterctl version: &version.Info{Major:"1", Minor:"3", GitVersion:"v1.3.3", GitCommit:"09030092bf1e9891da4cf6b48b486cb76efaab82", GitTreeState:"clean", BuildDate:"2023-01-31T17:17:34Z", GoVersion:"go1.19.5", Compiler:"gc", Platform:"linux/amd64"}

ここまででマネジメント クラスターを初期化する準備が整いました。clusterctl とサービスアカウント キーを使ってマネジメント クラスターを初期化しましょう。次のコマンドで Google Cloud 向けの Provider である cluster-api-provider-gcp と Cluster API 標準の Provider をマネジメント クラスターにインストールできます。

$ export GCP_B64ENCODED_CREDENTIALS=$( cat "${GCP_CREDENTIAL_PATH}" | base64 | tr -d '\n' )
$ clusterctl init --infrastructure gcp
Fetching providers
Installing cert-manager Version="v1.11.0"
Waiting for cert-manager to be available...
Installing Provider="cluster-api" Version="v1.3.3" TargetNamespace="capi-system"
Installing Provider="bootstrap-kubeadm" Version="v1.3.3" TargetNamespace="capi-kubeadm-bootstrap-system"
Installing Provider="control-plane-kubeadm" Version="v1.3.3" TargetNamespace="capi-kubeadm-control-plane-system"
Installing Provider="infrastructure-gcp" Version="v1.2.1" TargetNamespace="capg-system"
Your management cluster has been initialized successfully!
You can now create your first workload cluster by running the following:
  clusterctl generate cluster [name] --kubernetes-version [version] | kubectl apply -f -

ターゲット クラスターをデプロイする

ターゲット クラスターを作成するためのマニフェストを生成します。先ほどインストールした clusterctl にはターゲット クラスターを作成するためのマニフェストを生成するコマンド clusterctl generate cluster が用意されています。次のコマンドでコントロール プレーンノード3台とワーカーノード1台の構成の Kubernetes を作成するマニフェストを生成できます。

$ clusterctl generate cluster $CLUSTER_NAME  --control-plane-machine-count=3  --worker-machine-count=1  > capi-on-gcp.yaml
$ less capi-on-gcp.yaml

生成されたマニフェストには次のようなカスタムリソース(CR)が定義されています。これらの CRD はマネジメント クラスターを初期化したタイミングで用意されており、それぞれのリソースを適切に定義することで必要なスペックの Kubernetes クラスターを作成できます。

  • cluster.x-k8s.io/v1beta1
    • Cluster
    • MachineDeployment
  • infrastructure.cluster.x-k8s.io/v1beta1
    • GCPMachineTemplate
    • GCPCluster

ターゲット クラスターのマニフェストをデプロイするとマネジメント クラスターにデプロイされているProviderたちが実際にクラスターを作成し始めます。

$ kubectl apply -f capi-on-gcp.yaml
cluster.cluster.x-k8s.io/capi-gcp-cluster created
gcpcluster.infrastructure.cluster.x-k8s.io/capi-gcp-cluster created
kubeadmcontrolplane.controlplane.cluster.x-k8s.io/capi-gcp-cluster-control-plane created
gcpmachinetemplate.infrastructure.cluster.x-k8s.io/capi-gcp-cluster-control-plane created
machinedeployment.cluster.x-k8s.io/capi-gcp-cluster-md-0 created
gcpmachinetemplate.infrastructure.cluster.x-k8s.io/capi-gcp-cluster-md-0 created
kubeadmconfigtemplate.bootstrap.cluster.x-k8s.io/capi-gcp-cluster-md-0 created

ターゲット クラスターを見守る

ターゲット クラスターが適切に作成されているか見てみましょう。Kubernetes のリソースとして ClusterKubeadmControlplane をチェックしてみましょう。私が検証した際はだいたい10分ほどでコントロール プレーンのステータスがINITIALIZEDになりました。次のコマンドを実行すると現在のクラスターの状況を確認できます。

$ kubectl get cluster
NAME               PHASE         AGE     VERSION
capi-gcp-cluster   Provisioned   6m38s   

$ kubectl get kubeadmcontrolplane
NAME                             CLUSTER            INITIALIZED   API SERVER AVAILABLE   REPLICAS   READY   UPDATED   UNAVAILABLE   AGE     VERSION
capi-gcp-cluster-control-plane   capi-gcp-cluster   true                                 3                  3         3             6m40s   v1.23.3

また、Google Cloud のコンソールから実際に作成された GCE インスタンスと Cloud LoadBalancer を確認できます。

作成できたターゲット クラスターを覗いてみましょう。Kubernetes にアクセスするためには Kubernetes によって署名された証明書が必要です。その証明書はマネジメント クラスターにSecretとして保存されています。kubectl コマンドで取得するか、clusterctl コマンドで取得できます。次のコマンドを実行すると、ターゲットクラスターにアクセスするためのファイルを取得できます。

# ターゲットクラスターのkubeconfigをローカルに保存
$ clusterctl get kubeconfig $CLUSTER_NAME > capi-on-gcp.kubeconfig
# kubectl get secret "${CLUSTER_NAME}-kubeconfig" -o jsonpath='{.data.value}' | base64 -d > capi-on-gcp.kubeconfig

# 取得したkubeconfigを指定してターゲットクラスターのノードをチェックする
$ kubectl get nodes --kubeconfig capi-on-gcp.kubeconfig
NAME                                   STATUS     ROLES                  AGE     VERSION
capi-gcp-cluster-control-plane-56bxs   NotReady   control-plane,master   8m31s   v1.23.3
capi-gcp-cluster-control-plane-clxxw   NotReady   control-plane,master   5m25s   v1.23.3
capi-gcp-cluster-control-plane-xd5lj   NotReady   control-plane,master   7m8s    v1.23.3
capi-gcp-cluster-md-0-w657q            NotReady   <none>                 7m13s   v1.23.3

このままでは各ノードが READY のステータスにはなりません。これはクラスターにまだ CNI がインストールされていないことが原因です。お好みの CNI をインストールしてクラスターを READY にセットアップしましょう。次のコマンドを実行すると、GitHub で公開されている Calico を CNI としてターゲット クラスターにインストールできます。

$ kubectl --kubeconfig=./capi-on-gcp.kubeconfig apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.24.1/manifests/calico.yaml

$ kubectl get kubeadmcontrolplane
NAME                             CLUSTER            INITIALIZED   API SERVER AVAILABLE   REPLICAS   READY   UPDATED   UNAVAILABLE   AGE   VERSION
capi-gcp-cluster-control-plane   capi-gcp-cluster   true          true                   3          3       3         0             10m   v1.23.3

$ kubectl get nodes --kubeconfig capi-on-gcp.kubeconfig
NAME                                   STATUS   ROLES                  AGE     VERSION
capi-gcp-cluster-control-plane-56bxs   Ready    control-plane,master   12m     v1.23.3
capi-gcp-cluster-control-plane-clxxw   Ready    control-plane,master   8m58s   v1.23.3
capi-gcp-cluster-control-plane-xd5lj   Ready    control-plane,master   10m     v1.23.3
capi-gcp-cluster-md-0-w657q            Ready    <none>                 10m     v1.23.

Cluster API で遊んでみる

さてマネジメント クラスターとターゲットクラスターを作成したところで少し Cluster API と cluster-api-provider-gcp で遊んでみましょう。
まずは試しにターゲット クラスターのワーカーノードの数を1から3に増やしてみましょう。ワーカーノードの数を変更するには、MachineDeployment リソースの .spec.replicas を変更してマニフェストをデプロイするだけです。

$ kubectl get machinedeployment
NAME                    CLUSTER            REPLICAS   READY   UPDATED   UNAVAILABLE   PHASE     AGE   VERSION
capi-gcp-cluster-md-0   capi-gcp-cluster   1          1       1         0             Running   32m   v1.23.3
$ # edit capi-on-gcp.yaml
$ cat capi-on-gcp.yaml 
...
apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
  name: capi-gcp-cluster-md-0
  namespace: default
spec:
-  replicas: 1
+  replicas: 3
...

$ kubectl apply -f capi-on-gcp.yaml
machinedeployment.cluster.x-k8s.io/capi-gcp-cluster-md-0 configured

$ kubectl get machinedeployment -w
NAME                    CLUSTER            REPLICAS   READY   UPDATED   UNAVAILABLE   PHASE     AGE   VERSION
capi-gcp-cluster-md-0   capi-gcp-cluster   3          3       3         0             Running   39m   v1.23.3

$ kubectl get nodes --kubeconfig capi-on-gcp.kubeconfig
NAME                                   STATUS   ROLES                  AGE   VERSION
capi-gcp-cluster-control-plane-56bxs   Ready    control-plane,master   40m   v1.23.3
capi-gcp-cluster-control-plane-clxxw   Ready    control-plane,master   37m   v1.23.3
capi-gcp-cluster-control-plane-xd5lj   Ready    control-plane,master   38m   v1.23.3
capi-gcp-cluster-md-0-q2c5t            Ready    <none>                 46s   v1.23.3
capi-gcp-cluster-md-0-w657q            Ready    <none>                 39m   v1.23.3
capi-gcp-cluster-md-0-zf8m8            Ready    <none>                 44s   v1.23.3

MachineDeployment リソースは Kubernetes における Deployment に相当するリソースで、Pod のレプリカを作成するのと同じようにリソースのスペック通りの GCE インスタンスを作成してくれます。
このように Cluster API では Kubernetes のノードや Kubeadm の設定などを宣言的なマニフェストとして管理できます。これはつまり ArgoCD や Flux2 などの GitOps ツールを使ってGoogle Cloud上に Kubernetes as a Service を構築することもできると思います。これは E2E テストやパフォーマンス試験などで使えそうですね。
一方でステートレスなワークロードのみを利用したい、もしくはターゲットクラスターの稼働時間が 24 時間以下の短期間になるなどの理由で GCE プリエンプティブルインスタンスを利用したい場合もあるでしょう。またディスクサイズやディスクタイプを変更したいこともあるでしょう。そういった Google Cloud の設定は GCPMachineTemplate に定義できます。

apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: GCPMachineTemplate
metadata:
  name: sample-template
  namespace: default
spec:
  template:
    spec:
      additionalDisks:
        - deviceType: pd-ssd
          size: 200
      additionalLabels:
        foo: bar
      additionalNetworkTags:
        - secure
      image: projects/k8s-staging-cluster-api-gcp/global/images/cluster-api-ubuntu-2004-v1-23-3-nightly
      instanceType: n1-standard-2
      ipForwarding: Enabled
      preemptible: true
      publicIP: false
      rootDeviceType: pd-ssd
      rootDeviceSize: 60
      serviceAccounts:
        email: ryusa@example.com
        scopes:
          - https://www.googleapis.com/auth/cloud-platform

クリーンアップ

作成したリソースなどを削除していきます。

ターゲット クラスターの削除

$ kubectl delete cluster $CLUSTER_NAME
$ rm capi-on-gcp.kubeconfig

マネジメント クラスターの削除

$ kind delete cluster

Cloud Router / Cloud NAT の削除

$ gcloud compute routers nats delete "${CLUSTER_NAME}-mynat" --project="${GCP_PROJECT}" \
--router-region="${GCP_REGION}" --router="${CLUSTER_NAME}-myrouter"
$ gcloud compute routers delete "${CLUSTER_NAME}-myrouter" --project="${GCP_PROJECT}" \
--region="${GCP_REGION}"

サービス アカウントの削除

$ gcloud iam service-accounts delete capi-administrator@${GCP_PROJECT}.iam.gserviceaccount.com
$ rm ${GCP_CREDENTIAL_PATH}

プロジェクトの削除

$ gcloud projects delete $GCP_PROJECT

最後に

Cluster API を利用することでベアメタルからクラウドまで、マネジメント クラスターと宣言的なマニフェストをベースに一括で管理できます。特にArgoCD や Flux2 などの GitOps ツールと連携したり CI 環境と統合することで、より柔軟な環境構築と継続的なオペレーションを遂行できるかと思います。
Cluster API と Google Cloud のチュートリアルは、公式ドキュメントに詳細が記載されています。ご参考ください。

Kubernetesは、いいぞ

Google Cloud Japan

Discussion