Kubernetes 資源對象序列化實(shí)現(xiàn)
序列化和反序列化在很多項(xiàng)目中都有應(yīng)用,Kubernetes也不例外。Kubernetes中定義了大量的API對象,為此還單獨(dú)設(shè)計(jì)了一個(gè)包(https://github.com/kubernetes/api),方便多個(gè)模塊引用。API對象在不同的模塊之間傳輸(尤其是跨進(jìn)程)可能會用到序列化與反序列化,不同的場景對于序列化個(gè)格式又不同,比如grpc協(xié)議用protobuf,用戶交互用yaml(因?yàn)閥aml可讀性強(qiáng)),etcd存儲用json。Kubernetes反序列化API對象不同于我們常用的json.Unmarshal()函數(shù)(需要傳入對象指針),Kubernetes需要解析對象的類型(Group/Version/Kind),根據(jù)API對象的類型構(gòu)造API對象,然后再反序列化。因此,Kubernetes定義了Serializer接口,專門用于API對象的序列化和反序列化。本文引用源碼為kubernetes的release-1.21分支。
Serializer
因?yàn)镵ubernetes需要支持json、yaml、protobuf三種數(shù)據(jù)格式的序列化和反序列化,有必要抽象序列化和反序列化的統(tǒng)一接口,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/interfaces.go#L86
// Serializer是用于序列化和反序列化API對象的核心接口,
type Serializer interface {
// Serializer繼承了編碼器和解碼器,編碼器就是用來序列化API對象的,序列化的過程稱之為編碼;反之,反序列化的過程稱之為解碼。
// 關(guān)于編/解碼器的定義下面有注釋。
Encoder
Decoder
}
// 序列化的過程稱之為編碼,實(shí)現(xiàn)編碼的對象稱之為編碼器(Encoder)
type Encoder interface {
// Encode()將對象寫入流??梢詫ncode()看做為json(yaml).Marshal(),只是輸出變?yōu)閕o.Writer。
Encode(obj Object, w io.Writer) error
// Identifier()返回編碼器的標(biāo)識符,當(dāng)且僅當(dāng)兩個(gè)不同的編碼器編碼同一個(gè)對象的輸出是相同的,那么這兩個(gè)編碼器的標(biāo)識符也應(yīng)該是相同的。
// 也就是說,編碼器都有一個(gè)標(biāo)識符,兩個(gè)編碼器的標(biāo)識符可能是相同的,判斷標(biāo)準(zhǔn)是編碼任意API對象時(shí)輸出都是相同的。
// 標(biāo)識符有什么用?標(biāo)識符目標(biāo)是與CacheableObject.CacheEncode()方法一起使用,CacheableObject又是什么東東?后面有介紹。
Identifier() Identifier
}
// 標(biāo)識符就是字符串,可以簡單的理解為標(biāo)簽的字符串形式,后面會看到如何生成標(biāo)識符。
type Identifier string
// 反序列化的過程稱之為解碼,實(shí)現(xiàn)解碼的對象稱之為解碼器(Decoder)
type Decoder interface {
// Decode()嘗試使用Schema中注冊的類型或者提供的默認(rèn)的GVK反序列化API對象。
// 如果'into'非空將被用作目標(biāo)類型,接口實(shí)現(xiàn)可能會選擇使用它而不是重新構(gòu)造一個(gè)對象。
// 但是不能保證輸出到'into'指向的對象,因?yàn)榉祷氐膶ο蟛槐WC匹配'into'。
// 如果提供了默認(rèn)GVK,將應(yīng)用默認(rèn)GVK反序列化,如果未提供默認(rèn)GVK或僅提供部分,則使用'into'的類型補(bǔ)全。
Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
}
我們平時(shí)工作中最常用的序列化格式包括json、yaml以及protobuf,非常"巧合",Kubernetes也是用這幾種序列化格式。以json為例,編碼器和解碼器可以等同于json.Marshal()和json.Unmarshal(),定義成interface是對序列化與反序列化的統(tǒng)一抽象。為什么我們平時(shí)很少抽象(當(dāng)然有些讀者是有抽象的,我們不能一概而論),是因?yàn)槲覀児ぷ髦锌赡苤挥玫揭环N序列化格式,所以抽象顯得沒那么必要。而Kubernetes中,這三種都是需要的,yaml的可視化效果好,比如我們寫的各種yaml文件;而API對象存儲在etcd中是json格式,在用到grpc的地方則需要protobuf格式。
再者,Kubernetes對于Serializer的定義有更高的要求,即根據(jù)序列化的數(shù)據(jù)中的元數(shù)據(jù)自動識別API對象的類型(GVK),這在Decoder.Decode()接口定義中已經(jīng)有所了解。而我們平時(shí)使用json.Marshal()的時(shí)候傳入了指定類型的對象指針,相比于Kubernetes對于反序列化的要求,我們使用的相對更"靜態(tài)"。
綜上所述,抽象Serializer就有必要了,尤其是RecognizingDecoder可以解碼任意格式的API對象就可以充分體現(xiàn)這種抽象的價(jià)值。
json
json.Serializer實(shí)現(xiàn)了將API對象序列化成json數(shù)據(jù)和從json數(shù)據(jù)反序列化API對象,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L100
// Serializer實(shí)現(xiàn)了runtime.Serializer接口。
type Serializer struct {
// MetaFactory從json數(shù)據(jù)中提取GVK(Group/Version/Kind),下面有MetaFactory注釋。
// MetaFactory很有用,解碼時(shí)如果不提供默認(rèn)的GVK和API對象指針,就要靠MetaFactory提取GVK了。
// 當(dāng)然,即便提供了供默認(rèn)的GVK和API對象指針,提取的GVK的也是非常有用的,詳情參看Decode()接口的實(shí)現(xiàn)。
meta MetaFactory
// SerializerOptions是Serializer選項(xiàng),可以看做是配置,下面有注釋。
options SerializerOptions
// runtime.ObjectCreater根據(jù)GVK構(gòu)造API對象,在反序列化時(shí)會用到,其實(shí)它就是Schema。
// runtime.ObjectCreater的定義讀者可以自己查看源碼,如果對Schema熟悉的讀者這都不是事。
creater runtime.ObjectCreater
// runtime.ObjectTyper根據(jù)API對象返回可能的GVK,也是用在反序列化中,其實(shí)它也是Schema。
// 這個(gè)有什么用?runtime.Serializer.Decode()接口注釋說的很清楚,在json數(shù)據(jù)和默認(rèn)GVK無法提供的類型元數(shù)據(jù)需要用輸出類型補(bǔ)全。
typer runtime.ObjectTyper
// 標(biāo)識符,Serializer一旦被創(chuàng)建,標(biāo)識符就不會變了。
identifier runtime.Identifier
}
// SerializerOptions定義了Serializer的選項(xiàng).
type SerializerOptions struct {
// true: 序列化/反序列化yaml;false: 序列化/反序列化json
// 也就是說,json.Serializer既可以序列化/反序列json,也可以序列化/反序列yaml。
Yaml bool
// Pretty選項(xiàng)僅用于Encode接口,輸出易于閱讀的json數(shù)據(jù)。當(dāng)Yaml選項(xiàng)為true時(shí),Pretty選項(xiàng)被忽略,因?yàn)閥aml本身就易于閱讀。
// 什么是易于閱讀的?舉個(gè)例子就立刻明白了,定義測試類型為:
// type Test struct {
// A int
// B string
// }
// 則關(guān)閉和開啟Pretty選項(xiàng)的對比如下:
// {"A":1,"B":"2"}
// {
// "A": 1,
// "B": "2"
// }
// 很明顯,后者更易于閱讀。易于閱讀只有人看的時(shí)候才有需要,對于機(jī)器來說一點(diǎn)價(jià)值都沒有,所以這個(gè)選項(xiàng)使用范圍還是比較有限的。
Pretty bool
// Strict應(yīng)用于Decode接口,表示嚴(yán)謹(jǐn)?shù)?。那什么是?yán)謹(jǐn)?shù)??筆者很難用語言表達(dá),但是以下幾種情況是不嚴(yán)謹(jǐn)?shù)模?/span>
// 1. 存在重復(fù)字段,比如{"value":1,"value":1};
// 2. 不存在的字段,比如{"unknown": 1},而目標(biāo)API對象中不存在Unknown屬性;
// 3. 未打標(biāo)簽字段,比如{"Other":"test"},雖然目標(biāo)API對象中有Other字段,但是沒有打`json:"Other"`標(biāo)簽
// Strict選項(xiàng)可以理解為增加了很多校驗(yàn),請注意,啟用此選項(xiàng)的性能下降非常嚴(yán)重,因此不應(yīng)在性能敏感的場景中使用。
// 那什么場景需要用到Strict選項(xiàng)?比如Kubernetes各個(gè)服務(wù)的配置API,對性能要求不高,但需要嚴(yán)格的校驗(yàn)。
Strict bool
}
MetaFactory
MetaFactory類型名定義其實(shí)挺忽悠人的,工廠類都是用來構(gòu)造對象的,MetaFactory的功能雖然也是構(gòu)造類型元數(shù)據(jù)的,但是它更像是一個(gè)解析器,所以筆者認(rèn)為"MetaParser"更加貼切,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/meta.go#L28
type MetaFactory interface {
// 解析json數(shù)據(jù)中的元數(shù)據(jù)字段,返回GVK(Group/Version/Kind)。
// 如果MetaFactory就這么一個(gè)接口函數(shù),筆者認(rèn)為叫解釋器或者解析器更加合理。
Interpret(data []byte) (*schema.GroupVersionKind, error)
}
// SimpleMetaFactory是MetaFactory的一種實(shí)現(xiàn),用于檢索在json中由"apiVersion"和"kind"字段標(biāo)識的對象的類型和版本。
type SimpleMetaFactory struct {
}
// Interpret()實(shí)現(xiàn)了MetaFactory.Interpret()接口。
func (SimpleMetaFactory) Interpret(data []byte) (*schema.GroupVersionKind, error) {
// 定義一種只有apiVersion和kind兩個(gè)字段的匿名類型
findKind := struct {
// +optional
APIVersion string `json:"apiVersion,omitempty"`
// +optional
Kind string `json:"kind,omitempty"`
}{}
// 只解析json中apiVersion和kind字段,這個(gè)玩法有點(diǎn)意思,但是筆者認(rèn)為這個(gè)方法有點(diǎn)簡單粗暴。
// 讀者可以嘗試閱讀json.Unmarshal(),該函數(shù)會遍歷整個(gè)json,開銷不小,其實(shí)必要性不強(qiáng),因?yàn)橹恍枰猘piVersion和kind字段。
// 試想一下,如果每次反序列化一個(gè)API對象都要有一次Interpret()和Decode(),它的開銷相當(dāng)于做了兩次反序列化。
if err := json.Unmarshal(data, &findKind); err != nil {
return nil, fmt.Errorf("couldn't get version/kind; json parse error: %v", err)
}
// 將apiVersion解析為Group和Version
gv, err := schema.ParseGroupVersion(findKind.APIVersion)
if err != nil {
return nil, err
}
// 返回API對象的GVK
return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: findKind.Kind}, nil
}
Decode
// Decode實(shí)現(xiàn)了Decoder.Decode(),嘗試從數(shù)據(jù)中提取的API類型(GVK),應(yīng)用提供的默認(rèn)GVK,然后將數(shù)據(jù)加載到所需類型或提供的'into'匹配的對象中:
// 1. 如果into為*runtime.Unknown,則將提取原始數(shù)據(jù),并且不執(zhí)行解碼;
// 2. 如果into的類型沒有在Schema注冊,則使用json.Unmarshal()直接反序列化到'into'指向的對象中;
// 3. 如果'into'不為空且原始數(shù)據(jù)GVK不全,則'into'的類型(GVK)將用于補(bǔ)全GVK;
// 4. 如果'into'為空或數(shù)據(jù)中的GVK與'into'的GVK不同,它將使用ObjectCreater.New(gvk)生成一個(gè)新對象;
// 成功或大部分錯(cuò)誤都會返回GVK,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) {
data := originalData
// 如果配置選項(xiàng)為yaml,則將yaml格式轉(zhuǎn)為json格式,是不是有一種感覺:“臥了個(gè)槽”!所謂可支持json和yaml,就是先將yaml轉(zhuǎn)為json!
// 那我感覺我可以支持所有格式,都轉(zhuǎn)為json就完了唄!其實(shí)不然,yaml的使用場景都是需要和人交互的地方,所以對于效率要求不高(qps低)。
// 那么實(shí)現(xiàn)簡單易于維護(hù)更重要,所以這種實(shí)現(xiàn)并沒有什么毛病。
if s.options.Yaml {
// yaml轉(zhuǎn)json
altered, err := yaml.YAMLToJSON(data)
if err != nil {
return nil, nil, err
}
data = altered
}
// 此時(shí)data是json了,以后就不用再考慮yaml選項(xiàng)了
// 解析類型元數(shù)據(jù),原理已經(jīng)在MetaFactory解釋過了。
actual, err := s.meta.Interpret(data)
if err != nil {
return nil, nil, err
}
// 解析類型元數(shù)據(jù)大部分情況是正確的,除非不是json或者apiVersion格式不對。
// 但是GVK三元組可能有所缺失,比如只有Kind,Group/Version,其他字段就用默認(rèn)的GVK補(bǔ)全。
// 這也體現(xiàn)出了原始數(shù)據(jù)中的GVK的優(yōu)先級最高,其次是默認(rèn)的GVK。gvkWithDefaults()函數(shù)下面有注釋。
if gvk != nil {
*actual = gvkWithDefaults(*actual, *gvk)
}
// 如果'into'是*runtime.Unknown類型,需要返回*runtime.Unknown類型的對象。
// 需要注意的是,此處復(fù)用了'into'指向的對象,因?yàn)榉祷氐念愋团c'into'指向的類型完全匹配。
if unk, ok := into.(*runtime.Unknown); ok && unk != nil {
// 輸出原始數(shù)據(jù)
unk.Raw = originalData
// 輸出數(shù)據(jù)格式為JSON,那么問題來了,不是'data'才能保證是json么?如s.options.Yaml == true,originalData是yaml格式才對。
// 所以筆者有必要提一個(gè)issue,看看官方怎么解決,如此明顯的一個(gè)問題為什么沒有暴露出來?
// 筆者猜測:runtime.Unknown只在內(nèi)部使用,而內(nèi)部只用json格式,所以自然不會暴露出來。
unk.ContentType = runtime.ContentTypeJSON
// 輸出GVK。
unk.GetObjectKind().SetGroupVersionKind(*actual)
return unk, actual, nil
}
// 'into'不為空,通過into類型提取GVK,這樣在原始數(shù)據(jù)中的GVK和默認(rèn)GVK都沒有的字段用into的GVK補(bǔ)全。
if into != nil {
// 判斷'into'是否為runtime.Unstructured類型
_, isUnstructured := into.(runtime.Unstructured)
// 獲取'into'的GVK,需要注意的是返回的是一組GVK,types[0]是推薦的GVK。
types, _, err := s.typer.ObjectKinds(into)
switch {
// 'into'的類型如果沒有被注冊或者為runtime.Unstructured類型,則直接反序列成'into'指向的對象。
// 沒有被注冊的類型自然無法構(gòu)造對象,而非結(jié)構(gòu)體等同于map[string]interface{},不可能是API對象(因?yàn)锳PI對象必須是結(jié)構(gòu)體)。
// 所以這兩種情況直接反序列化到'into'對象就可以了,此時(shí)與json.Unmarshal()沒什么區(qū)別。
case runtime.IsNotRegisteredError(err), isUnstructured:
if err := caseSensitiveJSONIterator.Unmarshal(data, into); err != nil {
return nil, actual, err
}
return into, actual, nil
// 獲取'into'類型出錯(cuò),多半是因?yàn)椴皇侵羔?/span>
case err != nil:
return nil, actual, err
// 用'into'的GVK補(bǔ)全未設(shè)置的GVK,所以GVK的優(yōu)先級:originalData > default gvk > into
default:
*actual = gvkWithDefaults(*actual, types[0])
}
}
// 如果沒有Kind
if len(actual.Kind) == 0 {
return nil, actual, runtime.NewMissingKindErr(string(originalData))
}
// 如果沒有Version
if len(actual.Version) == 0 {
return nil, actual, runtime.NewMissingVersionErr(string(originalData))
}
// 那么問題來了,為什么不判斷Group?Group為""表示"core",比如我們寫ymal的時(shí)候Kind為Pod,apiVersion是v1,并沒有設(shè)置Group。
// 從函數(shù)名字可以看出復(fù)用'into'或者重新構(gòu)造對象,復(fù)用的原則是:如果'into'注冊的一組GVK有任何一個(gè)與*actual相同,則復(fù)用'into'。
// runtime.UseOrCreateObject()源碼讀者感興趣可以自己看下
obj, err := runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
if err != nil {
return nil, actual, err
}
// 反序列化對象,caseSensitiveJSONIterator暫且不用關(guān)心,此處可以理解為json.Unmarshal()。
// 當(dāng)然,讀者非要知道個(gè)究竟,可以看看代碼,筆者此處不注釋。
if err := caseSensitiveJSONIterator.Unmarshal(data, obj); err != nil {
return nil, actual, err
}
// 如果是非strict模式,可以直接返回了。其實(shí)到此為止就可以了,后面是針對strict模式的代碼,是否了解并不重要。
if !s.options.Strict {
return obj, actual, nil
}
// 筆者第一眼看到下面的以為看錯(cuò)了,但是擦了擦懵逼的雙眼,發(fā)現(xiàn)就是YAMLToJSON。如果原始數(shù)據(jù)是json不會有問題么?
// 筆者查看了一下yaml.YAMLToJSONStrict()函數(shù)注釋:由于JSON是YAML的子集,因此通過此方法傳遞JSON應(yīng)該是沒有任何操作的。
// 除非存在重復(fù)的字段,會解析出錯(cuò)。所以此處就是用來檢測是否有重復(fù)字段的,當(dāng)然,如果是yaml格式順便轉(zhuǎn)成了json。
// 感興趣的讀者可以閱讀源碼,筆者只要知道它的功能就行了,就不“深究”了。
altered, err := yaml.YAMLToJSONStrict(originalData)
if err != nil {
return nil, actual, runtime.NewStrictDecodingError(err.Error(), string(originalData))
}
// 接下來會因?yàn)槲粗淖侄螆?bào)錯(cuò),比如對象未定義的字段,未打標(biāo)簽的字段等。
// 此處使用DeepCopyObject()等同于新構(gòu)造了一個(gè)對象,而這個(gè)對象其實(shí)又沒什么用,僅作為一個(gè)臨時(shí)的變量使用。
strictObj := obj.DeepCopyObject()
if err := strictCaseSensitiveJSONIterator.Unmarshal(altered, strictObj); err != nil {
return nil, actual, runtime.NewStrictDecodingError(err.Error(), string(originalData))
}
// 返回反序列化的對象、GVK,所謂的strict模式無非是再做了一次轉(zhuǎn)換和反序列化來校驗(yàn)數(shù)據(jù)的正確性,結(jié)果直接丟棄。
// 所以說strict沒有必要不用開啟,除非你真正理解他的作用并且能夠承受帶來的后果。
return obj, actual, nil
}
// gvkWithDefaults()利用defaultGVK補(bǔ)全actual中未設(shè)置的字段。
// 需要注意的是,參數(shù)'defaultGVK'只是一次調(diào)用相對于actual的默認(rèn)GVK,不是Serializer.Decode()的默認(rèn)GVK。
func gvkWithDefaults(actual, defaultGVK schema.GroupVersionKind) schema.GroupVersionKind {
// actual如果沒有設(shè)置Kind則用默認(rèn)的Kind補(bǔ)全
if len(actual.Kind) == 0 {
actual.Kind = defaultGVK.Kind
}
// 如果Group和Version都沒有設(shè)置,則用默認(rèn)的Group和Version補(bǔ)全。
// 為什么必須是都沒有設(shè)置?缺少Version或者Group有什么問題么?下面的代碼給出了答案。
if len(actual.Version) == 0 && len(actual.Group) == 0 {
actual.Group = defaultGVK.Group
actual.Version = defaultGVK.Version
}
// 如果Version未設(shè)置,則用默認(rèn)的Version補(bǔ)全,但是前提是Group與默認(rèn)Group相同。
// 因?yàn)镚roup不同的API即便Kind/Version相同可能是兩個(gè)完全不同的類型,比如自定義資源(CRD)。
if len(actual.Version) == 0 && actual.Group == defaultGVK.Group {
actual.Version = defaultGVK.Version
}
// 如果Group未設(shè)置而Version與默認(rèn)的Version相同,為什么不用默認(rèn)的Group補(bǔ)全?
// 前面已經(jīng)解釋過了,應(yīng)該不用再重復(fù)了。
return actual
}
Encode
相比于解碼,編碼就簡單很多,直接按照選項(xiàng)(yaml、pretty)編碼就行了,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L297
// Encode()實(shí)現(xiàn)了Encoder.Encode()接口。
func (s *Serializer) Encode(obj runtime.Object, w io.Writer) error {
// CacheableObject允許對象緩存其不同的序列化數(shù)據(jù),以避免多次執(zhí)行相同的序列化,這是一種出于效率考慮設(shè)計(jì)的類型。
// 因?yàn)橥粋€(gè)對象可能會多次序列化json、yaml和protobuf,此時(shí)就需要根據(jù)編碼器的標(biāo)識符找到對應(yīng)的序列化數(shù)據(jù)。
if co, ok := obj.(runtime.CacheableObject); ok {
// CacheableObject筆者不再注釋了,感興趣的讀者可以自行閱讀源碼。
// 其實(shí)根據(jù)傳入的參數(shù)也能猜出來具體實(shí)現(xiàn):利用標(biāo)識符查一次map,如果有就輸出,沒有就調(diào)用一次s.doEncode()。
return co.CacheEncode(s.Identifier(), s.doEncode, w)
}
// 非CacheableObject對象,就執(zhí)行一次json的序列化。
return s.doEncode(obj, w)
}
// doEncode()類似于于json.Marshal(),只是寫入是io.Writer而不是[]byte。
func (s *Serializer) doEncode(obj runtime.Object, w io.Writer) error {
// 序列化成yaml?
if s.options.Yaml {
// 序列化對象為json
json, err := caseSensitiveJSONIterator.Marshal(obj)
if err != nil {
return err
}
// json->yaml
data, err := yaml.JSONToYAML(json)
if err != nil {
return err
}
// 寫入io.Writer。
_, err = w.Write(data)
return err
}
// 輸出易于理解的格式?那么問題來了,為什么輸出yaml不需要這個(gè)判斷?很簡單,yaml就是易于理解的。
if s.options.Pretty {
// 序列化對象為json
data, err := caseSensitiveJSONIterator.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
// 寫入io.Writer。
_, err = w.Write(data)
return err
}
// 非pretty模式,用我們最常用的json序列化方法,無非我們最常用方法是json.Marshal()。
// 如果需要寫入io.Writer,下面的代碼是標(biāo)準(zhǔn)寫法。
encoder := json.NewEncoder(w)
return encoder.Encode(obj)
}
identifier
前面筆者提到了,編碼器標(biāo)識符可以看做是標(biāo)簽的字符串形式,大家應(yīng)該很熟悉Kubernetes中的標(biāo)簽選擇器,符合標(biāo)簽選擇器匹配規(guī)則的所有API對象都可以看做"同質(zhì)"。同樣的道理,編碼器也有自己的標(biāo)簽,標(biāo)簽相同的所有編碼器是同質(zhì)的,即編碼同一個(gè)API對象的結(jié)果都是一樣的。編碼器標(biāo)識符的定義沒有那么復(fù)雜,就是簡單的字符串,匹配也非常簡單,標(biāo)識符相等即為匹配,所以標(biāo)識符可以理解為標(biāo)簽的字符串形式。源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/json/json.go#L66
// identifier()根據(jù)給定的選項(xiàng)計(jì)算編碼器的標(biāo)識符。
func identifier(options SerializerOptions) runtime.Identifier {
// 編碼器的唯一標(biāo)識符是一個(gè)map[string]string,不同屬性的組合形成更了唯一性。
result := map[string]string{
// 名字是json,表明是json編碼器。
"name": "json",
// 輸出格式為yaml或json
"yaml": strconv.FormatBool(options.Yaml),
// 是否為pretty模式
"pretty": strconv.FormatBool(options.Pretty),
}
// 序列化成json,生成最終的標(biāo)識符,json序列化是標(biāo)簽的一種字符串形式。
identifier, err := json.Marshal(result)
if err != nil {
klog.Fatalf("Failed marshaling identifier for json Serializer: %v", err)
}
// 也就是說,只要yaml和pretty選項(xiàng)相同的任意兩個(gè)json.Serializer,任何時(shí)候編碼同一個(gè)API對象輸出一定是相同的。
// 所以當(dāng)API對象被多個(gè)編碼器多次編碼時(shí),以編碼器標(biāo)識符為鍵利用緩沖避免重復(fù)編碼。
return runtime.Identifier(identifier)
}
yaml
其實(shí)json.Serializer支持json和ymal,那么還有必要定義yaml.Serializer么?畢竟構(gòu)造兩個(gè)json.Serializer對象,一個(gè)開啟yaml選型,一個(gè)關(guān)閉yaml選項(xiàng)就可以了。來看看yaml.Serializer的定義,源碼鏈接:https://github.com/kubernetes/apimachinery/blob/release-1.21/pkg/runtime/serializer/yaml/yaml.go#L26
// yamlSerializer實(shí)現(xiàn)了runtime.Serializer()。
// 沒有定義Serializer類型,而是一個(gè)包內(nèi)的私有類型yamlSerializer,如果需要使用這個(gè)類型必須通過包內(nèi)的公有接口創(chuàng)建。
type yamlSerializer struct {
// 直接繼承了runtime.Serializer,不用想肯定是json.Serializer。
runtime.Serializer
}
// NewDecodingSerializer()向支持json的Serializer添加yaml解碼支持。
// 也就是說yamlSerializer編碼json,解碼yaml,當(dāng)然從接口名字看,調(diào)用這個(gè)接口估計(jì)只需要用解碼能力吧。
// 好在筆者檢索了一下源碼,yamlSerializer以及NewDecodingSerializer()沒有引用的地方,應(yīng)該是歷史遺留的代碼。
func NewDecodingSerializer(jsonSerializer runtime.Serializer) runtime.Serializer {
return &yamlSerializer{jsonSerializer}
}
// Decode()實(shí)現(xiàn)了Decoder.Decode()接口。
func (c yamlSerializer) Decode(data []byte, gvk *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
// yaml->json
out, err := yaml.ToJSON(data)
if err != nil {
return nil, nil, err
}
// 反序列化json
data = out
return c.Serializer.Decode(data, gvk, into)
}
總結(jié)
json.Serializer可以實(shí)現(xiàn)json和yaml兩種數(shù)據(jù)格式的序列化/反序列化,而yaml.Serializer基本不用了; MetaFactory的功能就是提取apiVersion和kind字段,然后返回GVK; json.Serializer也可以像json/yaml.Unmarshal()一樣使用,只要傳入的'into'的類型沒有在Schema中注冊就可以了; json.Serializer在反序列化之前需要計(jì)算API對象的GVK,計(jì)算原則是優(yōu)先使用json中的GVK,如果GVK有殘缺,則采用默認(rèn)GVK補(bǔ)全,最后用'into'指向的對象類型補(bǔ)全; 其實(shí)runtime.Serializer只是比json/yaml.Unmarshal()多了類型提取并構(gòu)造對象的過程,但是依然存在無法通用的問題,即解碼json和yaml需要不同的對象,這就要RecognizingDecoder來解決了;
原文鏈接:https://github.com/jindezgm/k8s-src-analysis/blob/master/apimachinery/runtime/Serializer.md
