开发流程

1.确定crd结构

确定crd结构,本质上就是确定spec和status。

spec:不可变属性

status:可变状态

2.使用kubebuilder生成框架

3.编写controller代码

用到的方法和特性

日志

logger := log.FromContext(ctx, "devbox", req.NamespacedName)

获取资源

  devbox := &devboxv1alpha1.Devbox{}
  if err := r.Get(ctx, req.NamespacedName, devbox); err != nil {
    return ctrl.Result{}, client.IgnoreNotFound(err)
  }

finalizer操作

用于管理 Kubernetes 对象的生命周期,特别是添加和移除对象的 finalizer,确保在对象被删除之前,控制器可以执行一些必要的清理操作。

if devbox.ObjectMeta.DeletionTimestamp.IsZero() {
    if controllerutil.AddFinalizer(devbox, FinalizerName) {
      if err := r.Update(ctx, devbox); err != nil {
        return ctrl.Result{}, err
      }
    }
  } else {
    if controllerutil.RemoveFinalizer(devbox, FinalizerName) {
      if err := r.Update(ctx, devbox); err != nil {
        return ctrl.Result{}, err
      }
    }
    return ctrl.Result{}, nil
  }

首先判断DeletionTimestamp是否为0.

DeletionTimestamp是ObjectMeta 中的一个字段,用来标记该资源被删除的时间戳。当一个 Kubernetes 资源被删除时,Kubernetes 会将这个时间戳设置为删除请求的时间。

当一个对象被删除时,Kubernetes 会自动设置 deletionTimestamp 字段,标记删除请求的时间点。这个字段用于指示对象即将被删除,并且可能还在执行 finalizer 相关的清理操作。

直到所有 finalizer 被移除对象才会真正被删除。

在ObjectMeta中,还有一些其他的不会体现在crd的yaml中的字段,如下:

  • uid:唯一标识符,由 Kubernetes 在创建对象时自动生成,确保每个对象在集群中有一个唯一的 ID。

  • resourceVersion:表示对象的版本号,用于实现乐观并发控制和资源的更新操作。

  • creationTimestamp:记录对象创建的时间

上面这段逻辑:

如果deletionTimestamp为0,那么说明它是活跃状态,于是调用controllerutil.AddFinalizer这个方法为对象添加Finalizer。如果对象上已经存在这个 finalizer,AddFinalizer 不会重复添加。添加finalizer后,调用 r.Update(ctx, devbox) 将更新后的对象(即带有 finalizer 的对象)保存到 Kubernetes 集群中。

如果deletionTimestamp不为0,那么说明已经开始删除,控制器会尝试移除对象的 finalizer。以便于执行一些其他的操作。

demo如下:

if myResource.ObjectMeta.DeletionTimestamp.IsZero() {
    // Resource is not being deleted, add the finalizer
    if !containsString(myResource.ObjectMeta.Finalizers, "mycompany.com/myresource-finalizer") {
        myResource.ObjectMeta.Finalizers = append(myResource.ObjectMeta.Finalizers, "mycompany.com/myresource-finalizer")
        err := r.Update(ctx, myResource)
        if err != nil {
            return ctrl.Result{}, err
        }
    }
} else {
    // Resource is being deleted, check finalizer
    if containsString(myResource.ObjectMeta.Finalizers, "mycompany.com/myresource-finalizer") {
        // Perform cleanup logic here (e.g., delete an external resource)
        err := cleanupExternalResources(myResource)
        if err != nil {
            return ctrl.Result{}, err
        }
        
        // Remove finalizer to allow Kubernetes to delete the resource
        myResource.ObjectMeta.Finalizers = removeString(myResource.ObjectMeta.Finalizers, "mycompany.com/myresource-finalizer")
        err := r.Update(ctx, myResource)
        if err != nil {
            return ctrl.Result{}, err
        }
    }
}

这种模式常用于需要先清理外部资源的场景,确保资源删除时不会留下孤立的外部依赖。

标签操作

recLabels := label.RecommendedLabels(&label.Recommended{
  Name:      devbox.Name,
  ManagedBy: label.DefaultManagedBy,
  PartOf:    DevBoxPartOf,
})

