Kubernetes 源碼分析 -- API Server之編解碼

在Kubernetes源碼分析-- API Server之API Install篇中,我們了解到K8S可以支持多版本的API,但是Rest API的不同版本中接口的輸入輸出參數(shù)的格式是有差別的,Kubernetes是怎么處理這個問題的呢?另外Kubernetes支持yaml、json兩個格式的配置,同時又能夠支持json、yaml和pb等格式的編解碼進(jìn)行協(xié)議傳輸,那么Kubernetes又是如何實(shí)現(xiàn)各種數(shù)據(jù)對象的序列化、反序列化的呢?

runtime.Scheme

為了同時解決數(shù)據(jù)對象的序列化、反序列化與多版本數(shù)據(jù)對象的兼容和轉(zhuǎn)換問題,Kubernetes設(shè)計(jì)了一套復(fù)雜的機(jī)制,首先,它設(shè)計(jì)了runtime.Scheme這個結(jié)構(gòu)體(k8s.io/apimachinery/pkg/runtime/schema.go),以下是對它的定義。

type Scheme struct {
    // versionMap allows one to figure out the go type of an object with
    // the given version and name.
    gvkToType map[schema.GroupVersionKind]reflect.Type

    // typeToGroupVersion allows one to find metadata for a given go object.
    // The reflect.Type we index by should *not* be a pointer.
    typeToGVK map[reflect.Type][]schema.GroupVersionKind

    // unversionedTypes are transformed without conversion in ConvertToVersion.
    unversionedTypes map[reflect.Type]schema.GroupVersionKind

    // unversionedKinds are the names of kinds that can be created in the context of any group
    // or version
    // TODO: resolve the status of unversioned types.
    unversionedKinds map[string]reflect.Type

    // Map from version and resource to the corresponding func to convert
    // resource field labels in that version to internal version.
    fieldLabelConversionFuncs map[string]map[string]FieldLabelConversionFunc

    // defaulterFuncs is an array of interfaces to be called with an object to provide defaulting
    // the provided object must be a pointer.
    defaulterFuncs map[reflect.Type]func(interface{})

    // converter stores all registered conversion functions. It also has
    // default coverting behavior.
    converter *conversion.Converter
}

runtime.Scheme是帶版本的API與帶版本的配置文件的基礎(chǔ)。
runtime.Scheme定義了以下內(nèi)容:

  • 序列化和反序列化API對象的方法
  • 類型登記處:用于在不同版本的Go shemas之間轉(zhuǎn)換group 、version和kind信息

從上述代碼中可以看到,typeToGVK與gkvToType屬性就是為了解決數(shù)據(jù)對象的序列化與反序列化問題,converter屬性則負(fù)責(zé)不同版本的數(shù)據(jù)對象轉(zhuǎn)換問題;unversionedKind用于映射哪些能夠在任意group和version情況下的類型,key是一個string,也就是kind;fieldLabelConversionFuncs:用于解決數(shù)據(jù)對象的屬性名稱的兼容性轉(zhuǎn)換和檢驗(yàn),比如講需要兼容Pod的spec.Host屬性改為spec.nodeName的情況。

Kubernetes這個設(shè)計(jì)思路簡單方便地建解決多版本的序列化和數(shù)據(jù)轉(zhuǎn)換問題,下面是runtime.Scheme里序列化、反序列化的核心方法New()的代碼:通過查找gkvToType里匹配的諸惡類型,以反射方法生成一個空的數(shù)據(jù)對象:

// New returns a new API object of the given version and name, or an error if it hasn't
// been registered. The version and kind fields must be specified.
func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) {
    if t, exists := s.gvkToType[kind]; exists {
        return reflect.New(t).Interface().(Object), nil
    }

    if t, exists := s.unversionedKinds[kind.Kind]; exists {
        return reflect.New(t).Interface().(Object), nil
    }
    return nil, NewNotRegisteredErrForKind(kind)
}

由于這個方法的實(shí)現(xiàn),也讓Scheme實(shí)現(xiàn)了ObjectCreater接口。

