Skip to main content

runtime.Object

在本节中,我们会基于Kubernetes源码(k8s.io/api库和k8s.io/apimachinery库)进一步去诠释先前在再识kind引入的论述——"在Kubernetes中,所有需要被序列化/反序列化的Go类型都被称为kind"。

为了说明此论述,本节的主要内容包括:

  • k8s.io/apimachinery库中如何抽象Kubernetes API类型系统(资源/kind);
  • k8s.io/apimachinery库中对资源编/解码过程的一般行为。

需要在这里强调的是,本小节的内容以文档API Conventions 中关于资源/kind必须要遵循的约定为基础。 我们认为文档API Conventions 是社区开发者想要在语言中立的情况下对Kubernetes API类型系统所做的规范,以便开发者可以依据规范利用不同的语言来实现客户端以及控制器。 所以,我们可以这样看待文档API Conventions 与Kubernetes源码的关系——k8s.io/api库和k8s.io/apimachinery库是基于该文档的一种实现方案。

当然,文档API Conventions 本身是后于Kubernetes源码产生的,与其说社区开发者想要保证"语言中立",不如说文档中的约定其实是对Kubernetes源码中API类型实现上的总结。🌧️

在本小节中我们将结合Kubernetes源码(Go语言)对该文档做进一步的诠释。

kind与runtime.Object1

在之前的小节中,我们知道了在Kubernetes中,kind是某一类Go类型(如type Pod struct {...}type Service struct {...})的统称。 这算是一种文字上的总结。 那么在Kubernetes中,如何从源码角度去描述"所有类型都属于"这种文字性质的总结呢? 在Go语言中,我们知道接口(interface)提供了一种抽象能力,所以我们可以通过“所有实现了某个接口的对象都被称为kind”来体现这种文字上的抽象。

事实也的确如此,在Kubernetes源码中,所有kindk8s.io/api)其实都实现了apimachinery库中的runtime.Object接口。 换一种叙述方式,也就是:在Kubernetes中,所有实现了runtime.Object接口的Go类型被称为kind

下图展示了k8s.io/api库中定义的kindk8s.io/apimachinery库中的基础接口的关系:

k8s.io/api/batch/v1type Job struct { // ...}k8s.io/api/apps/v1type Deployment struct { // ...}k8s.io/api/core/v1type Pod struct { // ...}k8s.io/apimachinery/pkgs/runtimetype Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object}k8s.io/apimachinery/pkgs/runtimetype ObjectKind interface { SetGroupVersionKind(kind GroupVersionKind) GroupVersionKind() GroupVersionKind}©Xudong Wang🤖️🎈k8s.io/apik8s.io/apimachinerykind

我们先来看看runtime.Object接口的具体定义:

k8s.io/apimachinery/pkg/runtime/interfaces.go
type Object interface {
GetObjectKind() schema.ObjectKind
DeepCopyObject() Object
}

这个接口包含两个方法:

  1. GetObjectKind()返回的是另一个schema.ObjectKind接口:

    schema.ObjectKind接口
    k8s.io/apimachinery/pkg/runtime/schema/interfaces.go
    type ObjectKind interface {
    SetGroupVersionKind(kind GroupVersionKind)
    GroupVersionKind() GroupVersionKind
    }

    schema.ObjectKind接口的两个方法用于获取及设置GVK信息。 总之,runtime.Object的第一个方法实际上规范了实现类必须能够获取/设置本类型对应的GVK信息。

  2. DeepCopyObject() 看起来是个非常基础的方法,即实现类需要提供一个能够深拷贝自身的方法。🎈

相比于在之前小节中对kind偏向于概念性的总结,在本小节中,我们侧重于真正从源码实现的角度给出kind的定义。

metav1.TypeMeta

另外,需要在这里提及的是在apimachinery库中,metav1.TypeMeta这个元类型2实现了runtime.Object接口的第一个方法GetObjectKind() schema.ObjectKind🎈

apimachinery库的metav1.TypeMeta元类型
k8s.io/apimachinery/pkg/apis/meta/v1/types.go
type TypeMeta struct {
Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}
k8s.io/apimachinery/pkg/apis/meta/v1/meta.go
func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj }

