k8s crd operator踩坑点

本文详细介绍了Kubernetes自定义资源定义(CRD)的操作,包括声明变量的两种方式及其区别,更新CRD status字段的方法,以及如何在KubeBuilder中处理status子资源。还提到了客户端操作的最佳实践,如使用unstructured.Unstructured对象进行通用操作,以及处理并发更新问题和避免reconcile循环。此外,讨论了shared informer的使用场景和深拷贝的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、在函数中,有两种方式声明一个crd变量

var a *appv1.deployment

b := new(appv1.deployment)

区别,new是有初始化值的。因此 b != nil。但是a == nil。

因此,new(appv1.deployment)可以作为client.get(ctx, b),进行赋值。但是如果要进行判断,用var比较好,不会进行初始化。

 

2、crd的status字段,如果要更新,需要使用client.status().update()方法,而不能直接使用client.update()

 

3、利用kubebuider时,需要在crd type定义代码上加一行注释:

// +kubebuilder:subresource:status
type xxx struct {
   metav1.TypeMeta   `json:",inline"`
   metav1.ObjectMeta `json:"metadata,omitempty"`

   Spec   xxx   `json:"spec,omitempty"`
   Status xxx `json:"status,omitempty"`
}

这样在代码中更新status,才会成功,不然会报错。

 

4、crd都有一个spec字段,叫Generation。该字段是自动生成的,是每次修改/创建crd对象,都会修改,创建时为1。后面每次修改都会+1。

只有当spec修改时,才会触发Generation+1,status的修改不会触发。

因此可以用该字段,判断是spec修改还是status修改。防止在reconclie中,spec和status变化,引起reconclie死循环。

 

5、使用kubebuider时,可以在setupmanager时,watch方法,添加fliter,只监听符合要求的对象,入队列处理。也可以依靠这里过滤掉status的变化。

 

6、client-go 的list方法,其中listoption,如果要使用namespace作为条件进行过滤,不能在listoption里添加,应如下操作:

labelsSelect := make(map[string]string)
	labelsSelect[xxx] = xxx
	wn := &client.ListOptions{
		LabelSelector: labels.SelectorFromSet(labelsSelect),
	}
	err := h.r.List(ctx, appContextList, wn, client.InNamespace(Namespace))

 

7、client-go 创建一个cr对象时,如果立即去get对象,可能会获取不到,因为create是向api server请求创建对象,创建需要时间。而get是获取本地缓存,informer从api server watch到,并缓存在本地,这个过程存在时间差。

因此好的方法,是创建cr对象后,重新入队列,等待下一次reconclie处理。

 

 

8、如果想获取/更新,不确定的对象。k8s提供了一种结构体,

unstructured.Unstructured

该结构体可以表示所有对象。在get/update时,只需要指定该结构体的GVK即可。

	obj := new(unstructured.Unstructured)

	w, err := oamutil.RawExtension2Unstructured(&component.Spec.Workload)
	if err != nil {
		r.Log.Info("Unstructured  component workload failed. componentName", fmt.Sprintf("error is "), err, "componentName", component.Name)
		return nil, err
	}
	obj.SetKind(w.GetKind())
	obj.SetAPIVersion(w.GetAPIVersion())
	key := ctypes.NamespacedName{Name: component.Name + "-" + canaryColor, Namespace: workloadNs}
	err = client.Get(ctx, key, obj)

 

对于获取该结构体的字段,可以使用绝对路径。比如想获取replicas

unstructured.NestedInt64(obj.Object, "spec", "replicas")

 

9、在更新某个对象时,很有可能会报错object is already modify, please apply the latest one。这个意思是,在更新该对象时,该对象被其他更新了

因为client.update(ctx, &app)时,app已经是有个值的,他的generation和revision是有值的,但是被其他修改后,这个值会变化。导致更新时,发现revision对不上。

解决方法:1、更新错误,再入队列,重新reconclie即可。2、或者采用patch操作。

 

10、sharedinformer是复用reflector来监听对象,当同时监听deployment,多个informer都会收到该deployment curd的事件。为了让各个infomer不互相干扰,不要采用sharedinformer。采用普通的informer