注意到runtime.Scheme只是實(shí)現(xiàn)了一個序列化與類型轉(zhuǎn)換的框架API,提供了注冊資源數(shù)據(jù)類型與轉(zhuǎn)換函數(shù)的功能,那么具體的資源數(shù)據(jù)對象、轉(zhuǎn)換函數(shù)又是在哪個包里實(shí)現(xiàn)的呢?答案是k8s.io/api/{resource}/{version}/register.go,而核心資源在k8s.io/api/core/v1/register.go中完成注冊,以核心資源為例說明一下目錄下的關(guān)鍵的代碼

  • types.go:k8s.io/api/core/v1/types.go
    定義了Rest API接口中設(shè)計(jì)的所有核心的數(shù)據(jù)類型
  • register.go 負(fù)責(zé)將types.go里定義的數(shù)據(jù)類型注冊到runtime.Scheme里。
    在register.go中,我們看到這里可以看到兩個定義:
    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    AddToScheme = SchemeBuilder.AddToScheme
    但是只提供了注冊的方法,其實(shí)還沒有執(zhí)行注冊的過程。什么時候執(zhí)行這個過程呢?

在k8s.io/client-go/kubernetes/scheme/register.go中,有一個 init方法,在引用的時候就會被調(diào)用。

func init() {
    v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
    AddToScheme(Scheme)
}

該方法中的AddToScheme方法,會調(diào)用各個資源、版本的AddToScheme方法,從而把所有的核心數(shù)據(jù)資源都注冊進(jìn)了runtime.Scheme實(shí)例中了。

  • generated.proto
    基于genclient自動創(chuàng)建的clientsets Go Client,在types.go中,我們可以看到有些對象加了一行注釋:“+genclient=true”,那么這些對象,就會被自動genclient工具自動創(chuàng)建出相應(yīng)的client。
  • zz_generated.deepcopy.go
    自動創(chuàng)建的資源數(shù)據(jù)對象的DeepCopy方法,實(shí)現(xiàn)資源對象的深度拷貝能力。

Serializer

Serializer是用于對象于序列化格式之間進(jìn)行轉(zhuǎn)的核心接口,它的定義如下所示:

// Encoders write objects to a serialized form
type Encoder interface {
    // Encode writes an object to a stream. Implementations may return errors if the versions are
    // incompatible, or if no conversion is defined.
    Encode(obj Object, w io.Writer) error
}

// Decoders attempt to load an object from data.
type Decoder interface {
    // Decode attempts to deserialize the provided data using either the innate typing of the scheme or the
    // default kind, group, and version provided. It returns a decoded object as well as the kind, group, and
    // version from the serialized data, or an error. If into is non-nil, it will be used as the target type
    // and implementations may choose to use it rather than reallocating an object. However, the object is not
    // guaranteed to be populated. The returned object is not guaranteed to match into. If defaults are
    // provided, they are applied to the data by default. If no defaults or partial defaults are provided, the
    // type of the into may be used to guide conversion decisions.
    Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
}

// Serializer is the core interface for transforming objects into a serialized format and back.
// Implementations may choose to perform conversion of the object, but no assumptions should be made.
type Serializer interface {
    Encoder
    Decoder
}

目前系統(tǒng)支持的序列化格式有三種:json、yaml和protobuf??梢詤⒖枷乱还?jié)CodecFactory,這里描述了三種序列化格式的初始化。

  • json與yaml序列化

json與yaml序列化的代碼在k8s.io/apimachinary/pkg/runtime/serializer/json/json.go,他們共享一個結(jié)構(gòu),如下所示:

type Serializer struct {
    meta    MetaFactory
    creater runtime.ObjectCreater       // 用于創(chuàng)建對應(yīng)Group、版本、kind的資源數(shù)據(jù)對象實(shí)例,一般來說這里會傳入Scheme對象
    typer   runtime.ObjectTyper         // 用于從資源數(shù)據(jù)對象獲取對應(yīng)的Group、Version和Kind,一般來說這里會傳入Scheme對象
    yaml    bool                        // 是否yaml
    pretty  bool                        // 是否pretty格式,只有josn支持
}

通過成員 yaml來區(qū)分是json還是yaml序列化器;creater和typer成員傳入的都是Scheme對象,前面我們分析過Scheme,它可以讓我們根據(jù)GVK來創(chuàng)建對應(yīng)的實(shí)例,并可以判斷一個對象的GVK,這將在我們后續(xù)的編解碼過程中起到重要作用;而meta成員是一個MetaFactory的對象,它主要用于從序列化數(shù)據(jù)(這里就是json或者yaml)中獲得對象的GroupVersionKind。(下一節(jié)也有說明)

下面我們看看Encode方法,Encode方法比較簡單,只需要按需把對象,轉(zhuǎn)換為json或yaml格式即可。代碼如下所示:

