使用 Crossplane 和 VCluster 在 Kubernetes 上快速構(gòu)建新集群
Crossplane 是一個(gè)開(kāi)源的 Kubernetes 插件,通過(guò)擴(kuò)展 Kubernetes API 來(lái)解決云資源的供應(yīng)問(wèn)題。使用 Crossplane 時(shí),你可以用聲明的方式定義需要?jiǎng)?chuàng)建哪些云資源才能讓你的應(yīng)用程序正常工作,不需要編寫(xiě)任何代碼??梢灾苯油ㄟ^(guò)創(chuàng)建相關(guān)的 CRD 對(duì)象來(lái)完成這些云資源的定義,可以看成是一個(gè)云原生版本的 Terraform。
VCluster 是一個(gè)通過(guò)輕量級(jí)虛擬 Kubernetes 集群提供靈活性并節(jié)省成本的工具,使用 VCluster,你可以在 Kubernetes 集群內(nèi)創(chuàng)建一個(gè)隔離的虛擬 Kubernetes 集群。這大大降低了創(chuàng)建和維護(hù) Kubernetes 集群控制平面的復(fù)雜性。
下表比較了使用命名空間、vcluster 和 Kubernetes 集群的隔離級(jí)別和管理復(fù)雜性:
那么我們將 Crossplane 和 VCluster 這兩個(gè)工具一起來(lái)結(jié)合時(shí)候會(huì)產(chǎn)生怎樣的效果呢?接下來(lái)我們將通過(guò)一個(gè)示例來(lái)說(shuō)明這二者的結(jié)合使用。
示例
在這個(gè)示例中我們想要實(shí)現(xiàn)如下所示的一些功能:
- 擁有一個(gè)集群可以接收請(qǐng)求來(lái)啟動(dòng)一個(gè)新的集群環(huán)境
- 這些環(huán)境將可以使用 Helm 來(lái)安裝應(yīng)用程序
- 請(qǐng)求新環(huán)境的團(tuán)隊(duì)并不關(guān)心集群在哪里創(chuàng)建的,所以使用 VCluster 或在云提供商中創(chuàng)建一個(gè) Kubernetes 集群應(yīng)該為終端用戶提供類似的體驗(yàn)。
這里我在本地環(huán)境使用 KinD 來(lái)進(jìn)行演示,相關(guān)的資源清單可以在 https://github.com/salaboy/from-monolith-to-k8s/tree/main/platform/crossplane-vcluster 此處找到(需要自己提前安裝 kubectl、helm、kind)。
如上圖所示,我們只需要?jiǎng)?chuàng)建一個(gè) KinD 集群(當(dāng)然也可以是其他任何的 Kuberentes 集群),然后在集群上安裝 Crossplane 和 Crossplane Helm Provider,因?yàn)槲覀冞@里沒(méi)有創(chuàng)建任何云資源,所以我們不需要配置任何其他的 Crossplane Provider(比如 GCP、AWS、Azure 等)。
安裝 Crossplane
接下來(lái)我們可以先使用 KinD 創(chuàng)建一個(gè) Kubernetes 集群。
$ kind create cluster
Creating cluster "kind" ...
? Ensuring node image (kindest/node:v1.23.4) ??
? 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 question, bug, or feature request? Let us know! https://kind.sigs.k8s.io/#community ??
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
kind-control-plane Ready control-plane,master 56s v1.23.4
集群準(zhǔn)備好后接下來(lái)我們可以將 Crossplane 和 Crossplane Helm Provider 安裝到我們的 KinD 集群中去,如下所示:
$ kubectl create ns crossplane-system
namespace/crossplane-system created
$ helm install crossplane --namespace crossplane-system crossplane-stable/crossplane
NAME: crossplane
LAST DEPLOYED: Tue Aug 9 15:20:22 2022
NAMESPACE: crossplane-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Release: crossplane
Chart Name: crossplane
Chart Description: Crossplane is an open source Kubernetes add-on that enables platform teams to assemble infrastructure from multiple vendors, and expose higher level self-service APIs for application teams to consume.
Chart Version: 1.9.0
Chart Application Version: 1.9.0
Kube Version: v1.23.4
安裝完成后會(huì)在 crossplane-system 命名空間下面運(yùn)行如下所示的兩個(gè) Pod:
$ kubectl get pods -n crossplane-system
NAME READY STATUS RESTARTS AGE
crossplane-c9b9fc9f9-4hn47 1/1 Running 0 11m
crossplane-rbac-manager-56c8ff5b65-8lgrp 1/1 Running 0 11m
接著需要安裝 Crossplane Helm Provider,直接使用 crossplane 的 kubectl 插件即可安裝:
$ kubectl crossplane install provider crossplane/provider-helm:v0.10.0
provider.pkg.crossplane.io/crossplane-provider-helm created
另外需要注意在安裝 Crossplane Helm Provider 的時(shí)候,我們需要為該 Provider 提供一個(gè)合適的 ServiceAccount 來(lái)創(chuàng)建新的 ClusterRoleBinding,以便該 Provider 可以安裝 Helm Charts。
$ SA=$(kubectl -n crossplane-system get sa -o name | grep provider-helm | sed -e 's|serviceaccount\/|crossplane-system:|g')
$ echo $SA
crossplane-system:crossplane-provider-helm-3d2f09bcd965
$ kubectl create clusterrolebinding provider-helm-admin-binding --clusterrole cluster-admin --serviceaccount="${SA}"
clusterrolebinding.rbac.authorization.k8s.io/provider-helm-admin-binding created
然后創(chuàng)建一個(gè)如下所示的 ProviderConfig 對(duì)象,用來(lái)聲明安裝 Helm Provider:
# helm-provider-config.yaml
apiVersion: helm.crossplane.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: InjectedIdentity
---
# SA=$(kubectl -n crossplane-system get sa -o name | grep provider-helm | sed -e 's|serviceaccount\/|crossplane-system:|g')
# kubectl create clusterrolebinding provider-helm-admin-binding --clusterrole cluster-admin --serviceaccount="${SA}"
直接應(yīng)用上面的資源清單即可:
$ kubectl apply -f helm-provider-config.yaml
providerconfig.helm.crossplane.io/default created
到這里我們就將 Crossplane 安裝完成了。最后我們還推薦安裝 vcluster 命令行工具來(lái)連接 Kubernetes 集群,可以參考文檔 https://www.vcluster.com/docs/getting-started/setup 進(jìn)行安裝。
使用 Crossplane Composition 創(chuàng)建 VClusters
Crossplane 和 Crossplane Helm Provider 現(xiàn)在已經(jīng)準(zhǔn)備好了,接下來(lái)讓我們來(lái)看看 Crossplane Composition。Crossplane 提供了組合托管資源的機(jī)制,用戶可以在其中以聲明的方式創(chuàng)建自己的抽象。
- 組合資源(Composite Resource):組合資源(XR)是一種自定義資源,它由托管資源組成,允許你抽象基礎(chǔ)設(shè)施細(xì)節(jié)。CompositeResourceDefinition(XRD)定義了一種新型的組合資源,XRD 是集群范圍的,為了創(chuàng)建一個(gè)命名空間的 XR,相應(yīng)的 XRD 可以提供一個(gè)組合資源聲明(XRC)。
- 組合(Composition):一個(gè)組合指定 XR 將由哪些資源組成,也就是當(dāng)你創(chuàng)建 XR 時(shí)會(huì)發(fā)生什么,一個(gè) XR 可以有多個(gè)組合。例如,對(duì)于 CompositeDatabase XR,你可以使用一個(gè)組合來(lái)創(chuàng)建 AWS RDS 實(shí)例、一個(gè)安全組和一個(gè) MySQL 數(shù)據(jù)庫(kù)。另一種組合可以定義 GCP CloudSQL 實(shí)例和 PostgreSQL 數(shù)據(jù)庫(kù)。
- 配置(Configuration):配置是一個(gè) XRD 和組合的包,然后可以使用 Crossplane CLI 將其發(fā)布到 OCI 鏡像注冊(cè)中心,并通過(guò)創(chuàng)建聲明性配置資源將其安裝到一個(gè) Crossplane 集群中。
我們這里的 Crossplane Composition(XR) 定義了創(chuàng)建新環(huán)境資源時(shí)需要執(zhí)行的相關(guān)操作,該對(duì)象會(huì)執(zhí)行以下的一些操作:
- 使用我們安裝 Helm Provider 時(shí)配置的 Helm Provider Config 來(lái)安裝 VCluster Helm Chart,當(dāng)我們安裝這個(gè) Chart 時(shí),就可以創(chuàng)建一個(gè)新的 VCluster,是不是非常簡(jiǎn)單。
- VCluster 安裝會(huì)創(chuàng)建一個(gè) Kubernetes Secret 對(duì)象,其中包含連接到 VCluster APIServer 的 tokens,我們可以使用該 Secret 來(lái)配置第二個(gè) Helm Provider Config,它允許我們將 Helm Charts 安裝到新創(chuàng)建的集群中。
- 我們可以使用第二個(gè) Helm Provider Config 來(lái)將應(yīng)用程序安裝到創(chuàng)建的 VCluster 中。
接下來(lái)我們來(lái)看下這是如何實(shí)現(xiàn)的,首先我們需要將 Crossplane Composition 和 Environment CRD 應(yīng)用到我們的集群中來(lái),這樣我們就可以創(chuàng)建新的 Environment 資源了。
首先定義一個(gè) Environment 的組合資源,對(duì)應(yīng)的資源清單如下所示,該對(duì)象相當(dāng)于 Kubernetes 集群中的 CRD:
# environment-resource-definition.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: environments.fmtok8s.salaboy.com
spec:
group: fmtok8s.salaboy.com
names:
kind: Environment
plural: environments
claimNames:
kind: Cluster
plural: clusters
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties: {}
有了 XRD 組合資源聲明過(guò)后,接下來(lái)定義一個(gè)組合對(duì)象,資源清單文件內(nèi)容如下所示,在該 Composition 組合對(duì)象中定義了多個(gè)資源,其中關(guān)聯(lián)了上面的 XRD 對(duì)象聲明的 Environment 對(duì)象:
# composition.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: environment.fmtok8s.salaboy.com
spec:
writeConnectionSecretsToNamespace: crossplane-system
compositeTypeRef:
apiVersion: fmtok8s.salaboy.com/v1alpha1
kind: Environment
resources:
- name: vcluster-helm-release
base:
apiVersion: helm.crossplane.io/v1beta1
kind: Release
metadata:
annotations:
crossplane.io/external-name: # patched
spec:
rollbackLimit: 3
forProvider:
namespace: # patched
chart:
name: vcluster
repository: https://charts.loft.sh
version: "0.10.2"
values:
syncer:
extraArgs: [] # patched
# - --out-kube-config-server=https://cluster-1.cluster-1.svc
providerConfigRef:
name: default
patches:
- fromFieldPath: metadata.name
toFieldPath: spec.forProvider.namespace
policy:
fromFieldPath: Required
- fromFieldPath: metadata.name
toFieldPath: metadata.annotations[crossplane.io/external-name]
policy:
fromFieldPath: Required
- fromFieldPath: metadata.name
toFieldPath: metadata.name
transforms:
- type: string
string:
fmt: "%s-vcluster"
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: metadata.name
strategy: string
string:
fmt: "--out-kube-config-secret=%s-secret"
toFieldPath: spec.forProvider.values.syncer.extraArgs[0]
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: metadata.name
- fromFieldPath: metadata.name
strategy: string
string:
fmt: "--out-kube-config-server=https://%s.%s.svc"
toFieldPath: spec.forProvider.values.syncer.extraArgs[1]
- type: CombineFromComposite
combine:
variables:
- fromFieldPath: metadata.name
- fromFieldPath: metadata.name
strategy: string
string:
fmt: "--tls-san=%s.%s.svc"
toFieldPath: spec.forProvider.values.syncer.extraArgs[2]
- name: helm-providerconfig
base:
apiVersion: helm.crossplane.io/v1alpha1
kind: ProviderConfig
spec:
credentials:
source: Secret
secretRef:
name: # patched
namespace: # patched
key: config
patches:
- fromFieldPath: metadata.name
toFieldPath: spec.credentials.secretRef.name
transforms:
- type: string
string:
fmt: vc-%s
- fromFieldPath: metadata.name
toFieldPath: spec.credentials.secretRef.namespace
- fromFieldPath: metadata.uid
toFieldPath: metadata.name
- name: helm-provider-vcluster
base:
apiVersion: helm.crossplane.io/v1beta1
kind: ProviderConfig
spec:
credentials:
source: Secret
secretRef:
namespace: #patched
key: config
patches:
- fromFieldPath: metadata.name
toFieldPath: metadata.name
- fromFieldPath: metadata.name
toFieldPath: spec.credentials.secretRef.namespace
policy:
fromFieldPath: Required
# This ProviderConfig uses the above VCluster's connection secret as
# its credentials secret.
- fromFieldPath: "metadata.name"
toFieldPath: spec.credentials.secretRef.name
transforms:
- type: string
string:
fmt: "%s-secret"
readinessChecks:
- type: None
- name: conference-chart-vcluster
base:
apiVersion: helm.crossplane.io/v1beta1
kind: Release
metadata:
annotations:
crossplane.io/external-name: conference
spec:
forProvider:
chart:
name: fmtok8s-conference-chart
repository: https://salaboy.github.io/helm/
version: "v0.1.1"
namespace: conference
providerConfigRef:
name: #patched
patches:
- fromFieldPath: metadata.name
toFieldPath: spec.providerConfigRef.name
我們?yōu)?VCluster 的 Chart 包設(shè)置了 3 個(gè)參數(shù),以便與 Crossplane 一起使用:
- 在第 53 行配置了fmt: "--out-kube-config-secret=%s-secret"?,因?yàn)槲覀冃枰?VCluster 創(chuàng)建一個(gè) Secret 對(duì)象,將kubeconfig 托管在里面,這樣我們就可以獲取它,與新創(chuàng)建的 APIServer 進(jìn)行連接了。
- 在第 62 行配置了fmt: "--out-kube-config-server=https://%s.%s.svc"? ,因?yàn)槲覀冃枰猭ubeconfig? 從集群內(nèi)指向新的 APIServer URL,默認(rèn)情況下,生成的kubeconfig? 指向https://localhost:8443。
- 在第 71 行配置了fmt: "--tls-san=%s.%s.svc",表示需要將新的服務(wù)地址添加到到 APIServer 接受連接的主機(jī)列表中。
接著直接應(yīng)用上面的兩個(gè)對(duì)象即可:
$ kubectl apply -f composition.yaml
composition.apiextensions.crossplane.io/environment.fmtok8s.salaboy.com created
$ kubectl apply -f environment-resource-definition.yaml
compositeresourcedefinition.apiextensions.crossplane.io/environments.fmtok8s.salaboy.com created
一旦集群內(nèi)的組合和 CRD 可用后,我們就可以開(kāi)始創(chuàng)建新的環(huán)境資源,在運(yùn)行之前要檢查哪些 VClusters 目前是可用的。
$ vcluster list
NAME NAMESPACE STATUS CONNECTED CREATED AGE
No entries found
現(xiàn)在應(yīng)該還沒(méi)有任何 VClusters,現(xiàn)在我們可以創(chuàng)建一個(gè)新的環(huán)境,比如現(xiàn)在我們定義一個(gè) dev 的環(huán)境,只需要聲明一個(gè)如下所示的 Environment 對(duì)象即可:
# environment-resource.yaml
apiVersion: fmtok8s.salaboy.com/v1alpha1
kind: Environment
metadata:
name: dev-environment
spec: {}
直接應(yīng)用該對(duì)象:
$ kubectl apply -f environment-resource.yaml
environment.fmtok8s.salaboy.com/dev-environment created
$ kubectl get environments
NAME READY COMPOSITION AGE
dev-environment False environment.fmtok8s.salaboy.com 57s
$ kubectl describe environments dev-environment
Name: dev-environment
Namespace:
Labels: crossplane.io/composite=dev-environment
Annotations: <none>
API Version: fmtok8s.salaboy.com/v1alpha1
Kind: Environment
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal PublishConnectionSecret 76s defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully published connection details
Normal SelectComposition 19s (x7 over 76s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully selected composition
Normal ComposeResources 18s (x7 over 76s) defined/compositeresourcedefinition.apiextensions.crossplane.io Successfully composed resources
現(xiàn)在我們可以像對(duì)待其他 Kubernetes 資源一樣對(duì)待你創(chuàng)建的環(huán)境資源了,你可以直接使用 kubectl get environments 列出它們,甚至可以描述它們以查看更多細(xì)節(jié)。
現(xiàn)在我們?cè)偃z查 VCluster 正常就會(huì)發(fā)現(xiàn)一個(gè)新的資源了:
$ vcluster list
NAME NAMESPACE STATUS CONNECTED CREATED AGE
dev-environment dev-environment Running 2022-08-09 17:44:07 +0800 CST 56m38s
VCluster 將在一個(gè)新的命名空間 dev-environment 中安裝一個(gè) APIServer(默認(rèn)使用 K3s)、CoreDNS 實(shí)例和一個(gè) Syncer,讓用戶能夠通過(guò) kubectl 與 VCluster API Server 進(jìn)行交互,就像與常規(guī)集群一樣,VCluster 將與負(fù)責(zé)調(diào)度工作負(fù)載的主機(jī)集群同步這些資源,這樣來(lái)實(shí)現(xiàn)了一個(gè)命名空間就是一個(gè) Kubernetes 集群的功能。
一旦我們配置了 Crossplane 和 Crossplane Helm Provider,我們就可以通過(guò)創(chuàng)建一個(gè)新的 Helm Release 安裝一個(gè) Helm Chart 來(lái)創(chuàng)建一個(gè)新的 VCluster,非常簡(jiǎn)單。
一旦我們使用在 secret 中創(chuàng)建的正確 kubeconfig 創(chuàng)建了 VCluster,我們就可以配置第二個(gè) Helm Provider 以將我們的應(yīng)用程序安裝到新創(chuàng)建的 VCluster 中,上面 composition 對(duì)象中第 95 行定義的 helm-provider-vcluster 就是該描述。
然后在 composition 內(nèi)部,我們配置使用了一個(gè)我們的會(huì)議應(yīng)用的 Helm Chart 包。
配置完所有內(nèi)容并創(chuàng)建新環(huán)境后,我們可以連接到 VCluster 并檢查應(yīng)用程序是否已安裝:
$ vcluster connect dev-environment --server https://localhost:8443 -- bash
The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
bash-3.2$ kubectl get ns
NAME STATUS AGE
default Active 32m
kube-system Active 32m
kube-public Active 32m
kube-node-lease Active 32m
conference Active 23m
bash-3.2$ kubectl get pods -n conference
NAME READY STATUS RESTARTS AGE
conference-fmtok8s-frontend-7cd5db8669-pv944 1/1 Running 0 23m
conference-fmtok8s-email-service-768bc88cbb-sklrg 1/1 Running 0 23m
conference-postgresql-0 1/1 Running 0 23m
conference-fmtok8s-c4p-service-7f56d7bd9d-2vjtx 1/1 Running 2 (19m ago) 23m
conference-redis-master-0 1/1 Running 0 23m
conference-redis-replicas-0 1/1 Running 0 23m
conference-fmtok8s-agenda-service-7db66c9568-xsh5m 1/1 Running 2 (16m ago) 23m
可以看到我們的應(yīng)用在該集群中已經(jīng)安裝成功了,而這些應(yīng)用實(shí)際上就是部署在 KinD 這個(gè)原始集群的 dev-environment 命名空間下面的:
$ kubectl get pods -n dev-environment
NAME READY STATUS RESTARTS AGE
conference-fmtok8s-agenda-service-7db66c9568-xsh5m-x-08f9332627 1/1 Running 2 (18m ago) 25m
conference-fmtok8s-c4p-service-7f56d7bd9d-2vjtx-x-co-fc2c58eaec 1/1 Running 2 (21m ago) 25m
conference-fmtok8s-email-service-768bc88cbb-sklrg-x--c5d9594434 1/1 Running 0 25m
conference-fmtok8s-frontend-7cd5db8669-pv944-x-confe-2832ac1bef 1/1 Running 0 25m
conference-postgresql-0-x-conference-x-dev-environment 1/1 Running 0 25m
conference-redis-master-0-x-conference-x-dev-environment 1/1 Running 0 25m
conference-redis-replicas-0-x-conference-x-dev-environment 1/1 Running 0 25m
coredns-76dd5485df-6cbl7-x-kube-system-x-dev-environment 1/1 Running 0 34m
dev-environment-0 2/2 Running 0 63m
只是 VCluster 將一個(gè)命名空間進(jìn)行了隔離,使用起來(lái)和 Kubernetes 集群體驗(yàn)基本一致。
總結(jié)
這只是一個(gè)簡(jiǎn)單的示例,介紹了如何使用 Crossplane 與 VCluster 來(lái)結(jié)合使用快速配置一套 Kubernetes 集群環(huán)境并在其中安裝應(yīng)用,以使開(kāi)發(fā)人員提高工作效率。當(dāng)然還有很多可以優(yōu)化的地方,比如:
- 在 VCluster 中安裝 ArgoCD 并使用作為環(huán)境參數(shù)提供的 GitHub URL 來(lái)實(shí)現(xiàn) GitOps,這將避免對(duì) VCluster 使用 kubectl。使用 composition 可以來(lái)創(chuàng)建 ArgoCD 資源以配置存儲(chǔ)庫(kù)和集群,而無(wú)需用戶干預(yù)。
- 在 VCluster 中安裝 Knative,以便開(kāi)發(fā)人員可以依靠 Knative Functions、Knative Serving 和 Eventing 功能來(lái)設(shè)計(jì)他們的應(yīng)用程序。
- 通過(guò)環(huán)境參數(shù)來(lái)決定 VCluster 使用哪個(gè)云資源,比如 GCP、AKS 和 EKS 實(shí)現(xiàn)等。
- 同樣 Crossplane 也是在本地 KinD 集群中進(jìn)行測(cè)試使用的,我們也可以對(duì)接真實(shí)云資源,比如 GCP、AWS、Azure 等等。