修改crd格式

修改格式后,要运行以下命令:

make manifest:manifest 目标通常用于生成 Kubernetes 清单文件(manifests),例如 CRD 的 YAML 文件、RBAC 配置等。

make generate:目标通常用于生成项目中的代码,尤其是 CRD 相关的深度拷贝函数、OpenAPI 校验等

make pre-deploy:pre-deploy 目标通常用于在实际部署之前做一些准备工作,比如生成配置、创建所需的 Kubernetes 资源等。

pre-deployment会涉及到yaml格式的修改,修改之后把新的yaml复制到原有的yaml.tmpl下。

当部署到正式/测试环境之后,使用如下命令:

make install 更新/创建crd资源。

然后启动controller即可。

kubectl获取状态栏展示

查看某种资源的详细信息的时候,具体展示哪些字段的配置。

格式如下:

// +kubebuilder:printcolumn:name="OriginalImage",type="string",JSONPath=".status.originalImage"

意义:

向 kubectl get 命令的输出中添加自定义列。在这个例子中,它将为 CRD 对象的 status.originalImage 字段创建一个名为 OriginalImage 的列。

解释:

•+kubebuilder:printcolumn:这是一个 kubebuilder 注释,表示该字段应该作为输出的列之一。

•name="OriginalImage":列的名字为 OriginalImage,这个名字会在 kubectl get <CRD> 命令的输出中显示。

•type="string":表示该列的数据类型是字符串。

•JSONPath=".status.originalImage":指向 CRD 状态中的具体字段,这里是 .status.originalImage。kubectl 将根据这个 JSONPath 从 CRD 中提取数据并显示在这列中。

使用例子:

当你定义这个注释后,执行 kubectl get <CRD> 时,输出将包含一列名为 OriginalImage,展示 status.originalImage 字段的值。

管控 manager与refernence