// Encode serializes the provided object to the given writer.
func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
    // 看起來針對yaml和pretty需要用到gitlib下面的jsoniter庫來完成
    if s.yaml {
        json, err := jsoniter.ConfigCompatibleWithStandardLibrary.Marshal(obj)
        if err != nil {
            return err
        }
        data, err := yaml.JSONToYAML(json)
        if err != nil {
            return err
        }
        _, err = w.Write(data)
        return err
    }

    if s.pretty {
        data, err := jsoniter.ConfigCompatibleWithStandardLibrary.MarshalIndent(obj, "", "  ")
        if err != nil {
            return err
        }
        _, err = w.Write(data)
        return err
    }
    // 而針對普通的json序列器,直接通過json.Encoder編碼即可
    encoder := json.NewEncoder(w)
    return encoder.Encode(obj)
}

Decode比較復(fù)雜,它嘗試把提供的數(shù)據(jù)轉(zhuǎn)換成YAML或者JSON,從中提取出其中存儲的schema、kind,并合并缺省的gvk,然后在數(shù)據(jù)加載到對象中,注意該對象滿足前面指定的schema、kind。或者也可以指定into字段(這時候會加載到into對象中)。

  • 如果into為*runtime.Unknown,則不會執(zhí)行解碼操鄒,而直接把裸數(shù)據(jù)提取出來。
  • 如果into沒有在typer(也就是Scheme)中注冊,那么對象將會從JSON/YAML中直接解碼(unmarshalling)
  • 如果into為nil或者提供的數(shù)據(jù)對象的gvk與into的gvk不同,那么將會基于ObjectCreater.New(gvk)創(chuàng)建出一個新的對象
    從上面的分析,可以看出:gvk的計(jì)算優(yōu)先級為:originalData > default gvk > into。具體的代碼如下所示:
func (s *Serializer) Decode(originalData []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
    if versioned, ok := into.(*runtime.VersionedObjects); ok {
        into = versioned.Last()
        obj, actual, err := s.Decode(originalData, gvk, into)
        if err != nil {
            return nil, actual, err
        }
        versioned.Objects = []runtime.Object{obj}
        return versioned, actual, nil
    }

    data := originalData
    if s.yaml {
        altered, err := yaml.YAMLToJSON(data)
        if err != nil {
            return nil, nil, err
        }
        data = altered
    }

    actual, err := s.meta.Interpret(data)
    if err != nil {
        return nil, nil, err
    }

    if gvk != nil {
        *actual = gvkWithDefaults(*actual, *gvk)
    }

    if unk, ok := into.(*runtime.Unknown); ok && unk != nil {
        unk.Raw = originalData
        unk.ContentType = runtime.ContentTypeJSON
        unk.GetObjectKind().SetGroupVersionKind(*actual)
        return unk, actual, nil
    }

    if into != nil {
        _, isUnstructured := into.(runtime.Unstructured)
        types, _, err := s.typer.ObjectKinds(into)
        switch {
        case runtime.IsNotRegisteredError(err), isUnstructured:
            if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, into); err != nil {
                return nil, actual, err
            }
            return into, actual, nil
        case err != nil:
            return nil, actual, err
        default:
            *actual = gvkWithDefaults(*actual, types[0])
        }
    }

    if len(actual.Kind) == 0 {
        return nil, actual, runtime.NewMissingKindErr(string(originalData))
    }
    if len(actual.Version) == 0 {
        return nil, actual, runtime.NewMissingVersionErr(string(originalData))
    }

    // use the target if necessary
    obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
    if err != nil {
        return nil, actual, err
    }

    if err := jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal(data, obj); err != nil {
        return nil, actual, err
    }
    return obj, actual, nil
}

CodecFactory

在這里,我們需要首先說明一下CodecFactory實(shí)現(xiàn)的接口StorageSerializer,StorageSerializer是一個接口,用來獲取encoders,decoders和serailaizers,通過這些序列化器我們可以基于REST來讀寫數(shù)據(jù)。通常被client工具使用,這些客戶端工具用于讀取文件或者持久化restful對象的server存儲接口。

而CodecFactory就是StorageSerializer的實(shí)現(xiàn)類。在BuildStorageFactory中,我們可以看到調(diào)用kubeapiserver.NewStorageFactory方法時傳入了legacyscheme.Codecs到第三個參數(shù)(該參數(shù)類型為runtime.StorageSerializer)。

