.NET如何开发kubernetes CRD Controller
为什么说标准API才是K8s最具价值的地方?什么是k8s CRD?CRD可以用来做什么?怎样用C#实现K8s CRD扩展功能?
本文项目地址:k8s-crd - Repos (azure.com)
CRD
CRD指的是Custom Resource Definition。开发者更多的关注k8s对于容器的编排与调度,这也是k8s最初惊艳开发者的地方。而k8s最具价值的地方是它提供了一套标准化、跨厂商的 API、结构和语义。
k8s将它拥有的一切看作是资源,而它提供了操作资源的标准 API。我们可以使用api部署容器,进行调度,可以设置ingress 网络,可以操作volumes 做持久化存储等。
定制资源(Custom Resource)是对 Kubernetes API 的扩展。k8s内置了pod,services,configmap等资源,而用户则可以利用CRD和API,来对k8s的功能进行扩展。
声明式 API
k8s使用声明式API,声明式API与命令式API最大的区别在于:
命令式API是同步的,即时的,当服务收到命令之后就会去执行,并且将结果返回。
而声明式API则是向服务提交对象的配置信息,表达期望得到的对象状态,再由服务选择时机去完成。
例如,当我们使用kubectl apply 命令之后,此时可能会因令牌过期导致无法拉取image或者HelmRelease,因为k8s使用的是声明式api,用户期待的状态已经被提交,k8s会在令牌刷新成功之后最终实现用户期望。
CRD Controller
创建 CRD 不需要编程,只需要编写合规的yaml文件。
而实现CRD对象所想要的功能则需要CRD Controller来完成。编写k8s Controller并非go或者python的专利,这篇博客则是要讲如何使用C#来编写一个CRD Controller。
CRD能用来做什么?
CRD可以用来声明服务所需要或者所具有的资源。这里我们假设一个场景:
我们的服务中需要用到对象存储,来存储客户照片,或者一些大文件。通常我们会在项目中配置对象存储服务的路径,访问所需要的账号密码等,更重要的是,我们需要确定对象存储服务的类型:使用公司机房搭建的minio,还是使用云服务厂商提供的对象存储服务,配置显然是不同的。
这时我们就可以使用k8s CRD来屏蔽掉这类基础设施的实现细节。
想想看,如果我们只需要在服务的yaml中声明用来存储文件的Bucket Name和Service Name,具体使用哪种存储方式由当前所在的k8s集群来决定,存储地址自动生成在configmap中,账号密码也可以自动创建并生成secret,同时存储对象的Bucket也会自动被创建出来,是不是很方便呢?
这个只包含了Bucket Name和Service Name的yaml就是我们要创建的CRD,而这个目标则需要由对应的CRD Controller来完成。
C#实现CRD Controller
KubeOps
KubeOps是用dotnet编写的kubernetes operator SDK。和Go语言的sdk相比,它提供了相同甚至更多功能,它已经包含了对kubernetes-client/csharp 官方C# Client类库的引用。
我们可以使用命令安装KubeOps项目模板:
dotnet new --install KubeOps.Templates::*
之后我们就可以在VS中新建一个KubeOps operators 项目了。
Object Storage Operator
按约定,用来承载CRD Controller的服务一般被称为Operator,例如Helm Operator。
CRD Entity
打开刚才创建的项目,在Entities目录下编辑CRD对象对应的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相当于该CRD所在的组织类型,只有符合定义就可以了。
Kind相当于CRD的name,如同我们使用kubectl get pod -A 一样,创建CRD之后,我们就可以使用kubectl get bucket -A来查看集群当前的bucket定义。
CRD YAML
重新生成项目(自行处理掉项目中报错的地方),我们可以看到在config/crds目录下已经为我们自动生成了Bucket CRD YAML,我们可以按照CRD规范,对其进行简单修改,并部署
Mock ObjectStorageManager
我在这里Mock了一个ObjectStorageManager,在监听到集群中的Bucket资源变化时做出响应,正如前面所说的:确定对象存储服务地址;生成secret;并在对象存储服务中创建出需要的Buckcet。
因为KubeOps已经集成了kubernetes-client/csharp,我们可以直接在服务中注入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已经为我们搭建好了项目基架,我们只需在BucketController当中注入需要的ObjectStorageManager服务即可。
ReconcileAsync方法会不断监视Bucket CRD的状态并执行,我们利用类库内置的IFinalizerManager服务为其添加finalizers 。
finalizers的作用我之前的博客已经提到过了K8s-Finalizers。Bucket作为基础设施用于持久化存储,有必要为其设置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来模拟一个需要对象存储服务的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的,按照设计,当我们启动Operator会自动为其添加finalizers并生成secret。
Debug ObjectStorage.Operator
我们可以看到finalizers已经被添加,而且我们想要的对象存储服务的secret也自动生成了出来。
结论
这里就是通过一个简单的例子,为大家介绍k8s CRD的一些基本概念,以及如何使用C#来实现一个CRD Controller。CRD为我们扩展K8s提供了条件,在项目中我们可以根据需要来选择实现。
更多推荐
所有评论(0)