在具体实现上,它包含的类成员命名也符合API Conventions 中关于kind元字段名称的要求。 根据约定,文档要求Kubernetes资源必须有名为kind以及apiversion字段分别表示类型名称以及分组和版本信息:

Resources

All JSON objects returned by an API MUST have the following fields:

  • kind: a string that identifies the schema this object should have
  • apiVersion: a string that identifies the version of the schema the object should have

These fields are required for proper decoding of the object.

意思是说,kindapiVersion这两个字段需要用于解码(反序列化)资源。

apimachinery库中与反序列化资源有关的代码也确实会检查这两个字段以分析得出资源的GVK信息

apimachinery库中序列化资源时推断GVK信息的逻辑
k8s.io/apimachinery/pkg/api/meta/help.go
func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
findKind := struct {
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// +optional
Kind string `json:"kind,omitempty"`
}{}
if err := json.Unmarshal(data, &findKind); err != nil {
return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err)
}
gv, err := schema.ParseGroupVersion(findKind.APIVersion)
if err != nil {
return nil, err
}
return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil
}

这只是apimachinery库反序列化最初的步骤。在后续小节序列化器与序列化器工厂中,我们还会进一步介绍apimachinery库反序列化的后续步骤。

我们在这里突然提及metav1.TypeMeta似乎有点"唐突"。 不过在本节,你只需要了解到这个事实即可。这个知识点是作为后续kind案例探究小节的预备知识而不得不在这里提前指出。

小结

目前我们已经揭露了Kubernetes中kind这个概念与apimachinery库中runtime.Object接口的关系。

不过在之前的小节中,我们基于Kubernetes API Reference Doc kind继续细分为了三个种类, 但也仍是基于文字性的叙述去描述每种分类。 其中kind的第三个种类过于特殊,在类型上没有具体要遵守的约定,因此我们不再深入研究。 对于kind的单体种类以及集合种类,它们在Kubernetes源码中也分别有对应的接口抽象。 下面我们将继续从Kubernetes源码角度探讨kind的单体种类以及集合种类的本质。

kind的单体种类与metav1.Object

对于单体种类,由于它属于kind,根据先前的论述,它必然实现了runtime.Object接口。除此以外,作为单体种类,它们还需要实现metav1.Object接口:

pkg/apimachinery/pkg/api/meta/v1/meta.go
type Object interface {
GetNamespace() string
SetNamespace(namespace string)
GetName() string
SetName(name string)
GetGenerateName() string
SetGenerateName(name string)
GetUID() types.UID
SetUID(uid types.UID)
GetResourceVersion() string
SetResourceVersion(version string)
// ...
}

从接口的定义可以看出单体类型需要提供获取/设置一些元信息例如名称(name),命名空间(namespace),标签(labels)注解(annotations)等的方法。

下图以Pod类型为例,它其实实现了runtime.Objectmetav1.Object两个接口:

k8s.io/api/core/v1type Pod struct { // ...}k8s.io/apimachinery/pkgs/runtimetype Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object}k8s.io/apimachinery/pkgs/meta/v1type Object interface { GetNamespace() string SetNamespace(namespace string) GetName() string SetName(name string)}k8s.io/apik8s.io/apimachinery©Xudong Wang🤖️🎈

metav1.ObjectMeta元类型

另外,有必要在本节提及的是apimachinery库中, metav1.ObjectMeta元类型实现了metav1.Object接口。🎈

metav1.ObjectMeta元类型
k8s.io/apimachinery/pkg/apis/meta/v1/types.go
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"`
Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"`
UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`
Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"`
CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"`
DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"`
DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"`
Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`
OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"`
Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"`
ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"`
}
k8s.io/apimachinery/pkg/apis/meta/v1/meta.go
func (meta *ObjectMeta) GetNamespace() string                { return meta.Namespace }
func (meta *ObjectMeta) SetNamespace(namespace string) { meta.Namespace = namespace }
func (meta *ObjectMeta) GetName() string { return meta.Name }
func (meta *ObjectMeta) SetName(name string) { meta.Name = name }
func (meta *ObjectMeta) GetGenerateName() string { return meta.GenerateName }
func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName }
func (meta *ObjectMeta) GetUID() types.UID { return meta.UID }
func (meta *ObjectMeta) SetUID(uid types.UID) { meta.UID = uid }
func (meta *ObjectMeta) GetResourceVersion() string { return meta.ResourceVersion }
func (meta *ObjectMeta) SetResourceVersion(version string) { meta.ResourceVersion = version }
func (meta *ObjectMeta) GetGeneration() int64 { return meta.Generation }
func (meta *ObjectMeta) SetGeneration(generation int64) { meta.Generation = generation }
func (meta *ObjectMeta) GetSelfLink() string { return meta.SelfLink }
func (meta *ObjectMeta) SetSelfLink(selfLink string) { meta.SelfLink = selfLink }
func (meta *ObjectMeta) GetCreationTimestamp() Time { return meta.CreationTimestamp }
func (meta *ObjectMeta) SetCreationTimestamp(creationTimestamp Time) { meta.CreationTimestamp = creationTimestamp }
func (meta *ObjectMeta) GetDeletionTimestamp() *Time { return meta.DeletionTimestamp }
func (meta *ObjectMeta) SetDeletionTimestamp(deletionTimestamp *Time) { meta.DeletionTimestamp = deletionTimestamp }
func (meta *ObjectMeta) GetDeletionGracePeriodSeconds() *int64 { return meta.DeletionGracePeriodSeconds }
func (meta *ObjectMeta) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) { meta.DeletionGracePeriodSeconds = deletionGracePeriodSeconds }
func (meta *ObjectMeta) GetLabels() map[string]string { return meta.Labels }
func (meta *ObjectMeta) SetLabels(labels map[string]string) { meta.Labels = labels }
func (meta *ObjectMeta) GetAnnotations() map[string]string { return meta.Annotations }
func (meta *ObjectMeta) SetAnnotations(annotations map[string]string) { meta.Annotations = annotations }
func (meta *ObjectMeta) GetFinalizers() []string { return meta.Finalizers }
func (meta *ObjectMeta) SetFinalizers(finalizers []string) { meta.Finalizers = finalizers }
func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference { return meta.OwnerReferences }
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) { meta.OwnerReferences = references }
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields }
func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) { meta.ManagedFields = managedFields }

在具体实现上,metav1.Object类成员命名也符合API Conventions 中关于单体类型元字段的规范。

根据文档API Conventions ,对于单体种类,它们必须包含名为metadata的字段,同时在metadata字段下由必须要有名为namespacenameuid等字段。 另外,它们应该带有resourceVersiongenerationlabelsannotationsdeletionTimestampcreationTimestamp等字段。

单体类型的实现约定

Objects

Metadata Every object kind MUST have the following metadata in a nested object field called "metadata":

  • namespace: a namespace is a DNS compatible label that objects are subdivided into. The default namespace is 'default'. See the namespace docs for more.
  • name: a string that uniquely identifies this object within the current namespace (see the identifiers docs). This value is used in the path when retrieving an individual object.
  • uid: a unique in time and space value (typically an RFC 4122 generated identifier, see the identifiers docs) used to distinguish between objects with the same name that have been deleted and recreated

