ApiServer Codec
在Kubernetes源码分析– API Server之API Install篇中,我们了解到K8S可以支持多版本的API,但是Rest API的不同版本中接口的输入输出参数的格式是有差别的,Kubernetes是怎么处理这个问题的呢?另外Kubernetes支持yaml、json两个格式的配置,同时又能够支持json、yaml和pb等格式的编解码进行协议传输,那么Kubernetes又是如何实现各种数据对象的序列化、反序列化的呢?
runtime.Scheme
Serializer
Serializer是用于对象于序列化格式之间进行转的核心接口,它的定义如下所示:
|
|
目前系统支持的序列化格式有三种:json、yaml和protobuf。可以参考下一节CodecFactory,这里描述了三种序列化格式的初始化。
- json与yaml序列化
json与yaml序列化的代码在k8s.io/apimachinary/pkg/runtime/serializer/json/json.go,他们共享一个结构,如下所示:
|
|
通过成员 yaml来区分是json还是yaml序列化器;creater和typer成员传入的都是Scheme对象,前面我们分析过Scheme,它可以让我们根据GVK来创建对应的实例,并可以判断一个对象的GVK,这将在我们后续的编解码过程中起到重要作用;而meta成员是一个MetaFactory的对象,它主要用于从序列化数据(这里就是json或者yaml)中获得对象的GroupVersionKind。(下一节也有说明)
下面我们看看Encode方法,Encode方法比较简单,只需要按需把对象,转换为json或yaml格式即可。代码如下所示:
|
|
Decode比较复杂,它尝试把提供的数据转换成YAML或者JSON,从中提取出其中存储的schema、kind,并合并缺省的gvk,然后在数据加载到对象中,注意该对象满足前面指定的schema、kind。或者也可以指定into字段(这时候会加载到into对象中)。
- 如果into为*runtime.Unknown,则不会执行解码操邹,而直接把裸数据提取出来。
- 如果into没有在typer(也就是Scheme)中注册,那么对象将会从JSON/YAML中直接解码(unmarshalling)
- 如果into为nil或者提供的数据对象的gvk与into的gvk不同,那么将会基于ObjectCreater.New(gvk)创建出一个新的对象 从上面的分析,可以看出:gvk的计算优先级为:originalData > default gvk > into。具体的代码如下所示:
|
|
CodecFactory
在这里,我们需要首先说明一下CodecFactory实现的接口StorageSerializer,StorageSerializer是一个接口,用来获取encoders,decoders和serailaizers,通过这些序列化器我们可以基于REST来读写数据。通常被client工具使用,这些客户端工具用于读取文件或者持久化restful对象的server存储接口。
而CodecFactory就是StorageSerializer的实现类。在BuildStorageFactory中,我们可以看到调用kubeapiserver.NewStorageFactory方法时传入了legacyscheme.Codecs到第三个参数(该参数类型为runtime.StorageSerializer)。
CodecFactory提供了方法来获取某个指定版本和内容类型的codecs和serializers,它的定义如下所示:
|
|
NewCodecFactory提供了方法来获取序列化对象,这些序列化对象为支持的传输格式进行编解码,也能为首选的内部和外部版本的转换封装。 在将来,内部版本会越来越少使用,调用者可能会使用缺省序列化器来取而代之,然后只需要转回内部共享的对象,如:Status,常用的API对象。
|
|
这里分为两个步骤:
- 第一个步骤:生成所有支持的序列化器 这里会采用一些缺省支持的协议,一般包括:json、yaml、protobuf。从newSerializersForScheme的代码可以看出。
|
|
那么这里没有看到protobuf的身影,但是上面的代码中,用到了serializerExtensions,它是一个全局变量,我们在k8s.io/apimachinenry/pkg/runtime/serializer/protobuf_extension.go文件中,找到了它的身影:
|
|
另外一个MetaFactory的作用是什么? MetaFactory用于从字符串中解析出version和kind数据。一般是json数据格式的,它有个缺省实现为DefaultMetaFactory,它的实现如下: 输入的二进制数据:{“apiVersion”:“apiextensions.k8s.io/v1beta1”, “kind”: “object”} 那么解析出来的得到:schema.GroupVersionKind{Group:“apiextensions.k8s.io”, Version:“v1beta1”, Kind: “object”}
- 第二个步骤:生成Codec工厂实例
newCodeFactory是一个帮助类,用于生成CodecFactory实例,它的主要功能,基于输入的序列化对象集合生成以下几项:
1 统一的decode 2 传统serializer(一般是Json) 3 可接受的编码类型
然后基于这几项数据,生成CodecFactory实例。
|
|
我们先看一下StorageSerializer的定义:
|
|
Encoder是一个接口,它负责把对象序列化写入到io中,但是它不关心具体的对象的GVK,同样Decoder也是如此,从字节码解码出对象出来。
而K8S在考虑了对多种版本的支撑能力,而它需要在存储时保证写入指定的版本,但是在接口层面程序层面又能够支撑多种版本,所以Codec就应运而生,Codec的定义如下所示:
Codec is a Serializer that deals with the details of versioning objects. It offers the same // interface as Serializer, so this is a marker to consumers that care about the version of the objects // they receive. type Codec Serializer
Codec也是由两部分组成:编码器与解码器,但是它要考虑版本功能,所以StorageSerializer提供了两个方法EncoderForVersion和DecoderToVersion来生成支持按指定版本进行序列化与反序列化的编解码器。我们来看看CodecFactory的这两个函数的具体实现。
|
|
从上代码来看,EncoderForVersion与DecodeToVersion都最终调用了同样的方法,如下所示:
|
|
-
生成对象版本转换实例
-
这里我们首先分析一下UnsafeObjectConverter的调用,它负责把scheme变成一个ObjectConvertor接口,ObjectConvertor负责把一个对象转换为另外一个不同的版本,对应的代码如下所示:
|
|
下面分析一下unsafeObjectObject的具体方法的实现,我们在最终变换版本的时候,是如何实现的:
|
|
再次回到Scheme结构负责版本转换的方法,这里调用的UnsafeConvertToVersion方法,Scheme还有一个ConvertToVersion的方法,两者都是调用底层的convertToVersion方法, 只是第一个参数不同。 安全转换在两种情况下会报错:目标版本不包括inKind类型的资源数据,转换不能得到有效的对象。 不安全转换输出的对象不与输入对象共享字段,会尝试以最高效率执行转换操作。
|
|
|
|
TODO:具体的converter如何实现不同版本对象的转换,我们分出新的章节来讨论。
- 回到NewDefaultingCodecForScheme函数中对NewCodec函数的调用
前面已经分析了runtime.UnsafeObjectConvertor会生成一个ObjectConvetor,它会负责把对象转换成我们的目标GVK。现在回到NewCodec方法的内容。这里的代码也就不贴出来了,其实他就是生成了一个codec实例。下面是codec结构的定义,它实现了Codec(Serializer)接口:
|
|
观察一下它的Encode方法,如下所示,它会在有必要的情况调用convertor把源对象转换成目标GVK的对象后,在调用encoder进行序列化。
|
|
-
No backlinks found.