CodecFactory提供了方法來獲取某個指定版本和內(nèi)容類型的codecs和serializers,它的定義如下所示:

type CodecFactory struct {
    scheme      *runtime.Scheme
    serializers []serializerType
    universal   runtime.Decoder
    accepts     []runtime.SerializerInfo

    legacySerializer runtime.Serializer
}

NewCodecFactory提供了方法來獲取序列化對象,這些序列化對象為支持的傳輸格式進(jìn)行編解碼,也能為首選的內(nèi)部和外部版本的轉(zhuǎn)換封裝。
在將來,內(nèi)部版本會越來越少使用,調(diào)用者可能會使用缺省序列化器來取而代之,然后只需要轉(zhuǎn)回內(nèi)部共享的對象,如:Status,常用的API對象。

func NewCodecFactory(scheme *runtime.Scheme) CodecFactory {
    serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory)
    return newCodecFactory(scheme, serializers)
}

這里分為兩個步驟:

  • 第一個步驟:生成所有支持的序列化器
    這里會采用一些缺省支持的協(xié)議,一般包括:json、yaml、protobuf。從newSerializersForScheme的代碼可以看出。
func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory) []serializerType {
    jsonSerializer := json.NewSerializer(mf, scheme, scheme, false)
    jsonPrettySerializer := json.NewSerializer(mf, scheme, scheme, true)
    yamlSerializer := json.NewYAMLSerializer(mf, scheme, scheme)

    serializers := []serializerType{
        {
            AcceptContentTypes: []string{"application/json"},
            ContentType:        "application/json",
            FileExtensions:     []string{"json"},
            EncodesAsText:      true,
            Serializer:         jsonSerializer,
            PrettySerializer:   jsonPrettySerializer,

            Framer:           json.Framer,
            StreamSerializer: jsonSerializer,
        },
        {
            AcceptContentTypes: []string{"application/yaml"},
            ContentType:        "application/yaml",
            FileExtensions:     []string{"yaml"},
            EncodesAsText:      true,
            Serializer:         yamlSerializer,
        },
    }

    for _, fn := range serializerExtensions {
        if serializer, ok := fn(scheme); ok {
            serializers = append(serializers, serializer)
        }
    }
    return serializers
}

那么這里沒有看到protobuf的身影,但是上面的代碼中,用到了serializerExtensions,它是一個全局變量,我們在k8s.io/apimachinenry/pkg/runtime/serializer/protobuf_extension.go文件中,找到了它的身影:

func init() {
    serializerExtensions = append(serializerExtensions, protobufSerializer)
}

另外一個MetaFactory的作用是什么?
MetaFactory用于從字符串中解析出version和kind數(shù)據(jù)。一般是json數(shù)據(jù)格式的,它有個缺省實(shí)現(xiàn)為DefaultMetaFactory,它的實(shí)現(xiàn)如下:
輸入的二進(jìn)制數(shù)據(jù):{"apiVersion":"apiextensions.k8s.io/v1beta1", "kind": "object"}
那么解析出來的得到:schema.GroupVersionKind{Group:"apiextensions.k8s.io", Version:"v1beta1", Kind: "object"}

  • 第二個步驟:生成Codec工廠實(shí)例

newCodeFactory是一個幫助類,用于生成CodecFactory實(shí)例,它的主要功能,基于輸入的序列化對象集合生成以下幾項(xiàng):

1 統(tǒng)一的decode
2 傳統(tǒng)serializer(一般是Json)
3 可接受的編碼類型

然后基于這幾項(xiàng)數(shù)據(jù),生成CodecFactory實(shí)例。

func newCodecFactory(scheme *runtime.Scheme, serializers []serializerType) CodecFactory {
    decoders := make([]runtime.Decoder, 0, len(serializers))
    var accepts []runtime.SerializerInfo
    alreadyAccepted := make(map[string]struct{})

    var legacySerializer runtime.Serializer
    for _, d := range serializers {
        decoders = append(decoders, d.Serializer)
        for _, mediaType := range d.AcceptContentTypes {
            if _, ok := alreadyAccepted[mediaType]; ok {
                continue
            }
            alreadyAccepted[mediaType] = struct{}{}
            info := runtime.SerializerInfo{
                MediaType:        d.ContentType,
                EncodesAsText:    d.EncodesAsText,
                Serializer:       d.Serializer,
                PrettySerializer: d.PrettySerializer,
            }
            if d.StreamSerializer != nil {
                info.StreamSerializer = &runtime.StreamSerializerInfo{
                    Serializer:    d.StreamSerializer,
                    EncodesAsText: d.EncodesAsText,
                    Framer:        d.Framer,
                }
            }
            accepts = append(accepts, info)
            if mediaType == runtime.ContentTypeJSON {
                legacySerializer = d.Serializer
            }
        }
    }
    if legacySerializer == nil {
        legacySerializer = serializers[0].Serializer
    }

    return CodecFactory{
        scheme:      scheme,
        serializers: serializers,
        universal:   recognizer.NewDecoder(decoders...),

        accepts: accepts,

        legacySerializer: legacySerializer,
    }
}