podListWatcher := cache.NewListWatchFromClient(client.AppsV1().RESTClient(), "deployments", v12.NamespaceAll, fields.Everything())
indexer, informer := cache.NewIndexerInformer(listerWatcher, &appsv1.Deployment{}, 0, cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				deploy := obj.(*appsv1.Deployment)
				deployobj := DeployObj {
					deploy: deploy,
					cluster: cluster,
					types: "add",
				}

				controller.handleObject(deployobj)
			},
			UpdateFunc: func(old, new interface{}) {
				newDepl := new.(*appsv1.Deployment)
				oldDepl := old.(*appsv1.Deployment)

				if newDepl.ResourceVersion != oldDepl.ResourceVersion && newDepl.Generation != oldDepl.Generation {
					// Periodic resync will send update events for all known Deployments.
					// Two different versions of the same Deployment will always have different RVs.
					deployobj := DeployObj {
						deploy: newDepl,
						cluster: cluster,
						types: "update",
					}

					klog.Info("update handleObject, ",deployobj)
					controller.handleObject(deployobj)
				}

			},
			DeleteFunc: func(obj interface{}) {
				deployobj := DeployObj {
					deploy: obj.(*appsv1.Deployment),
					cluster: cluster,
					types: "delete",
				}

				controller.handleObject(deployobj)
			},
		}, cache.Indexers{})
go informer.Run(stopCh)

 

 

11、一些字段的拷贝,如果是字段结构体里包含slice类型的数据,那么这些字段的拷贝,一定要用deepcopy,而不是简单的值传递。

例子:

status字段的拷贝:

status{

a int

b int

[]condition

}

a.status = b.status

这样拷贝,由于[]condtion是slice,传递过去的是地址,因此a.status的condition和b的condition的地址其实是一样的,那么修改a的condition时,b的condition也会被修改。

因此可以采用deepcopy方法,a.status=b.deepcopy().status。

相关文章:https://halfrost.com/go_slice/#toc-8

 

### 关于Kubernetes CRDOperator模式 #### 自定义资源定义(CRD) CRD允许扩展API服务器的功能,无需修改核心组件即可引入新的API对象。通过CRD可以创建特定业务需求的对象类型[^1]。 ```yaml apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: kvdis.example.com spec: group: example.com versions: - name: v1alpha1 served: true storage: true scope: Namespaced names: plural: kvdis singular: kvdI kind: Kvdi shortNames: - kd ``` 这段YAML配置文件展示了如何定义一个新的名为`kvdI`的自定义资源,在命名空间范围内可用,并属于`example.com`组的一部分。 #### Operator模式 Operators是一种封装、部署并管理Kubernetes应用的方法。它利用定制控制器来实现复杂的状态管理和操作逻辑自动化。对于像KVDI这样的虚拟桌面基础设施项目来说,operator可以帮助简化集群中的VDI环境管理工作流。 为了构建一个基于Go语言编写的简单Operator,通常会遵循如下结构: - 定义所需的Custom Resource Definitions (CRDs). - 创建Controller用于监听这些新类型的事件变化. - 编写Reconcile函数处理实际的任务执行. ```go package main import ( "context" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager/signals" ) func main() { ctx := context.Background() log := logf.Log.WithName("controller_kvd") mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{}) if err != nil { panic(err.Error()) } clientset, _ := kubernetes.NewForConfig(mgr.GetConfig()) kvdiCtrl := &KvdiController{ ClientSet: clientset, Log: log, Scheme: mgr.GetScheme(), } err = kvdiCtrl.SetupWithManager(mgr) if err != nil { panic(err.Error()) } log.Info("starting manager") if err := mgr.Start(signals.SetupSignalHandler()); err != nil { log.Error(err, "problem running manager") } } ``` 上述代码片段提供了一个基本框架,其中包含了启动带有自定义控制器的manager所需的关键部分. #### 最佳实践建议 当开发针对KVDI或其他项目的operators时,请考虑以下几最佳做法: - **模块化设计**: 将功能分解成独立的小型服务或库,以便更容易维护和发展。 - **幂等性保障**: 确保每次调用都能得到相同的结果,即使重复多次也不会影响最终状态。 - **错误恢复机制**: 实现重试策略和其他方法来应对临时性的失败情况。 - **监控与日志记录**: 添加足够的指标暴露以及详细的日志输出支持调试工作。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值