编解码器,包含编码器与解码器,指的是可以表示数据的任何格式,或者将数据转换为特定格式的过程。在 kubernetes 中应用则是将 Etcd 集群中的数据进行编解码操作。

1、接口定义

Codec 包含 Encode Decoder 两个方法,前者用于将对象编码,后者将对象解码,内部的具体编解码器都需要实现这两个方法。

1.1 Encoder

type Encoder interface {
    Encode(obj Object, w io.Writer) error
    Identifier() Identifier
}

1.2 Decoder

type Decoder interface {
    Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
}

2、序列化器

编解码器包含 3 种序列化器,在进行编解码操作时,每一种序列化器都对资源对象的 metav1.TypeMeta(即APIVersion和Kind字段)进行验证,如果资源对象未提供这些字段,就会返回错误。

2.1 JsonSerializer

JSON 格式序列化、反序列化器,使用 application/json 的 ContentType 作为标识。实际使用则通过 Go 语言标准库 encoding/json 来实现序列化和反序列化操作。

type Serializer struct {
    meta    MetaFactory
    options SerializerOptions
    creater runtime.ObjectCreater
    typer   runtime.ObjectTyper
    identifier runtime.Identifier
}

初始化流程:

jsonSerializer := json.NewSerializerWithOptions(
    mf, scheme, scheme,
    json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict},
)
jsonSerializerType := serializerType{
    AcceptContentTypes: []string{runtime.ContentTypeJSON},
    ContentType:        runtime.ContentTypeJSON,
    FileExtensions:     []string{"json"},
    EncodesAsText:      true,
    Serializer:         jsonSerializer,
​
    Framer:           json.Framer,
    StreamSerializer: jsonSerializer,
}

在序列化过程中直接将对象编码为 JSON 格式,这里有一个小优化,如果开启 pretty 参数,则会使用 caseSensitiveJsonIterator.MarshalIndent 函数(封装了 github.com/json-iterator/go 第三方库,区分大小写、性能更优、兼容性好)优化格式。

反序列化操作通过 s.meta.Interpret 函数从 JSON 格式数据中提取出资源对象的 APIVersion 和 Kind 字段,最后通过 caseSensitiveJsonIterator.Unmarshal 函数将 JSON 数据反序列化并返回。

2.2 YamlSerializer

YAML 格式序列化、序列化器,使用 application/yaml 的 ContentType 作为标识。 实际使用则通过第三方库 gopkg.in/yaml.v2 来实现序列化和反序列化操作。

type yamlSerializer struct {
    runtime.Serializer
}

初始化流程:

yamlSerializer := json.NewSerializerWithOptions(
    mf, scheme, scheme,
    json.SerializerOptions{Yaml: true, Pretty: false, Strict: options.Strict},
)
serializers := []serializerType{
    {
        AcceptContentTypes: []string{runtime.ContentTypeYAML},
        ContentType:        runtime.ContentTypeYAML,
        FileExtensions:     []string{"yaml"},
        EncodesAsText:      true,
        Serializer:         yamlSerializer,
        StrictSerializer:   strictYAMLSerializer,
    },
}

序列化过程中先将对象转换为 JSON 格式,在通过 yaml.JSONToYAML 将 JSON 格式转换为 YAML 格式并返回数据。反序列化过程先将格式转换为 JSON,之后流程同 JSON 解码过程一致。

2.3 protobufSerializer

Protobuf 格式序列化、反序列化器,使用 application/vnd.kubernetes.protobuf 的 ContentType 作为标识。

type Serializer struct {
    prefix  []byte
    creater runtime.ObjectCreater
    typer   runtime.ObjectTyper
}

初始化流程:

protoSerializer := protobuf.NewSerializer(scheme, scheme)
protoRawSerializer := protobuf.NewRawSerializer(scheme, scheme)
serializers := []serializerType{
    {
        AcceptContentTypes: []string{runtime.ContentTypeProtobuf},
        ContentType:        runtime.ContentTypeProtobuf,
        FileExtensions:     []string{"pb"},
        Serializer:         protoSerializer,
        StrictSerializer: protoSerializer,
​
        Framer:           protobuf.LengthDelimitedFramer,
        StreamSerializer: protoRawSerializer,
    },
}

3、版本转换器

资源版本转换器主要用于解决多资源版本转换问题,其原理通过内部版本作为桥梁,转换时先将指定版本转换为内部版本,再将内部版本转换为目标版本,如此一来只要资源支持内部版本,则可以在多个版本之间进行转换。

3.1 转换器

type Converter struct {
    conversionFuncs          ConversionFuncs // 默认转换函数,定义在资源目录下的 conversion.go 代码文件中
    generatedConversionFuncs ConversionFuncs // 自动生成的转换函数,定义在资源目录下的 zz_generated.conversion.go 代码文件中
​
    ignoredUntypedConversions map[typePair]struct{} // 若资源对象注册到此字段,则忽略此资源对象的转换操作
}

不管是默认的转换函数,还是自动生成的转换函数,都是同一类型,其中 key 是一个结构体,里面包含了需要转换原版本和目标版本关系,如下所示:

type typePair struct {
    source reflect.Type // 需要转换的源版本
    dest   reflect.Type // 需要转换的目标版本
}

3.2 注册转换函数

Converter 转换函数需要通过注册才能在 Kubernetes 内部使用:

  • scheme.AddIgnoredConversionType:注册忽略的资源类型,不会执行转换操作,忽略资源对象的转换操作;

  • scheme.AddConversionFunc:注册单个 Conversion Func 转换函数;

  • scheme.AddGeneratedConversionFunc:注册自动生成的转换函数;

3.3 转换流程