我們先看一下StorageSerializer的定義:

type StorageSerializer interface {
    // SupportedMediaTypes are the media types supported for reading and writing objects.
    SupportedMediaTypes() []SerializerInfo

    // UniversalDeserializer returns a Serializer that can read objects in multiple supported formats
    // by introspecting the data at rest.
    UniversalDeserializer() Decoder

    // 返回一個encoder,該encoder能夠保證寫入底層序列化器的對象是指定的GV
    // EncoderForVersion returns an encoder that ensures objects being written to the provided
    // serializer are in the provided group version.
    EncoderForVersion(serializer Encoder, gv GroupVersioner) Encoder

    // 返回一個decoder,該decoder能夠保證被底層序列化器的反序列化的對象是指定的GV
    // DecoderForVersion returns a decoder that ensures objects being read by the provided
    // serializer are in the provided group version by default.
    DecoderToVersion(serializer Decoder, gv GroupVersioner) Decoder
}

Encoder是一個接口,它負(fù)責(zé)把對象序列化寫入到io中,但是它不關(guān)心具體的對象的GVK,同樣Decoder也是如此,從字節(jié)碼解碼出對象出來。

而K8S在考慮了對多種版本的支撐能力,而它需要在存儲時保證寫入指定的版本,但是在接口層面程序?qū)用嬗帜軌蛑味喾N版本,所以Codec就應(yīng)運(yùn)而生,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來生成支持按指定版本進(jìn)行序列化與反序列化的編解碼器。我們來看看CodecFactory的這兩個函數(shù)的具體實(shí)現(xiàn)。

// CodecForVersions creates a codec with the provided serializer. If an object is decoded and its group is not in the list,
// it will default to runtime.APIVersionInternal. If encode is not specified for an object's group, the object is not
// converted. If encode or decode are nil, no conversion is performed.
func (f CodecFactory) CodecForVersions(encoder runtime.Encoder, decoder runtime.Decoder, encode runtime.GroupVersioner, decode runtime.GroupVersioner) runtime.Codec {
    // TODO: these are for backcompat, remove them in the future
    if encode == nil {
        encode = runtime.DisabledGroupVersioner
    }
    if decode == nil {
        decode = runtime.InternalGroupVersioner
    }
    return versioning.NewDefaultingCodecForScheme(f.scheme, encoder, decoder, encode, decode)
}

// DecoderToVersion returns a decoder that targets the provided group version.
func (f CodecFactory) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
    return f.CodecForVersions(nil, decoder, nil, gv)
}