Every object SHOULD have the following metadata in a nested object field called "metadata":

  • resourceVersion: a string that identifies the internal version of this object that can be used by clients to determine when objects have changed. This value MUST be treated as opaque by clients and passed unmodified back to the server. Clients should not assume that the resource version has meaning across namespaces, different kinds of resources, or different servers. (See concurrency control, below, for more details.)
  • generation: a sequence number representing a specific generation of the desired state. Set by the system and monotonically increasing, per-resource. May be compared, such as for RAW and WAW consistency.
  • creationTimestamp: a string representing an RFC 3339 date of the date and time an object was created
  • deletionTimestamp: a string representing an RFC 3339 date of the date and time after which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource will be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field except when the object has a finalizer set. In case the finalizer is set the deletion of the object is postponed at least until the finalizer is removed. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time.
  • labels: a map of string keys and values that can be used to organize and categorize objects (see the labels docs)
  • annotations: a map of string keys and values that can be used by external tooling to store and retrieve arbitrary metadata about this object (see the annotations docs)

我们目前为止只是陈述了kind中的单体种类(如Pod)实现了metav1.Object接口这个事实。但是却没有 补充kind中的单体种类是如何实现metav1.Object的具体细节。 在后续kind案例探究小节我们还会解释具体的kindk8s.io/api)是如何利用元类型(k8s.io/apimachinery)来间接实现这些抽象接口的——这也是我们在本节提及k8s.io/apimachinery库中元类型的动机。

集合种类

对于集合种类,除了要实现runtime.Object接口外,它们还需要实现metav1.ListInterface接口:

pkg/apis/meta/v1/meta.go
type ListInterface interface {
GetResourceVersion() string
SetResourceVersion(version string)
GetSelfLink() string
SetSelfLink(selfLink string)
GetContinue() string
SetContinue(c string)
GetRemainingItemCount() *int64
SetRemainingItemCount(c *int64)
}

下图以PodList类型为例,展示了它所实现的两个接口:

