連云港網(wǎng)站建設(shè)價格我要推廣
本文項目地址:k8s-crd - Repos (azure.com)
CRD
CRD指的是Custom Resource Definition。開發(fā)者更多的關(guān)注k8s對于容器的編排與調(diào)度,這也是k8s最初驚艷開發(fā)者的地方。而k8s最具價值的地方是它提供了一套標(biāo)準(zhǔn)化、跨廠商的 API、結(jié)構(gòu)和語義。
k8s將它擁有的一切看作是資源,而它提供了操作資源的標(biāo)準(zhǔn) API。我們可以使用api部署容器,進行調(diào)度,可以設(shè)置ingress 網(wǎng)絡(luò),可以操作volumes 做持久化存儲等。
定制資源(Custom Resource)是對 Kubernetes API 的擴展。k8s內(nèi)置了pod,services,configmap等資源,而用戶則可以利用CRD和API,來對k8s的功能進行擴展。
聲明式 API
k8s使用聲明式API,聲明式API與命令式API最大的區(qū)別在于:
命令式API是同步的,即時的,當(dāng)服務(wù)收到命令之后就會去執(zhí)行,并且將結(jié)果返回。
而聲明式API則是向服務(wù)提交對象的配置信息,表達期望得到的對象狀態(tài),再由服務(wù)選擇時機去完成。
例如,當(dāng)我們使用kubectl apply 命令之后,此時可能會因令牌過期導(dǎo)致無法拉取image或者HelmRelease,因為k8s使用的是聲明式api,用戶期待的狀態(tài)已經(jīng)被提交,k8s會在令牌刷新成功之后最終實現(xiàn)用戶期望。
CRD Controller
創(chuàng)建 CRD 不需要編程,只需要編寫合規(guī)的yaml文件。
而實現(xiàn)CRD對象所想要的功能則需要CRD Controller來完成。編寫k8s Controller并非go或者python的專利,這篇博客則是要講如何使用C#來編寫一個CRD Controller。
CRD能用來做什么?
CRD可以用來聲明服務(wù)所需要或者所具有的資源。這里我們假設(shè)一個場景:
我們的服務(wù)中需要用到對象存儲,來存儲客戶照片,或者一些大文件。通常我們會在項目中配置對象存儲服務(wù)的路徑,訪問所需要的賬號密碼等,更重要的是,我們需要確定對象存儲服務(wù)的類型:使用公司機房搭建的minio,還是使用云服務(wù)廠商提供的對象存儲服務(wù),配置顯然是不同的。
這時我們就可以使用k8s CRD來屏蔽掉這類基礎(chǔ)設(shè)施的實現(xiàn)細節(jié)。
想想看,如果我們只需要在服務(wù)的yaml中聲明用來存儲文件的Bucket Name和Service Name,具體使用哪種存儲方式由當(dāng)前所在的k8s集群來決定,存儲地址自動生成在configmap中,賬號密碼也可以自動創(chuàng)建并生成secret,同時存儲對象的Bucket也會自動被創(chuàng)建出來,是不是很方便呢?
這個只包含了Bucket Name和Service Name的yaml就是我們要創(chuàng)建的CRD,而這個目標(biāo)則需要由對應(yīng)的CRD Controller來完成。
C#實現(xiàn)CRD Controller
KubeOps
KubeOps是用dotnet編寫的kubernetes operator SDK。和Go語言的sdk相比,它提供了相同甚至更多功能,它已經(jīng)包含了對kubernetes-client/csharp 官方C# Client類庫的引用。
我們可以使用命令安裝KubeOps項目模板:
dotnet new --install KubeOps.Templates::*
之后我們就可以在VS中新建一個KubeOps operators 項目了。
Object Storage Operator
按約定,用來承載CRD Controller的服務(wù)一般被稱為Operator,例如Helm Operator。
CRD Entity
打開剛才創(chuàng)建的項目,在Entities目錄下編輯CRD對象對應(yīng)的C#類型定義。
[KubernetesEntity(Group = "mahua.crd.com", ApiVersion = "v1", Kind = "Bucket")]public class V1BucketEntity : CustomKubernetesEntity<V1BucketEntity.V1BucketEntitySpec, V1BucketEntity.V1BucketEntityStatus>{public class V1BucketEntitySpec{public string ServiceName { get; set; } = string.Empty;public string BucketName { get; set; } = string.Empty;}public class V1BucketEntityStatus{public string Status { get; set; } = string.Empty;}}
需要注意的是Attribute中Group相當(dāng)于該CRD所在的組織類型,只有符合定義就可以了。
Kind相當(dāng)于CRD的name,如同我們使用kubectl get pod -A 一樣,創(chuàng)建CRD之后,我們就可以使用kubectl get bucket -A來查看集群當(dāng)前的bucket定義。
CRD YAML
重新生成項目(自行處理掉項目中報錯的地方),我們可以看到在config/crds目錄下已經(jīng)為我們自動生成了Bucket CRD YAML,我們可以按照CRD規(guī)范,對其進行簡單修改,并部署

