避坑指南 | 我非要把这个bug优雅的解决掉
- 2019 年 11 月 14 日
- 筆記
腾讯云服务器http://t.cn/AirXAQ1X
之前写过手把手教你编写一个operator在中间件容器化中的实践,以及自定义代码生成:
https://liabio.blog.csdn.net/article/details/93620155
记录了编写redis-operator的过程,达到自动操作redis集群,达到自动化运维的目的。
最近搞正在搞给redis operator加记录events事件功能时,报错了(可以说每次加功能都没有顺顺利利的,但这又如何,奈何它也挡不住洒家探索的脚步)报错如下:
Could not construct reference to ***due to: ‘no kind is registered for the type *** in scheme... ’
记录事件events代码如下:
可以一路跟踪代码到client-go/tools/reference/ref.go中:
可以看到45行,其实强转为v1.ObjectReference,也是不会报错的。所以我们可以构建如下的结构体:
代码像这样子:
ref := &v1.ObjectReference{ APIVersion: controllerKind.Group + "/" + controllerKind.Version, Kind: controllerKind.Kind, Name: redisCluster.Name, Namespace: redisCluster.Namespace, UID:redisCluster.UID, ResourceVersion: redisCluster.ResourceVersion, } rco.eventRecorder.Eventf(ref, v1.EventTypeNormal, "CreateCluster", "create cluster start")
但每次记录events前,都写这么一坨,显然是逼格很低的,况且纵观k8s源码,到处都是直接传pod、svc等内置类型(其实现了runtime.Object),随手一找就看到下面这个样子,将&svc直接传入,优雅很多。
那么该怎么搞才能直接把redisCluster结构体传进去不报错呢?更接近原生代码风格,更优雅呢?我们先分析下面的源码:
// GetReference returns an ObjectReference which refers to the given // object, or an error if the object doesn't follow the conventions // that would allow this. // TODO: should take a meta.Interface see http://issue.k8s.io/7127 func GetReference(scheme *runtime.Scheme, obj runtime.Object) (*v1.ObjectReference, error) { if obj == nil { return nil, ErrNilObject } if ref, ok := obj.(*v1.ObjectReference); ok { // Don't make a reference to a reference. return ref, nil } gvk := obj.GetObjectKind().GroupVersionKind() // if the object referenced is actually persisted, we can just get kind from meta // if we are building an object reference to something not yet persisted, we should fallback to scheme kind := gvk.Kind if len(kind) == 0 { // TODO: this is wrong gvks, _, err := scheme.ObjectKinds(obj) if err != nil { return nil, err } kind = gvks[0].Kind } // An object that implements only List has enough metadata to build a reference var listMeta metav1.Common objectMeta, err := meta.Accessor(obj) if err != nil { listMeta, err = meta.CommonAccessor(obj) if err != nil { return nil, err } } else { listMeta = objectMeta } // if the object referenced is actually persisted, we can also get version from meta version := gvk.GroupVersion().String() if len(version) == 0 { selfLink := listMeta.GetSelfLink() if len(selfLink) == 0 { return nil, ErrNoSelfLink } selfLinkUrl, err := url.Parse(selfLink) if err != nil { return nil, err } // example paths: /<prefix>/<version>/* parts := strings.Split(selfLinkUrl.Path, "/") if len(parts) < 4 { return nil, fmt.Errorf("unexpected self link format: '%v'; got version '%v'", selfLink, version) } if parts[1] == "api" { version = parts[2] } else { version = parts[2] + "/" + parts[3] } } // only has list metadata if objectMeta == nil { return &v1.ObjectReference{ Kind: kind, APIVersion: version, ResourceVersion: listMeta.GetResourceVersion(), }, nil } return &v1.ObjectReference{ Kind: kind, APIVersion: version, Name: objectMeta.GetName(), Namespace: objectMeta.GetNamespace(), UID: objectMeta.GetUID(), ResourceVersion: objectMeta.GetResourceVersion(), }, nil }
看到最后其实会帮助我们构造v1.ObjectReference对象,但前提是能正确获取到从传入CRD的objectMeta、kind、version,一开始的报错就是因为 scheme.ObjectKinds(obj)
获取kind时返回error导致。
怎么才能正确获取到kind、version、objectMeta呢?可以查看官方提供的sample-controller,这也是最简单的通过CRD扩展的控制器示例:
// Create event broadcaster // Add sample-controller types to the default Kubernetes Scheme so Events can be // logged for sample-controller types. utilruntime.Must(samplescheme.AddToScheme(scheme.Scheme))
创建事件广播器,将样本控制器类型添加到默认的Kubernetes方案,以便可以记录事件以获取样本控制器类型。
samplescheme.AddToScheme(scheme.Scheme)
会去注册一些GroupVersion、kind等信息到Scheme,这样在记录events时才可以正确获取到。对应到我们的CRD,redisCluster对象,就应该是
utilruntime.Must(redisscheme.AddToScheme(scheme.Scheme))
这里的redisscheme是指向code-generator生成的clientset包下的versioned/scheme,register.go中
根据golang代码执行顺序可知,被调用时,init函数会被执行,从而调用 v1.AddToGroupVersion
进行注册流程:
这样分析,顺便把代码修改一番后,重新调试,可以看到正确记录了events事件: