Crossplaneで自らが動作するGKE Clusterを作成・運用する方法

2023/01/08に公開

内容

本記事では、Crossplaneが動作するGKEクラスターを作成します。
また、そのGKEクラスター自身もGKEクラスター上で動作するCrossplaneで運用し、 Crossplaneが動作するKubernetesクラスターをそのCrossplane自身で管理する構成 を作成していきます。

詳細

CrossplaneはKubernetes上で動作するアプリケーションのため、GKEクラスターを作成するためには、そもそもCrossplaneが動作するKubernetesクラスターが必要です。しかし、そのKubernetesクラスターを作成するためにはCrossplaneが必要です。この 卵が先か鶏が先か問題 を解決するために、以下のフローでCrossplaneが動作するGKEクラスターを作成します。

  • kindを使って、Crossplaneが動作するローカルkubernetesクラスターを作成する
  • そのローカルクラスターから、CrossplaneでGKEを作成する
  • そのGKEに対して再度Crossplaneをinstallし、ローカルクラスターに適用したmanifestファイルをGKEにも適用する
  • kindクラスターを削除する
    • CrossplaneのdeletionPolicyにOrphanを指定しておくことで、kindクラスターで作成したGKEクラスターを削除せずkindクラスターのみを削除し、GKEクラスターで動作するCrossplaneに自分自身が動くGKEクラスターの管理を委譲することが出来ます

ローカル環境構築

まずはkindを使って、ローカル環境でKubernetesクラスターを作成し、Crossplaneを動作させます。(minikube等でも大丈夫です。)

Kindを使ってローカルkubernetesクラスターを作成する

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

kubectl cluster-info --context kind-crossplane-local

Have a nice day! 👋

install Crossplane

helmを使って、Crossplaneをinstallします。

$ kubectl create namespace crossplane-system
$ helm repo add crossplane-stable https://charts.crossplane.io/stable
$ helm repo update

$ helm install crossplane --namespace crossplane-system crossplane-stable/crossplane

install CLI

crossplane CLIがローカルにinstallされていない場合は、installします。

$ curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh

これで、ローカルKubernetesクラスターでCrossplaneを動作させる準備が整いました。

GCP Providerをセットアップする

まずは、ローカルのKubernetesクラスターでCrossplaneからGCPのリソースを操作できるようにするために、GCP ProviderをCrossplaneに導入します。

Service Accountを作成する

Crossplane用のGCP Service Accountを作成します。

GCPの管理画面から、

  • Compute Network Admin
  • Kubernetes Engine Admin
  • Service Account User

の権限をもったServiceAccountを作成してください。

Service AccountのKeyをKubernetesのSecretに登録します。

先ほど作成したServiceAccountのkeyを発行し、base64加工してKubernetes Secretに登録します。

$ gcloud iam service-accounts keys create credentials.json --iam-account="${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com" --project $PROJECT_ID
$ GCP_CREDS=$(base64 credentials.json | tr -d "\n")

secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: gcp-credential
  namespace: crossplane-system
type: Opaque
data:
  creds: $GCP_CREDS

※githubで管理する場合は、Sealed Secret等を使ってください

$ kubectl apply -f secret.yaml
...

$ kubectl get secret -n crossplane-system
NAME                               TYPE                 DATA   AGE
gcp-credential                     Opaque               1      19h
sh.helm.release.v1.crossplane.v1   helm.sh/release.v1   1      19h

GCP ProviderをKubernetesにapplyする

最後に、GCP ProviderをKubernetesにapplyします。

gcp-provider.yaml

---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
  name: provider-gcp
spec:
  package: crossplane/provider-gcp:v0.21.0
---
apiVersion: gcp.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
  name: default
spec:
  projectID: <PROJECT_ID>
  credentials:
    source: Secret
    secretRef:
      name: gcp-credential
      namespace: crossplane-system
      key: creds
$ kubectl apply -f gcp-provider.yaml

Providerを確認する

正常にGCP Providerがapplyされたかどうかを確認します。

$ kubectl get provider
NAME                                       PROJECT-ID       AGE
providerconfig.gcp.crossplane.io/default   <PROJECT-ID>     74s

ローカルKubernetsクラスターからGKEを作成する

次に、ローカルKubernetesクラスターからGKEを作成します。

※もし、GCPのprojectで以下のAPIが有効になっていない場合は、あらかじめ有効化をお願いします。

  • Kubernetes Engine API
  • Compute Engine API

network作成

GKEを作成するネットワークをCrossplaneで作成します。

network.yaml

apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
  name: main-network
spec:
  deletionPolicy: "Orphan"
  forProvider:
    autoCreateSubnetworks: false
    description: 'Main Network created by Crossplane'
    routingConfig: 
      routingMode: 'REGIONAL'