k8s.io/api/core/v1type PodList struct { Items []Pod // ...}k8s.io/apimachinery/pkgs/runtimetype Object interface { GetObjectKind() schema.ObjectKind DeepCopyObject() Object}k8s.io/apimachinery/pkgs/meta/v1type ListInterface interface { GetResourceVersion() string SetResourceVersion(version string) // ... }k8s.io/apik8s.io/apimachinery©Xudong Wang🤖️🎈

根据API Conventions ,集合种类在实现上还必须要包含名为Items的数组,并且类型名称必须要以"List"结尾。

Lists are collections of resources of one (usually) or more (occasionally) kinds.

The name of a list kind must end with "List". Lists have a limited set of common metadata. All lists use the required "items" field to contain the array of objects they return. Any kind that has the "items" field must be a list kind.

另外,需要在这里提及的是在apimachinery库中,metav1.ListMeta类虽然实现了metav1.ListInterface接口,但是并没有包含API Conventions 所要求的Items字段🎈

至于metav1.ListMeta没有加入Items的切片成员的原因,私以为可能是metav1.ListInterface接口本身就没有与Items相关的方法。🌧️metav1.ListInterface接口没有与Items相关的方法的原因可能是开发者本身设计上的疏忽。🌧️证据是: 在apimachinery库中有一个判断类型是否为集合种类的函数IsListType

IsListType函数
k8s.io/apimachinery/pkg/api/meta/help.go
// IsListType returns true if the provided Object has a slice called Items.
// TODO: Replace the code in this check with an interface comparison by
// creating and enforcing that lists implement a list accessor.
func IsListType(obj runtime.Object) bool {
switch t := obj.(type) {
case runtime.Unstructured:
return t.IsList()
}
t := reflect.TypeOf(obj)

isListCache.lock.RLock()
ok, exists := isListCache.byType[t]
isListCache.lock.RUnlock()

if !exists {
_, err := getItemsPtr(obj)
ok = err == nil

// cache only the first 1024 types
isListCache.lock.Lock()
if len(isListCache.byType) < 1024 {
isListCache.byType[t] = ok
}
isListCache.lock.Unlock()
}

return ok
}

IsListType函数的函数文档中也明确说明了:

IsListType returns true if the provided Object has a slice called Items.

它是通过检查类型中是否存在一个Items的slice字段来判断类型是否为集合。 同时,在此函数的TODO信息中,我们可以看到这样一句话:

TODO: Replace the code in this check with an interface comparison by creating and enforcing that lists implement a list accessor.

说明通过检查类型中是否含有Items字段在官方开发者看来确实并不是一个好的方式,通过检查类型是否实现了metav1.ListInterface接口来判断更具有说服力。 所以在将来metav1.ListInterface可能会加上类似于GetItems()的方法。🌧️

小结

到现在为止我们总结了Kubernetes源码是如何遵循API Conventions 中对资源/kind字段所做的约定。 具体来说,首先Kubernetes源码k8s.io/apimachinery库通过Go接口的方式抽象了文档中对于单体种类以及集合种类的具体要求。 其次k8s.io/apimachinery库中的元类型实现了所抽象的接口并在成员变量的设置上也符合API Conventions 资源/kind需要所包含的元信息的要求。

初识3apimachinery库的序列化器

目前,我们似乎只论证了kind是指实现了runtime.Object的Go类型。然而本节最开始对kind的扩充定义是:所有需要被序列化/反序列化的Go类型都被称为kind"。 接下来,我们将"连带"介绍apimachinery库中的序列化器与runtime.Object的关系以完整的论述本节开始对kind的扩充定义。

runtime.Object接口本身其实并没有包含与编/解码直接相关的方法。 为了说清楚runtime.Object接口与编/解码的关系,我们需要从apimachinery库的序列化原理步骤说起。 我们在这里先直接给出结论,在后续序列化器与序列化器工厂章节我们还会给出具体介绍apimachinery库的序列化原理。

apimachinery库中,直接参与对kind编/解码操作的是序列化器(serializers)4。 而apimachinery库中的序列化器依赖于一个叫做runtime.Scheme5结构(也就是序列化器类型包含一个runtime.Scheme类型的成员)。 对于runtime.Scheme类型,你可以简单把它理解成一张Go类型的"注册表"。 只有在scheme6结构中“注册”过的Go类型才可以被序列化器序编/解码

runtime.Scheme与kind

在上一小节中,我们在介绍apimachinery库中的序列化器时引入了runtime.Scheme类型。 那么在前文所谓的“注册”类型具体指什么呢? 在runtime.Scheme中存在一个map类型的成员(映射表)7——gvkToType:

k8s.io/apimachinery/pkg/runtime/scheme.go
type Scheme struct {
gvkToType map[schema.GroupVersionKind]reflect.Type

// ...
}

同时,runtime.Scheme也对外提供了往此map中插入(注册)数据的方法:

k8s.io/apimachinery/pkg/runtime/scheme.go
func (s *Scheme) AddKnownTypeWithName(gvk schema.GroupVersionKind, obj Object) {
// ...
}

我们不需要关心这个方法的具体实现,我们现在只需要知道此方法可以将类型为runtime.Object(即参数obj)的对象以及对应的GVK信息(参数gvk)注册进gvkToType这张表中4。 也就是说,Scheme只能注册runtime.Object类型的对象。 而根据之前的定义,kind就是实现了runtime.Object接口的Go类型的统称,所以,我们可以说Scheme类型用于注册kind。

serializerschemeruntime.Object&Job{}{"batch","v1","Job"}&Pod{}{"","v1","Pod"}&ListOptions{}{"meta.k8s.io","v1","ListOptions"}&Status{}{"","v1","Status"}&JobList{}{"batch","v1","JobList"}&PodList{}{"","v1","PodList"}.........schemegvktype......registerregisterregisterpod([]bytes)&Pod{}©Xudong Wang🤖️🎈
编/解码"注册"机制的历史

Kubernetes中编/解码的"注册"机制最早可以追溯到 Pull Request #196

Pull Request #196中,开发者首次将kind直接作为字段嵌入API的JSON Schema8中,这样做的动机在作者看来是:"Make api able to marshal its types correctly"。 在 Pull Request #196之前,其实kube-apiserver甚至与常见Go的REST HTTP Server并本质区别。对于每个API请求,handler调用json.Unmarshal()/json.Marshal()进行朴素的反序列化/序列化即可。 这种方式的一个缺点是每个API的handler都要事先知道此API请求/返回体对应的Go类型,才能调用json.Unmarshal/json.Marshal。 在 Pull Request #196之后,在对API请求体的序列化/反序列化的处理发生了质的转变。 具体来说,开发者第一次引入了Go类型注册机制:

pkg/api/helper.go
var knownTypes = map[string]reflect.Type{}

func init() {
AddKnownTypes(
PodList{},
Pod{},
ReplicationControllerList{},
ReplicationController{},
ServiceList{},
Service{},
Status{},
)
}

func AddKnownTypes(types ...interface{}) {
for _, obj := range types {
t := reflect.TypeOf(obj)
knownTypes[t.Name()] = t
}
}

其中knownTypes变量用于将类型名称(string)映射到具体的反射类型(reflect.Type)。同时在init()函数中,将所有API的Go类型注册进knownTypes这张表中。 这么做的好处是,对于任意的HTTP请求,我们可以通过一个统一的解码函数来处理请求体的JSON字节序列([]byte)的反序列化:

pkg/api/helper.go
func Decode(data []byte) (interface{}, error) {
findKind := struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way, we understand both.
err := yaml.Unmarshal(data, &findKind)
if err != nil {
return nil, fmt.Errorf("Couldn't get kind: %#v", err)
}
objType, found := knownTypes[findKind.Kind]
if !found {
return nil, fmt.Errorf("%v is not a known type", findKind.Kind)
}
obj := reflect.New(objType).Interface()
err = yaml.Unmarshal(data, obj)
if err != nil {
return nil, err
}
_, jsonBase, err := nameAndJSONBase(obj)
if err != nil {
return nil, err
}
// Don't leave these set. Track type with go's type.
jsonBase.Kind = ""
return obj, nil
}

这个解码函数的逻辑非常简单清晰,首先捕获字节序列datakind字段,获取类型信息以后,然后在knownTypes表中里直接查找到对应的Go类型,最后调用json.Unmarshal()完成反序列化工作即可。

knownTypes正是后来runtime.Scheme类型的前身。彼时,甚至资源的概念还未在社区形成,API分组并不存在,apiVersion字段也没有引入API的JSON Schema。 因此,相比于现在Kubernetes使用完整的GVK信息映射到具体的Go类型, 当时knownTypes的映射关系十分朴素:kind直接到Go类型。

小结

小结

至此,从kind与runtime.Object的关系,从序列化器与runtime.Scheme关系,从runtime.Schemeruntime.Object的关系, 我们的论述形成了逻辑闭环——我们从源码的角度阐述了"在Kubernetes中,任何需要序列化/反序列化的Go类型统称为kind"。


  1. 本文中所出现的runtimeapimachinery库下runtime包。
  2. 本文中所出现的metav1apimachinerymeta包的v1子包。
  3. 在后续序列化器与序列化器工厂小节我们还会对apimachinery中的序列化器做进一步介绍。
  4. 这里的序列化器并不指代apimachinery库中某一个具体的类型,在apimachinery库中有多种行为复杂的序列化器。在本节中,我们用序列化器指代apimachinery库中各种序列化器的一般行为。
  5. 注意与前文提到的schema包区分,此处为runtime包下的Scheme类型。
  6. 在不作另外说明的情况下,本书所出现的scheme表示runtime.Scheme类型的实例。
  7. runtime.Scheme中,不仅仅存在gvkToType这一张映射表,同时,方法AddKnownTypeWithName也不仅仅向一张映射表中插入数据。 我们在这尽量抹去实现上的一些细节,侧重于说明runtime.Scheme的一个重要作用——注册。
  8. 彼时资源的概念甚至还没有正式形成。在Kubernetes早期阶段,官方开发者更倾向于使用术语API对象(API Object)或是API类型(API Type)来指代API的JSON Schema。API对象(API Object)首次出现在 Pull Request #529