為CRD添加名稱縮寫

部署CRD YAML并測試
Mock ObjectStorageManager
我在這里Mock了一個ObjectStorageManager,在監(jiān)聽到集群中的Bucket資源變化時做出響應(yīng),正如前面所說的:確定對象存儲服務(wù)地址;生成secret;并在對象存儲服務(wù)中創(chuàng)建出需要的Buckcet。
因為KubeOps已經(jīng)集成了kubernetes-client/csharp,我們可以直接在服務(wù)中注入IKubernetesClient來完成所需要的k8s操作。
public async Task RegisterBucketAsync(V1BucketEntity bucketEntity){if (bucketEntity?.Metadata is null || bucketEntity.Spec is null){throw new ArgumentNullException();}await CreateSecretAsync(bucketEntity.Spec.ServiceName,bucketEntity.Spec.BucketName,bucketEntity.Metadata.NamespaceProperty,_environment.EnvironmentName);}private async Task RegisterBucketAsync(string serviceName, string bucketName, string @namespace, string environmentName){var secretName = GetSecretName(serviceName, bucketName);var url = MockObjectStorageUrl(environmentName);var account = MockObjectStorageAccount(serviceName);await MockCreateBucketAsync(bucketName);var secretEntity = CreateBucketSecret(@namespace, serviceName, secretName, url, account.UserName, account.Password);var secret = await _client.Get<V1Secret>(secretName, @namespace);if (secret == null){await _client.Create(secretEntity);}}
CRD Controller
因為Template已經(jīng)為我們搭建好了項目基架,我們只需在BucketController當(dāng)中注入需要的ObjectStorageManager服務(wù)即可。
ReconcileAsync方法會不斷監(jiān)視Bucket CRD的狀態(tài)并執(zhí)行,我們利用類庫內(nèi)置的IFinalizerManager服務(wù)為其添加finalizers 。
finalizers的作用我之前的博客已經(jīng)提到過了K8s-Finalizers。Bucket作為基礎(chǔ)設(shè)施用于持久化存儲,有必要為其設(shè)置finalizers 。
[EntityRbac(typeof(V1BucketEntity), Verbs = RbacVerb.All)]public class BucketController : IResourceController<V1BucketEntity>{private readonly ILogger<BucketController> _logger;private readonly IFinalizerManager<V1BucketEntity> _finalizerManager;private readonly ObjectStorageManager _objectStorageManager;public BucketController(ILogger<BucketController> logger, IFinalizerManager<V1BucketEntity> finalizerManager,ObjectStorageManager objectStorageManager){_logger = logger;_finalizerManager = finalizerManager;_objectStorageManager = objectStorageManager;}public async Task<ResourceControllerResult?> ReconcileAsync(V1BucketEntity entity){_logger.LogInformation($"entity {entity.Name()} called {nameof(ReconcileAsync)}.");await _finalizerManager.RegisterFinalizerAsync<BucketFinalizer>(entity);await _objectStorageManager.RegisterBucketAsync(entity);return ResourceControllerResult.RequeueEvent(TimeSpan.FromSeconds(15));}}
驗證CRD Controller
my-test-bucket.yaml
我們可以編寫YAML來模擬一個需要對象存儲服務(wù)的service部署后的場景。
apiVersion: "mahua.crd.com/v1"
kind: Bucket
metadata:name: my-test-bucket
spec:bucketName: "user-photo"serviceName: "my-app"
kubectl apply
我們可以注意到此時my-test-bucket是沒有finalizers的,按照設(shè)計,當(dāng)我們啟動Operator會自動為其添加finalizers并生成secret。


Debug ObjectStorage.Operator
我們可以看到finalizers已經(jīng)被添加,而且我們想要的對象存儲服務(wù)的secret也自動生成了出來。



結(jié)論
這里就是通過一個簡單的例子,為大家介紹k8s CRD的一些基本概念,以及如何使用C#來實現(xiàn)一個CRD Controller。CRD為我們擴展K8s提供了條件,在項目中我們可以根據(jù)需要來選擇實現(xiàn)。