---
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Subnetwork
metadata:
  name: subnet-1
spec:
  deletionPolicy: "Orphan"
  forProvider:
    ipCidrRange: '10.0.0.0/20'
    networkRef: 
      name: main-network
    region: asia-northeast1

上記のような形で、サブネット等を作成し、GKEが動作するnetworkを作成します(CIDR等は自由に変更してください)

$ kubectl apply -f network.yaml

以下のコマンドで正常に作成されたかを確認できます。

$ kubectl get managed         
NAME                                            READY   SYNCED
subnetwork.compute.gcp.crossplane.io/subnet-1   True    True

NAME                                             READY   SYNCED
network.compute.gcp.crossplane.io/main-network   True    True

deletionPolicyを忘れない

先ほど作成したVPCとSubnetのmanaged resourceには deletionPolicy: "Orphan" を付与しています。 このstatusはCrossplane上から対象のresourceを削除してもGCP上からは削除しないという設定のために必要で、この設定がないとローカルKubernetesクラスターからGKEクラスターにCrossplaneを移行する際に削除処理が走ってしまいます。

GKEクラスターを作成する

gke.yaml

---
apiVersion: container.gcp.crossplane.io/v1beta2
kind: Cluster
metadata:
  name: crossplane-cluster
spec:
  deletionPolicy: "Orphan"
  forProvider:
    initialClusterVersion: latest
    network: "projects/<PROJECT_ID>/global/networks/main-network"
    subnetwork: "projects/<PROJECT_ID>/regions/asia-northeast1/subnetworks/subnet-1"
    ipAllocationPolicy:
      useIpAliases: true
    defaultMaxPodsConstraint:
      maxPodsPerNode: 100
    addonsConfig:
      gcePersistentDiskCsiDriverConfig:
        enabled: true
    location: asia-northeast1
    monitoringService: monitoring.googleapis.com/kubernetes
    loggingService: logging.googleapis.com/kubernetes
  writeConnectionSecretToRef:
    name: crossplane-cluster
    namespace: default
---
apiVersion: container.gcp.crossplane.io/v1beta1
kind: NodePool
metadata:
  name: crossplane-nodepool
spec:
  deletionPolicy: "Orphan"
  forProvider:
    autoscaling:
      autoprovisioned: false
      enabled: true
      minNodeCount: 1
      maxNodeCount: 2
    cluster: projects/<PROJECT_ID>/locations/asia-northeast1/clusters/crossplane-cluster
    config:
      serviceAccount: crossplane-sa@<PROJECT_ID>.iam.gserviceaccount.com
      diskSizeGb: 30
      diskType: pd-ssd
      imageType: cos_containerd
      labels:
        created_by: crossplane
      machineType: e2-medium
      oauthScopes:
        - "https://www.googleapis.com/auth/devstorage.read_only"
        - "https://www.googleapis.com/auth/logging.write"
        - "https://www.googleapis.com/auth/monitoring"
        - "https://www.googleapis.com/auth/servicecontrol"
        - "https://www.googleapis.com/auth/service.management.readonly"
        - "https://www.googleapis.com/auth/trace.append"
        - "https://www.googleapis.com/auth/compute"
    initialNodeCount: 1
    locations:
      - asia-northeast1-a
      - asia-northeast1-b
      - asia-northeast1-c

diskSizeやlocations等は自由に変更してください。また、GKE用のServiceAccountも作成し、serviceAccountの項目で紐づけてください。

$ kubectl apply -f gke.yaml

以下のコマンドで正常稼働しているか確認します。

$ kubectl get managed
NAME                                            READY   SYNCED
subnetwork.compute.gcp.crossplane.io/subnet-1   True    True

NAME                                             READY   SYNCED
network.compute.gcp.crossplane.io/main-network   True    True

NAME                                                     READY   SYNCED   STATE     ENDPOINT       LOCATION          AGE
cluster.container.gcp.crossplane.io/crossplane-cluster   True    True     RUNNING   34.146.81.14   asia-northeast1   12m

NAME                                                       READY   SYNCED   STATE     CLUSTER-REF   AGE
nodepool.container.gcp.crossplane.io/crossplane-nodepool   True    True     RUNNING                 12m

先ほど作ったネットワーク群だけでなく、GKEに関するクラスターとノードプールも正常に作成されていることがわかります。

これで、ローカルKubernetesクラスターで動作するCrossplaneでGKEを作成・管理することができました。

GKEにCrossplaneをInstallする

GKEクラスターの作成が完了したため、次は、作成したGKEクラスターにCrossplaneをinstallします。

GKEクラスターのcredentialを取得

