k8s crd operator踩坑点
1、在函数中,有两种方式声明一个crd变量var a *appv1.deploymentb := new(appv1.deployment)区别,new是有初始化值的。因此 b != nil。但是a == nil。因此,new(appv1.deployment)可以作为client.get(ctx, b),进行赋值。但是如果要进行判断,用var比较好,不会进行初始化。2、crd的status字段,
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
更多推荐
所有评论(0)