欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

【k8s源码篇】k8s类型定义3之顶层设计runtime.Object接口

时间:2023-05-14
揭开apimachinery runtime 的秘密

参考:深入剖析kubernetes的API对象类型定义 1 | schema

上一节我们学习到 metav1.Typemeta 就是 sechema.ObjectKind 接口的实现

回顾 metav1.Typemeta 结构体

type Typemeta struct { // Kind 是一个字符串值,表示此对象所代表的 REST 资源。//服务器可以从客户端向其提交请求的端点推断出这一点。//无法更新。Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"` // APIVersion定义对象表示的版本化架构。服务器应将已识别的架构转换为最新的内部值,并且可能会拒绝未识别的值。APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`}

可以看出该结构体的字段与我们编写yaml时候的 apiversion,kind 一一对应的

但是在实际的代码编写过程中,实际标记 API 对象采用的是 GVK 模式

https://xinchen.blog.csdn.net/article/details/113715847就是 Group、Version、KindGroup和 Version才是xxx.yaml的apiVersion字段

那么可能有个疑问? —— 为什么创建 pod 的时候 apiversion 只写了 v1?因为这个省略了核心组core,这个是由于历史原因导致的,最开始 API 种类少,所以没有 group 概念,后来产生了因此解析不带 group 的,会默认认为是 核心 core group Kind 就是我们的 资源 种类,如 Pod、Deployment 等

查看下面代码可以更清晰明白

schema.ObjecKind 所定义的方法目的就是

GroupVersionKind() GroupVersionKind 将 apiversion,kind 装换为 GVK(GroupVerisonKind)结构体SetGroupVersionKind(kind GroupVersionKind)将 GVK(GroupVerisonKind)结构体装换为 apiversion,kind其实就是 GVK 和 apiversion,kind 两种表现形式的转换

因此总结为

schema.ObjecKind是所有API对象类型meta的抽象metav1.Typemeta是schema.ObjecKind的一个实现,API对象类型通过继承metav1.Typemeta实现schema.ObjecKind

// 代码源自k8s.io/apimachinery/pkg/runtime/schema/interfaces.go// ObjectKind是接口,两个接口函数是GroupVersionKind类型的setter和gettertype ObjectKind interface { SetGroupVersionKind(kind GroupVersionKind) GroupVersionKind() GroupVersionKind}// GroupVersionKind才是kubernetes的API对象类型真身,他包括Kind、Version和Group。其中Kind和// Version还比较好理解,Group又是什么?其实Group/Version才是xxx.yaml的apiVersion字段。// 在kuberentes中API对象是分组的,像Pod、Service、ConfigMap都属于core分组,而core分组的对象// 无需在apiVersion字段明文写出来,系统会默认将这类的对象归为core分组,正如文章开始那个Pod的例子。// 详情可以看下面的代码实现。type GroupVersionKind struct { Group string Version string Kind string}// 这个函数在metav1.Typemeta实现GroupVersionKind()接口的时候调用了,该函数调用了ParseGroupVersion// 实现从apiVersion解析Group和Version。func FromAPIVersionAndKind(apiVersion, kind string) GroupVersionKind { if gv, err := ParseGroupVersion(apiVersion); err == nil { return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind} } return GroupVersionKind{Kind: kind}}// 从apiVersion解析Group和Version。func ParseGroupVersion(gv string) (GroupVersion, error) { // 这种不报错是什么道理?什么情况下会有对象没有Group和Version? if (len(gv) == 0) || (gv == "/") { return GroupVersion{}, nil } // 数apiVersion中有几个‘/’ switch strings.Count(gv, "/") { // 没有'/',就像文章开始的Pod的例子,那么Group就是空字符串,系统默认会把空字符串归为core case 0: return GroupVersion{"", gv}, nil // 有一个'/',那么就以'/'分割apiVersion,左边为Group,右边为Version。 case 1: i := strings.Index(gv, "/") return GroupVersion{gv[:i], gv[i+1:]}, nil // 其他则为格式错误。 default: return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv) }}

2 | Object

前言知识

schema.ObjecKind是所有API对象类型的抽象(上面介绍)metav1.Object作为所有API单体对象公共属性的抽象(第二节介绍)

因此将上面两个组合,我们似乎已经找到了所有API对象的根,就是可以对所有 API 对象获取和操作

但是,考虑一个问题? —— 如果我们只想或只能用一个基类指针指向(获取)任意 API 单体对象呢?schema.ObjecKind和metav1.Object感觉都不合适,因为他们所能访问的域是有限

只用一个,只能对类型或公共属性其中一项操作 所以,如果有一个函数需要访问任何API对象的类型和公共属性,那么就要传入同一个对象的两个指针(schema.ObjecKind和metav1.Object)

但这个就不太满足我们的要求,我们只想用一个指针

所以?有没有一个类型作为API单体对象的统一的基类呢?

这就是本节要讨论的:runtime.Object(本章节简称 Object)

可以理解为runtime.Object对 schema.ObjecKind和metav1.Object两个接口进行了封装这样只需调用 runtime.Object 即可获取到 schema.ObjecKind和metav1.Object,从而对所有 API 对象进行操作

具体操作见下面代码

所以对于 runtime 的要求是可以承接任意 API 对象(具有 schema.ObjecKind和metav1.Object)

GetObjectKind() schema.ObjectKind 就是上面所说的 metav1.Typemeta的顶级抽象接口,用于获取类型meta元信息

前几节说过,metav1.Typemeta是所有API对象的父类,毕竟所有的对象都要说明自己是什么类型,无论单体还是列表都会具有,所以都实现了 schema.ObjecKind 接口 DeepCopyObject() Object 这个方法是深复制方法,所有 API 对象都会具有上面两个方法,所有 API 对象都会具有,所以runtime.Object 可以承接所有 API 对象那现在有个问题,缺少公共属性接口metav1.Object,不应该有一个类似GetObjectmeta()的接口吗?来获取metav1.Object接口。

这里 k8s 采用另一种实现k8s.io/apimachinery/pkg/api/meta/meta.go 定义Accessor()函数,他可以把obj安全的转换为metav1.Object为什么这么做?大部分情况,所有 API 对象都会继承 metav1.Objectmeta 结构体,其正是metav1.Object 接口的实现,直接返回即可另一种情况,没有直接继承 metav1.Objectmeta 结构体,可以通过case metav1.ObjectmetaAccessor 来返回metav1.Object 接口这里具体调用逻辑还不太清晰,暂且这么理解

runtime.Object 可以承接所有 API 对象,metav1.Object 可以通过Accessor()函数获取

// 代码源自k8s.io/apimachinery/pkg/runtime/interfaces.go// 此处可以理解为 Object 接口继承type Object interface { // 有了这个函数,就可以访问对象的类型域 GetObjectKind() schema.ObjectKind // deepcopy是golang深度复制对象的方法,至于什么是深度复制本文就不解释了。这是个不错的函数, // 可以通过这个接口复制任何API对象而无需类型依赖。 // deepcopy是golang深度复制对象的方法,各个类型都实现了此方法 见下面 pod 例子 DeepCopyObject() Object // 就这么两个函数了么?那如果需要访问对象的公共属性域怎么办?不应该有一个类似GetObjectmeta() // 的接口么?这一点,kubernetes是通过另一个方式实现的,见下面的代码。} // 代码源自k8s.io/apimachinery/pkg/api/meta/meta.go,注意是api包,不是apis// Accessor()函数可以把obj安全的转换为metav1.Object,这样也就避免了每个API对象类型都需要实现// 类似GetObjectmeta()的接口了。有的读者肯定会问:所有的API对象都继承了metav1.Objectmeta,// 这个类型不是实现了GetObjectmeta()么?笔者就要在这里做出说明:笔者提到是类似GetObjectmeta(),// 如果接口名字是Objectmeta(),那岂不是继承metav1.Objectmeta就没用了?一个顶层的类型抽象定义不// 应该依赖于相对底层类型的实现。func Accessor(obj interface{}) (metav1.Object, error) { // 使用了golang的switch type语法 switch t := obj.(type) { // 因为API对象类型都继承了metav1.Objectmeta,也就自然实现了metav1.Object。 case metav1.Object: return t, nil // 在Objectmeta章节笔者提到了,metav1.Objectmeta实现了metav1.ObjectmetaAccessor, // 所以API对象也自然实现了metav1.ObjectmetaAccessor。但是API对象会在上一个case就返回 // 了,这个case是给谁用的呢?笔者也比较疑惑,笔者感觉是那些没有直接继承metav1.Objectmeta // 却实现了metav1.ObjectmetaAccessor的类型,笔者暂时还没找到相关类型定义。 case metav1.ObjectmetaAccessor: if m := t.GetObjectmeta(); m != nil { return m, nil } return nil, errNotObject default: return nil, errNotObject }}//deepcopy是golang深度复制对象的方法,各个类型都实现了此方法//core>v1>zz_generated.deepcopy.go// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.func (in *Pod) DeepCopyObject() runtime.Object {if c := in.DeepCopy(); c != nil {return c}return nil}

等下,为什么没有看到API对象实现runtime.Object.DeepCopyObject()?那是因为deep copy是具体API对象类型需要实现的,存在类型依赖,作为API对象类型的父类不能实现。此处还是以Pod为例,看看Pod是如何实现DeepCopyObject()的。

// +genclient// +genclient:method=GetEphemeralContainers,verb=get,subresource=ephemeralcontainers,result=EphemeralContainers// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers,input=EphemeralContainers,result=EphemeralContainers// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object// 上面+k8s:deepcopy-gen:....就是告诉代码生成工具为下面的类型生成runtime.Object接口的// DeepCopyObject()函数实现。因为所有的API对象类型都要实现DeepCopyObject()函数,这是一个相当// 大量的重复工作,所以kubernetes用代码生成工具来实现。至于如何实现的不作为本文讨论重点,只要读者// 知道deep copy的目的就可以了。type Pod struct { ......}

补充说明(未深入理解)

参考:https://blog.csdn.net/u014618114/article/details/113182770

如前所述,K8S的资源实现了runtime.object接口,其实大多数资源结构体继承了Objectmeta也实现了metav1.object接口来获取资源的公共属性。若资源要访问类型之外的公共属性,主要是Objectmeta结构相关,使用以下的处理方式。Accessor()函数可以把obj安全的转换为metav1.Object,这样也就避免了每个API对象类型都需要实现类似GetObjectmeta()的接口了。meta包中使用了metadataAccessor 接口聚合了对Objectmeta和Typemeta的属性访问和设置。

// meta包中使用了metadataAccessor 接口聚合了对Objectmeta和Typemeta的属性访问和设置。// metadataAccessor exposes Interface in a way that can be used with multiple objects.type metadataAccessor interface {APIVersion(obj runtime.Object) (string, error)SetAPIVersion(obj runtime.Object, version string) errorKind(obj runtime.Object) (string, error)SetKind(obj runtime.Object, kind string) errorNamespace(obj runtime.Object) (string, error)SetNamespace(obj runtime.Object, namespace string) error...}//k8s.io/apimachinery/pkg/api/meta/meta.go//实现了对Objectmeta的属性访问和设置func Accessor(obj interface{}) (metav1.Object, error) { // 使用了golang的switch type语法 switch t := obj.(type) { // 因为API对象类型都继承了metav1.Objectmeta,也就自然实现了metav1.Object。 case metav1.Object: return t, nil // metav1.Objectmeta也实现了metav1.ObjectmetaAccessor case metav1.ObjectmetaAccessor: if m := t.GetObjectmeta(); m != nil { return m, nil } return nil, errNotObject default: return nil, errNotObject }}//实现了对Typemeta的属性访问和设置type objectAccessor struct { runtime.Object}func (obj objectAccessor) GetKind() string {return obj.GetObjectKind().GroupVersionKind().Kind}func (obj objectAccessor) SetKind(kind string) {gvk := obj.GetObjectKind().GroupVersionKind()gvk.Kind = kindobj.GetObjectKind().SetGroupVersionKind(gvk)}...func NewAccessor() metadataAccessor {return resourceAccessor{}}// resourceAccessor implements ResourceVersioner and Selflinker.//聚合objectAccessor结构体方法和Accessor函数实现了metadataAccessor接口type resourceAccessor struct{} func (resourceAccessor) APIVersion(obj runtime.Object) (string, error) {return objectAccessor{obj}.GetAPIVersion(), nil}func (resourceAccessor) SetAPIVersion(obj runtime.Object, version string) error {objectAccessor{obj}.SetAPIVersion(version)return nil}func (resourceAccessor) Namespace(obj runtime.Object) (string, error) {accessor, err := Accessor(obj)if err != nil {return "", err}return accessor.GetNamespace(), nil}func (resourceAccessor) SetNamespace(obj runtime.Object, namespace string) error {accessor, err := Accessor(obj)if err != nil {return err}accessor.SetNamespace(namespace)return nil}

3 | 总结

至此,前面各章节的总结都可以忘掉了,因为那些总结都是基于当时的知识背景做的总结,可能缺乏全局性的考虑做出错误的结论,所以在此做出通盘的总结,如下图所示:

runtime.Object是所有API单体对象的根类(interface);schema.ObjectKind是对API对象类型的抽象(interface);metav1.Object是对API对象公共属性的抽象(interface);metav1.ListInterface是对API对象列表公共属性的抽象(interface);metav1.Typemeta是schema.ObjectKind的一个实现,API对象类型继承之;metav1.Objectmeta是metav1.Object的一个实现,API对象类型继承之;metav1.Listmeta是metav1.ListInterface的一个实现,API对象列表继承之;

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。