// EncoderForVersion returns an encoder that targets the provided group version.
func (f CodecFactory) EncoderForVersion(encoder runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder {
    return f.CodecForVersions(encoder, nil, gv, nil)
}

從上代碼來看,EncoderForVersion與DecodeToVersion都最終調(diào)用了同樣的方法,如下所示:

func NewDefaultingCodecForScheme(
    // TODO: I should be a scheme interface?
    scheme *runtime.Scheme,
    encoder runtime.Encoder,
    decoder runtime.Decoder,
    encodeVersion runtime.GroupVersioner,
    decodeVersion runtime.GroupVersioner,
) runtime.Codec {
    return NewCodec(encoder, decoder, runtime.UnsafeObjectConvertor(scheme), scheme, scheme, scheme, encodeVersion, decodeVersion)
}
  • 生成對象版本轉(zhuǎn)換實(shí)例
    這里我們首先分析一下UnsafeObjectConverter的調(diào)用,它負(fù)責(zé)把scheme變成一個ObjectConvertor接口,ObjectConvertor負(fù)責(zé)把一個對象轉(zhuǎn)換為另外一個不同的版本,對應(yīng)的代碼如下所示:
func UnsafeObjectConvertor(scheme *Scheme) ObjectConvertor {
    return unsafeObjectConvertor{scheme}
}
// unsafeObjectConvertor的定義
type unsafeObjectConvertor struct {
    *Scheme
}

type ObjectConvertor interface {
    // Convert attempts to convert one object into another, or returns an error. This method does
    // not guarantee the in object is not mutated. The context argument will be passed to
    // all nested conversions.
    Convert(in, out, context interface{}) error
    // ConvertToVersion takes the provided object and converts it the provided version. This
    // method does not guarantee that the in object is not mutated. This method is similar to
    // Convert() but handles specific details of choosing the correct output version.
    ConvertToVersion(in Object, gv GroupVersioner) (out Object, err error)
    ConvertFieldLabel(version, kind, label, value string) (string, string, error)
}

下面分析一下unsafeObjectObject的具體方法的實(shí)現(xiàn),我們在最終變換版本的時候,是如何實(shí)現(xiàn)的:

// ConvertToVersion converts in to the provided outVersion without copying the input first, which
// is only safe if the output object is not mutated or reused.
func (c unsafeObjectConvertor) ConvertToVersion(in Object, outVersion GroupVersioner) (Object, error) {
    return c.Scheme.UnsafeConvertToVersion(in, outVersion)
}

再次回到Scheme結(jié)構(gòu)負(fù)責(zé)版本轉(zhuǎn)換的方法,這里調(diào)用的UnsafeConvertToVersion方法,Scheme還有一個ConvertToVersion的方法,兩者都是調(diào)用底層的convertToVersion方法, 只是第一個參數(shù)不同。
安全轉(zhuǎn)換在兩種情況下會報錯:目標(biāo)版本不包括inKind類型的資源數(shù)據(jù),轉(zhuǎn)換不能得到有效的對象。
不安全轉(zhuǎn)換輸出的對象不與輸入對象共享字段,會嘗試以最高效率執(zhí)行轉(zhuǎn)換操作。

// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
// but does not guarantee the output object does not share fields with the input object. It attempts to be as
// efficient as possible when doing conversion.
func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) {
    return s.convertToVersion(false, in, target)
}
// convertToVersion handles conversion with an optional copy.
func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
    var t reflect.Type

    if u, ok := in.(Unstructured); ok {
        typed, err := s.unstructuredToTyped(u)
        if err != nil {
            return nil, err
        }

        in = typed
        // unstructuredToTyped returns an Object, which must be a pointer to a struct.
        t = reflect.TypeOf(in).Elem()   // 從指針轉(zhuǎn)換為一個結(jié)構(gòu)對象

    } else {
        // determine the incoming kinds with as few allocations as possible.
        t = reflect.TypeOf(in)  // 只有指針對象才能被轉(zhuǎn)換
        if t.Kind() != reflect.Ptr {
            return nil, fmt.Errorf("only pointer types may be converted: %v", t)
        }
        t = t.Elem()        // 得到指針的結(jié)構(gòu)對象
        if t.Kind() != reflect.Struct {
            return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
        }
    }

    kinds, ok := s.typeToGVK[t]     // 得到存儲在scheme中的GVK數(shù)據(jù),可能有多個
    if !ok || len(kinds) == 0 {
        return nil, NewNotRegisteredErrForType(t)
    }

    gvk, ok := target.KindForGroupVersionKinds(kinds)   // 基于GroupVersioner從源GVKs得到統(tǒng)一的最終目標(biāo)GVK,
                                                        // 譬如存儲到ETCD來說,同時希望按照統(tǒng)一的版本來存儲。
    if !ok {
        // try to see if this type is listed as unversioned (for legacy support)
        // TODO: when we move to server API versions, we should completely remove the unversioned concept
        if unversionedKind, ok := s.unversionedTypes[t]; ok {
            if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
                return copyAndSetTargetKind(copy, in, gvk)
            }
            return copyAndSetTargetKind(copy, in, unversionedKind)
        }
        return nil, NewNotRegisteredErrForTarget(t, target)
    }

    // target wants to use the existing type, set kind and return (no conversion necessary)
    for _, kind := range kinds {    // 如果gvk 是kinds中的一種,那說明版本一致,資源對象結(jié)構(gòu)一致,我們只需要把對象內(nèi)容拷貝出來,并設(shè)置好目標(biāo)GVK即可。
        if gvk == kind {
            return copyAndSetTargetKind(copy, in, gvk)
        }
    }

    // 如果類型是unversioned,那么沒有轉(zhuǎn)化你的必要
    // type is unversioned, no conversion necessary
    if unversionedKind, ok := s.unversionedTypes[t]; ok {
        if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
            return copyAndSetTargetKind(copy, in, gvk)
        }
        return copyAndSetTargetKind(copy, in, unversionedKind)
    }

    out, err := s.New(gvk)      // 從Scheme中,按照gvk創(chuàng)建出一個對應(yīng)的gvk的對象出來。
    if err != nil {
        return nil, err
    }

    // copy 標(biāo)識是需要安全轉(zhuǎn)換,安全轉(zhuǎn)換會執(zhí)行DeepCopy,這樣就不會存在目標(biāo)對象于源對象共享 Field的情況了。
    if copy {
        in = in.DeepCopyObject()
    }

    // 類型轉(zhuǎn)換,基于converter成員來完成轉(zhuǎn)換工作,這塊需要專門分析
    flags, meta := s.generateConvertMeta(in)
    meta.Context = target
    if err := s.converter.Convert(in, out, flags, meta); err != nil {
        return nil, err
    }
    // 設(shè)置目標(biāo)GVK
    setTargetKind(out, gvk)
    return out, nil
}