// SetupWithManager sets up the controller with the Manager.
func (r *DevboxReconciler) SetupWithManager(mgr ctrl.Manager) error {
  return ctrl.NewControllerManagedBy(mgr).
    For(&devboxv1alpha1.Devbox{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
    Owns(&corev1.Pod{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). // enqueue request if pod spec/status is updated
    Owns(&corev1.Service{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
    Owns(&corev1.Secret{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
    Owns(&networkingv1.Ingress{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})).
    Complete(r)
}

这个主要目的是表征 controller应该管理什么类型的资源。

不同的资源通过reference交给controller管控。

也就是说,修改controller的时候必须要考虑到的是setupwithmanager这个地方,不能不管,尤其是涉及到controller新增管理的资源类型的时候。

设置了这个和reference之后 每当他管控的那些资源发生变化的时候 就去执行一遍deployment方法对吗

SetupWithManager 方法中为控制器设置了对 Devbox 资源及其相关资源(如 PodServiceSecretIngress)的监控,并使用了特定的谓词(predicate)来过滤事件。

For 方法:监控 Devbox 资源,并使用 predicate.GenerationChangedPredicate{}。该谓词会过滤掉 Update 事件中 metadata.generation 未变化的情况,即仅当资源的 spec 部分发生变化时才触发调谐。 
GO DOC
​
Owns 方法:监控由 Devbox 拥有的资源:
​
Pod:使用 predicate.ResourceVersionChangedPredicate{},该谓词仅在资源的 metadata.resourceVersion 发生变化时触发,即当 Pod 的 spec 或 status 更新时会触发调谐。 
GO DOC
Service、Secret、Ingress:均使用 predicate.GenerationChangedPredicate{},仅当这些资源的 spec 部分发生变化时才触发调谐。

测试

1.在本地测试 :使用make file

2.在服务器上测试:在服务器上新建git,然后运行同样的makefile文件。

部署

编写完一个 CRD(自定义资源定义)和它的控制器之后,要将其应用到 Kubernetes 集群中,你需要执行以下步骤:

  1. 部署 CRD

    • 使用 kubectl apply -f <crd.yaml> 命令将 CRD YAML 文件应用到 Kubernetes 集群中。这会注册你的自定义资源。

  2. 构建和推送控制器镜像

    • 构建你的控制器应用程序的 Docker 镜像。

    • 将镜像推送到容器镜像仓库,如 Docker Hub、Google Container Registry 或私有仓库。

  3. 编写 Deployment 和 Service

    • 创建一个 Deployment YAML 文件来定义如何部署你的控制器应用程序。

    • 如果需要,创建一个 Service YAML 文件来暴露你的控制器应用程序。

  4. 配置 RBAC

    • 编写 Role 和 RoleBinding YAML 文件,为控制器应用程序定义所需的 Kubernetes 角色和权限。

  5. 应用 Deployment 和 Service

    • 使用 kubectl apply -f <deployment.yaml> 命令部署控制器应用程序。

    • 如果有 Service 文件,使用 kubectl apply -f <service.yaml> 命令应用它。

以下是一个简化的示例流程,展示如何将 CRD 和控制器部署到 Kubernetes 集群:

复制
# 部署 CRD
kubectl apply -f mycrd.yaml
​
# 构建并推送控制器镜像到容器仓库
# docker build -t my-controller-image .
# docker push my-controller-image
​
# 应用 Deployment 和 Service
kubectl apply -f mycontroller-deployment.yaml
kubectl apply -f mycontroller-service.yaml
​
# 应用 RBAC 配置
kubectl apply -f mycontroller-rbac.yaml
​
# 验证 CRD 是否注册成功
kubectl get crd
​
# 创建自定义资源实例
kubectl apply -f mycustomresource.yaml
​
# 检查控制器日志
kubectl logs -f <pod-name>
​
# 检查自定义资源的状态
kubectl describe <custom-resource-name>

前端访问

Client-go与Kubebuilder:

使用 Go 语言的 Kubernetes SDK(例如 client-go 库)创建 CRD(自定义资源定义)和使用 Kubebuilder 创建 CRD 有一些区别和联系:

  1. SDK 手动创建:使用 client-go SDK 创建 CRD 通常需要手动编写 Go 代码来定义 CRD 的结构,并且需要手动编写脚本来注册和处理 CRD。这个过程比较繁琐,需要对 Kubernetes API 和 Go 语言有较深的理解12。

  2. Kubebuilder 框架:Kubebuilder 是一个更高级的框架,它提供了一套简化的流程来创建、管理和部署 CRD 以及相关的控制器。Kubebuilder 通过提供脚手架和自动化工具来减少开发者的工作量,使得创建 CRD 和控制器变得更加容易和快捷61011。

  3. 自动化代码生成:Kubebuilder 使用 controller-gen 工具来自动生成 CRD 的定义文件和相关的代码,这包括了 Kubernetes 对象的 YAML 文件和 Go 客户端代码。这样开发者就不需要手动编写这些代码410。

  4. 声明式 API:Kubebuilder 支持声明式 API 的开发模式,允许开发者以声明的方式来定义期望的资源状态,而由控制器来实现这些状态312。

  5. 控制器模式:无论是使用 client-go 还是 Kubebuilder,CRD 都需要与控制器(Controller)配合使用。控制器是响应资源变化并确保资源状态符合预期的后台进程36。

  6. 多版本支持:Kubebuilder 支持在 CRD 中定义多个版本,并可以使用 webhook 来处理不同版本之间的转换。这是通过 +kubebuilder:storageversion 标签来实现的4。

  7. 社区和维护client-go 是 Kubernetes 官方提供的客户端库,而 Kubebuilder 则是由 Kubernetes 社区维护的框架。使用 Kubebuilder 可以确保更好的社区支持和与 Kubernetes 生态系统的兼容性6。

总的来说,Kubebuilder 提供了一种更高效、更简化的方式来创建和管理 CRD,而使用 client-go SDK 则需要更多的手动操作和对 Kubernetes API 的深入理解。两者都可以用来实现 CRD 的自动化管理,但 Kubebuilder 通过自动化和简化流程,使得开发更加快速和容易。

参考链接

https://developer.aliyun.com/article/749901

https://book.kubebuilder.io/quick-start