$ gcloud container clusters get-credentials crossplane-cluster --region asia-northeast1 --project $PROJECT_ID

これでGKEクラスターに対して、kubectlコマンドを実行できるようになりました。

GKEにCrossplaneをInstallする

先ほど、kindクラスターに実行したのと同じようにHelmを利用して、GKEにもCrossplaneをinstallします。

$ kubectl create namespace crossplane-system
$ helm repo add crossplane-stable https://charts.crossplane.io/stable
$ helm repo update

$ helm install crossplane --namespace crossplane-system crossplane-stable/crossplane

GKEにもkindと同じmanifestファイルをapplyする

GKEにもkindに適用したmanifestファイルを適用します。

$ kubectl apply -f secret.yaml
$ kubectl apply -f gcp-provider.yaml
$ kubectl apply -f network.yaml
$ kubectl apply -f gke.yaml

すべてのリソースが正常に連携されているかを確認する

kindクラスターから作成されたリソースとGKE上のCrossplaneのmanaged resourcesが紐づけられていることを確認します。

$ kubectl get managed
NAME                                            READY   SYNCED
subnetwork.compute.gcp.crossplane.io/subnet-1   True    True

NAME                                             READY   SYNCED
network.compute.gcp.crossplane.io/main-network   True    True

NAME                                                     READY   SYNCED   STATE     ENDPOINT       LOCATION          AGE
cluster.container.gcp.crossplane.io/crossplane-cluster   True    True     RUNNING   34.146.81.14   asia-northeast1   31s

NAME                                                       READY   SYNCED   STATE     CLUSTER-REF   AGE
nodepool.container.gcp.crossplane.io/crossplane-nodepool   True    True     RUNNING                 31s

ローカルKubernetes(kind)クラスターを削除する

いままでの操作で、

  • ローカルKubernetes(kind)クラスターで動作するCrossplaneからGKEを作成する
  • 作成されたGKE上で動作するCrossplanekindクラスター上のCrossplaneが作成したリソース を紐づける

ことが完了しました。

そのため、現時点で、kindクラスターとGKEクラスターで同一のGCPリソースを管理していることになります。
この状態でkindクラスターだけを削除すると、GKEクラスター上で動作しているCrossplaneが自分自身が動作するGKEリソースを管理する状態が完成します。

kindクラスターを削除

$ kind delete cluster --name crossplane-local

GKEクラスターにリソースを追加してみる

最後に試しにGKEクラスターに対して、リソースを追加してみます。(VPCにサブネットをもう一つ追加)

network.yaml

apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Network
metadata:
  name: main-network
spec:
  deletionPolicy: "Orphan"
  forProvider:
    autoCreateSubnetworks: false
    description: 'Main Network created by Crossplane'
    routingConfig: 
      routingMode: 'REGIONAL'
---
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Subnetwork
metadata:
  name: subnet-1
spec:
  deletionPolicy: "Orphan"
  forProvider:
    ipCidrRange: '10.0.0.0/20'
    networkRef: 
      name: main-network
    region: asia-northeast1
---
apiVersion: compute.gcp.crossplane.io/v1beta1
kind: Subnetwork
metadata:
  name: subnet-2
spec:
  deletionPolicy: "Orphan"
  forProvider:
    ipCidrRange: '10.0.16.0/20'
    networkRef: 
      name: main-network
    region: asia-northeast1

※subnet-2を追加

$ kubectl apply -f network.yaml

正常にSubentworkが作成されたかを確認

$ kubectl get managed
NAME                                             READY   SYNCED
network.compute.gcp.crossplane.io/main-network   True    True

NAME                                            READY   SYNCED
subnetwork.compute.gcp.crossplane.io/subnet-1   True    True
subnetwork.compute.gcp.crossplane.io/subnet-2   True    True

NAME                                                     READY   SYNCED   STATE     ENDPOINT       LOCATION          AGE
cluster.container.gcp.crossplane.io/crossplane-cluster   True    True     RUNNING   34.146.81.14   asia-northeast1   15h

NAME                                                       READY   SYNCED   STATE     CLUSTER-REF   AGE
nodepool.container.gcp.crossplane.io/crossplane-nodepool   True    True     RUNNING                 15h

正常にSubentworkが作成されています🎉

まとめ

CrossplaneはKubernetes上で動作するコントロールプレーンです。そのため、Crossplane自身が動作するKubernetesクラスターもCrossplaneでIaCとして管理しようとすると、今回紹介したようなフローが必要になります。是非、参考にしてみてください!

note

勉強法やキャリア構築法など、エンジニアに役立つ記事をnoteで配信しています。

https://note.com/ring_belle/membership

Discussion