TODO:具體的converter如何實(shí)現(xiàn)不同版本對象的轉(zhuǎn)換,我們分出新的章節(jié)來討論。

  • 回到NewDefaultingCodecForScheme函數(shù)中對NewCodec函數(shù)的調(diào)用

前面已經(jīng)分析了runtime.UnsafeObjectConvertor會生成一個ObjectConvetor,它會負(fù)責(zé)把對象轉(zhuǎn)換成我們的目標(biāo)GVK?,F(xiàn)在回到NewCodec方法的內(nèi)容。這里的代碼也就不貼出來了,其實(shí)他就是生成了一個codec實(shí)例。下面是codec結(jié)構(gòu)的定義,它實(shí)現(xiàn)了Codec(Serializer)接口:

type codec struct {
    encoder   runtime.Encoder
    decoder   runtime.Decoder
    convertor runtime.ObjectConvertor
    creater   runtime.ObjectCreater
    typer     runtime.ObjectTyper
    defaulter runtime.ObjectDefaulter

    encodeVersion runtime.GroupVersioner
    decodeVersion runtime.GroupVersioner
}

觀察一下它的Encode方法,如下所示,它會在有必要的情況調(diào)用convertor把源對象轉(zhuǎn)換成目標(biāo)GVK的對象后,在調(diào)用encoder進(jìn)行序列化。

// Encode ensures the provided object is output in the appropriate group and version, invoking
// conversion if necessary. Unversioned objects (according to the ObjectTyper) are output as is.
func (c *codec) Encode(obj runtime.Object, w io.Writer) error {
    switch obj.(type) {
    case *runtime.Unknown, runtime.Unstructured:
        return c.encoder.Encode(obj, w)
    }

    gvks, isUnversioned, err := c.typer.ObjectKinds(obj)
    if err != nil {
        return err
    }

    if c.encodeVersion == nil || isUnversioned {
        if e, ok := obj.(runtime.NestedObjectEncoder); ok {
            if err := e.EncodeNestedObjects(DirectEncoder{Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
                return err
            }
        }
        objectKind := obj.GetObjectKind()
        old := objectKind.GroupVersionKind()
        objectKind.SetGroupVersionKind(gvks[0])
        err = c.encoder.Encode(obj, w)
        objectKind.SetGroupVersionKind(old)
        return err
    }

    // Perform a conversion if necessary
    objectKind := obj.GetObjectKind()
    old := objectKind.GroupVersionKind()
    out, err := c.convertor.ConvertToVersion(obj, c.encodeVersion)
    if err != nil {
        return err
    }

    if e, ok := out.(runtime.NestedObjectEncoder); ok {
        if err := e.EncodeNestedObjects(DirectEncoder{Version: c.encodeVersion, Encoder: c.encoder, ObjectTyper: c.typer}); err != nil {
            return err
        }
    }

    // Conversion is responsible for setting the proper group, version, and kind onto the outgoing object
    err = c.encoder.Encode(out, w)
    // restore the old GVK, in case conversion returned the same object
    objectKind.SetGroupVersionKind(old)
    return err
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容