深入剖析kubernetes的API對象類型定義

目錄

 

1.背景

2.分析

2.1api

2.2metav1

2.2.1MetaType

2.2.3ListMeta

2.3runtime

2.3.1schema

2.3.2Object

3.總結


1.背景

在初學kuberentes的時候,筆者作爲碼農就非常好奇它的代碼實現。於是開始git clone代碼,首先就被kuberentes的代碼倉庫搞蒙了,kuberentes項目有好多倉庫,包括kubernetes、client-go、api、apimachinery...,我該從哪兒看起?索性就從kubernetes的API對象的定義開始看吧,跟api有關的倉庫有兩個api、apimachinery,同時在apimachinery倉庫中還有api、apis兩個包,貌似他們都有types.go、meta.go、interface.go。此時筆者的心情真是難以言表,但是好奇心又慫恿自己弄明白原理,於是也就有了這篇類似總結的文章。希望本文能夠幫助讀者初步瞭解kubernetes代碼,成爲一篇比較基礎的入門文章。

2.分析

以前,在筆者印象中API都是各種接口,比較典型的就是操作系統的API。一提到API默認就是函數定義,直到REST API流行起來,API的雖然形式上不再是函數,但是原理上是一樣的,只是接口的服務端從本地變成了遠程。其實筆者一直都忽略了API的另一個重點,那就是接口類型,也就是接口函數的參數類型,它也是API的一部分。筆者認爲:操作系統的API強調方法,爲方法設計參數類型居多,而Rest API的強調資源,而方法就那麼幾個。在瞭解一個系統的Rest API時首先就要看有哪些資源,這些資源如何定義,支持哪些操作等等。

kubernetes對外提供的就是Rest API,只是這部分被client-go這個項目封裝成了類似SDK的形態。而apisever提供的就是基於API對象的各種操作,本文目標是探究一下API對象在kubernetes內部是如何定義以及實現的。

2.1api

在kubernetes裏提供了非常多的API對象,它們被定義在k8s.io/api這個倉庫中,這也是本章節命名爲api的原因。Pod應該是最爲基礎的對象之一,在初學kubernetes時我相信大部分同學都寫過類似下面的代碼:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
spec:
  containers:
  - name: myapp-container
    image: busybox
    command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']

通過命令kubectl create -f xxx.yaml在kubernetes中創建了一個名字爲myapp-pod的Pod對象(此處忽略namespace)。

用編程的角度分析上面的流程:在kubernetes中需要有一個Pod的類型,每次執行kubectl create -f xxx.yaml創建Pod對象的時候需要實例化Pod,並把xxx.yaml中的參數賦值到Pod對象中。

現在就來看看kubernetes中Pod類型是如何定義的:

// 代碼源自k8s.io/api/core/v1/types.go
// kubernetes的API對象是單獨的git倉庫(https://github.com/kubernetes/api.git),可見API對象
// 在kubernetes項目中的重要程度。
type Pod struct {
    // metav1是"k8s.io/apimachinery/pkg/apis/meta/v1"的重命名。額...,apimachinery又是
    // 什麼鬼?apis包又是幹啥的?起初筆者被這些名字搞得雲裏霧裏,但所有的這些迷霧都會在本文揭開,此處
    // 讀者只需要知道這個是類型的meta。那什麼又是類型的meta呢?以普通類型int32爲例,類型名稱
    // “int32”、類型佔用內存空間4字節就是類型的meta,簡單的說就是類型屬性的描述。而kubernetes的
    // API對象的類型meta是如何定義的,後面會有單獨章節詳細說明。
    metav1.TypeMeta `json:",inline"`
    // 同樣是metav1包,這回是對象的meta。同樣以int32爲例,對象的地址就屬於對象meta。在kubernetes
    // 中,API對象都有自己的屬性,並且他們都有相同的屬性,例如名字、命名空間、標籤等等。kuberentes把
    // 這些公共的屬性提取出來就是metav1.ObjectMeta,成爲了API對象類型的父類。
    metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
    // 從名字上看還是比較好理解的,就是Pod規格,最爲代表性的就是CPU、內存的資源使用。它和xxx.yaml中
    // spec是關聯的。PodSpec不作爲本文的說明重點,所以不再詳細解釋。
    Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
    // Pod的狀態,比如是運行還是掛起、Pod的IP、啓動時間等等。
    Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

從Pod的定義來看,它繼承了metav1.TypeMeta和metav1.ObjectMeta兩個類型,同時還定義了Spec和Status兩個成員變量。其實kubernetes絕大部分API對象的類型都是這個結構,他們都繼承metav1.TypeMeta和metav1.ObjectMeta,前者用於定義類型的屬性,後者用於定義對象的公共屬性;Spec用於定義API對象類型的私有屬性,也是API對象之間的區別所在(例如Pod和Deployment雖然都繼承了兩個父類,但是他們二者的區別就是通過Spec實現的。就像是同一對父母的兩個孩子,有像的地方,更多的還是不像的地方讓他們成爲了兩個獨立的個體,這就是繼承的魅力所在);Status則是用於描述每個對象的狀態的,這和每個對象的類型緊密相關的。細心的讀者不難發現,metav1.TypeMeta和metav1.ObjectMeta對應的是xxx.yaml中的kind、apiVersion和metadata字段,Spec對應xxx.yaml中spec字段。這一點在代碼註釋`json:...`可以證實,這裏也可以得出另一個結論,那就是xxx.yaml就是Pod類型yaml的序列化。所以,kubectl create -f xxx.yaml就等同於new(Pod)。

此處要對metav1.TypeMeta和metav1.ObjectMeta多說兩句,可以把他們兩個看做是kubernetes全部API對象的基類,類似java中的Object類。語言因爲有編譯器的存在,類似metav1.TypeMeta的東西被編譯屏蔽了,所以開發者看到的所有的類繼承於Object。但在kubernetes中,每個API對象都需要metav1.TypeMeta字段用於描述自己是什麼類型,這樣才能構造相應類型的對象,所以相同類型的所有對象的metav1.TypeMeta字段都是相同的。但是metav1.ObjectMeta則不同,它是定義對象的公共屬性,即所有對象都應該具備的屬性。這部分就是和對象本身相關,和類型無關,所以相同類型的所有對象的metav1.ObjectMeta可能是不同的。

在kubernetes的API對象中除了單體對象外,還有對象列表類型,用於描述一組對象,等同於golang中的slice。對象列表的典型應用場景就是列舉,對象列表就可以表達一組對象。可能有些讀者會問爲什麼不用對象的slice,例如[]Pod,伴隨着筆者對對象列表的解釋讀者就會理解,此處以PodList爲例進行分析:

// 代碼源自k8s.io/api/core/v1/types.go
type PodList struct {
    // PodList同樣需要繼承metav1.TypeMeta,畢竟對象列表也好、單體對象也好都需要類型屬性。
    // PodList比[]Pod類型在yaml或者json表達上多了類型描述,當需要根據yaml構建對象列表的時候,
    // 就可以根據類型描述反序列成爲PodList。而[]Pod則不可以,他必須確保yaml就是[]Pod序列化的
    // 結果,否則就會報錯。這就無法實現一個通用的對象序列化/反序列化。
    metav1.TypeMeta `json:",inline"`
    // 與Pod不同,PodList繼承了metav1.ListMeta,metav1.ListMeta是所有對象類表類型的父類,
    // 他定義了所有列表類型公共屬性。
    metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
    // Items就是PodList定義的本質了,其實就是Pod的slice。說白了PodList就是[]Pod基礎上加了一些
    // 跟類型和類表相關的信息,這些信息的作用會在後面的章節做詳細解釋。
    Items []Pod `json:"items" protobuf:"bytes,2,rep,name=items"`
}

前面已經解釋了Pod的定義,PodList就不多解釋了。此處做一個小結:

  1. metav1.TypeMeta和metav1.ObjectMeta是所有API單體對象的父類;
  2. metav1.TypeMeta和metav1.ListMeta是所有API列表對象的父類;
  3. metav1.TypeMeta纔是所有API對象的父類,這也很好理解,畢竟所有的對象都要說明自己是什麼類型;

2.2metav1

metav1是k8s.io/apimachinery/pkg/apis/meta/v1的縮寫,後文會簡稱爲metav1。

2.2.1MetaType

作爲所有API對象的父類,是時候揭開它的真面目了:

// 代碼源自:k8s.io/apimachinery/pkg/apis/meta/v1/types.go
// 同樣來自apimachinery倉庫,ObjectMeta是xxx.yaml中metadata字段,平時我們填寫的metadata
// 一般只有name、label,其實ObjectMeta字段還是有很多內容了,讓筆者逐一介紹一下。
type ObjectMeta struct {
    // 對象的名字應該不用介紹了。
    Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
    // 如果Name爲空,系統這爲該對象生成一個唯一的名字。
    GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"`
    // 命名空間,在平時學習、調試的時候很少用,但是在發佈的時候會經常用。
    Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
    // 對象的URL,由系統生成。
    SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"`
    // 對象的唯一ID,由系統生成。
    UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"`
    // 資源版本,這是一個非常有意思且變量,版本可以理解爲對象在時間軸上的一個時間節點,代表着對象最後
    // 一次更新的時刻。如果說Name是在Namespace空間下唯一,那麼ResourceVersion則是同名、同類型
    // 對象時間下唯一。因爲同名對象在不同時間可能會更新、刪除再添加,在比較兩個對象誰比較新的情況
    // 非常有用,比如Watch。
    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"`
    // 該對象依賴的對象類表,如果這些依賴對象全部被刪除了,那麼該對象也會被回收。如果該對象對象被
    // 某一controller管理,那麼類表中有一條就是指向這個controller的。例如Deployment對象的
    // OwnerReferences有一條就是指向DeploymentController的。
    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"`
    ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"`
    ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"`
}

// 代碼源自k8s.io/apimachinery/pkg/apis/meta/v1/meta.go,GetObjectMeta是MetaAccessor
// 的接口函數,這個函數說明了ObjectMeta實現了MetaAccessor。
func (obj *ObjectMeta) GetObjectMeta() Object { return obj }

// 下面所有的函數是接口Object的ObjectMeta實現,可以看出來基本就是setter/getter方法。納尼?
// 又一個Object(這個Object是metav1.Object,本章節簡寫爲Object)?Object是API對象公共屬性
// (meta信息)的抽象,下面的函數是Object所有函數的實現,因爲功能比較簡單,筆者就不一一註釋了。
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) GetClusterName() string                 { return meta.ClusterName }
func (meta *ObjectMeta) SetClusterName(clusterName string)      { meta.ClusterName = clusterName }
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields }
func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) {
    meta.ManagedFields = managedFields
}

ObjectMeta每個成員變量是幹什麼用的對於理解本文並沒有什麼幫助,對於讀者來說只需要知道這些屬性是所有API對象都有的公共屬性。所以筆者對於每個成員變量的註釋也僅僅停留在表面定義,並沒有做深入的解釋。

此時,可以得出一個結論:ObjectMeta實現了Object和MetaAccessor兩個interface,而kubernetes所有單體對象都繼承了ObjectMeta,那麼所有的API對象就都實現了Object和MetaAccessor。kubernetes中有很多地方訪問API對象的這些meta信息並且不區分對象類型,Object是一個不錯的選擇。

2.2.3ListMeta

和ObjectMeta功能類似,ListMeta定義了所有列表對象的公共屬性:

// 代碼源自:k8s.io/apimachinery/pkg/apis/meta/v1/types.go
type ListMeta struct {
    // 下面這兩個變量在ObjectMeta相同,不多解釋
    SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,1,opt,name=selfLink"`
    ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,2,opt,name=resourceVersion"`
    // 在列舉對象的時候可能會有非常多的對象,kubernetes支持分頁獲取,類似於SQL的limit,當對象總量
    // 多於單頁的總量的時候,這個變量就會被設置。它用來告知用戶需要繼續獲取,並且它包含了下次獲取的
    // 起始位置。
    Continue string `json:"continue,omitempty" protobuf:"bytes,3,opt,name=continue"`
    // 從字面意思也能理解,就是還剩多少個對象,這個和Continue是配合使用的,當Continue被設置了
    // 這個變量就不會爲空,用來告訴用戶還有多少對象沒有獲取。
    RemainingItemCount *int64 `json:"remainingItemCount,omitempty" protobuf:"bytes,4,opt,name=remainingItemCount"`
}

// 代碼源自k8s.io/apimachinery/pkg/apis/meta/v1/meta.go
// 下面所有的函數是接口ListInterface的ListMeta實現,ListInterface是API對象列表公共屬性(meta)
// 的抽象。
func (meta *ListMeta) GetResourceVersion() string        { return meta.ResourceVersion }
func (meta *ListMeta) SetResourceVersion(version string) { meta.ResourceVersion = version }
func (meta *ListMeta) GetSelfLink() string               { return meta.SelfLink }
func (meta *ListMeta) SetSelfLink(selfLink string)       { meta.SelfLink = selfLink }
func (meta *ListMeta) GetContinue() string               { return meta.Continue }
func (meta *ListMeta) SetContinue(c string)              { meta.Continue = c }
func (meta *ListMeta) GetRemainingItemCount() *int64     { return meta.RemainingItemCount }
func (meta *ListMeta) SetRemainingItemCount(c *int64)    { meta.RemainingItemCount = c }

此處做一個小結,在metav1包中,爲API單體對象和對象列表的公共屬性(meta)做了抽象,分別爲metav1.Object和 metav1.ListInterface。同時,metav1包爲這兩個抽象做了實現,他們分別爲metav1.ObjectMeta和metav.listMeta,API對象類型可以通過繼承這些類實現抽象,是不是有一點apimachinery所定義的那樣“類型”、“基礎設施”的意思了?

2.3runtime

2.3.1schema

前文提到了metav1.TypeMeta實現了schema.ObjectKind(本節簡稱ObjectKind),並且metav1.TypeMeta已經非常直觀的和xxx.yaml的相應字段對應上了,那ObjectKind又是幹什麼的呢?

// 代碼源自k8s.io/apimachinery/pkg/runtime/schema/interfaces.go
// ObjectKind是接口,兩個接口函數是GroupVersionKind類型的setter和getter
type ObjectKind interface {
    SetGroupVersionKind(kind GroupVersionKind)
    GroupVersionKind() GroupVersionKind
}
// GroupVersionKind纔是kubernetes的API對象類型真身,他包括Kind、Version和Group。其中Kind和
// Version還比較好理解,Group又是什麼?其實Group/Version纔是xxx.yaml的apiVersion字段。
// 在kuberentes中API對象是分組的,像Pod、Service、ConfigMap都屬於core分組,而core分組的對象
// 無需在apiVersion字段明文寫出來,系統會默認將這類的對象歸爲core分組,正如文章開始那個Pod的例子。
// 詳情可以看下面的代碼實現。
type GroupVersionKind struct {
    Group   string
    Version string
    Kind    string
}
// 這個函數在metav1.TypeMeta實現GroupVersionKind()接口的時候調用了,該函數調用了ParseGroupVersion
// 實現從apiVersion解析Group和Version。
func FromAPIVersionAndKind(apiVersion, kind string) GroupVersionKind {
    if gv, err := ParseGroupVersion(apiVersion); err == nil {
        return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind}
    }
    return GroupVersionKind{Kind: kind}
}
// 從apiVersion解析Group和Version。
func ParseGroupVersion(gv string) (GroupVersion, error) {
    // 這種不報錯是什麼道理?什麼情況下會有對象沒有Group和Version?
    if (len(gv) == 0) || (gv == "/") {
        return GroupVersion{}, nil
    }
    // 數apiVersion中有幾個‘/’
    switch strings.Count(gv, "/") {
    // 沒有'/',就像文章開始的Pod的例子,那麼Group就是空字符串,系統默認會把空字符串歸爲core
    case 0:
        return GroupVersion{"", gv}, nil
    // 有一個'/',那麼就以'/'分割apiVersion,左邊爲Group,右邊爲Version。
    case 1:
        i := strings.Index(gv, "/")
        return GroupVersion{gv[:i], gv[i+1:]}, nil
    // 其他則爲格式錯誤。
    default:
        return GroupVersion{}, fmt.Errorf("unexpected GroupVersion string: %v", gv)
    }
}

這就爲什麼Deployment的apiVersion是apps/v1或者extension/v1beta1了,至此可以總結如下:

  1. schema.ObjecKind是所有API對象類型meta的抽象;
  2. metav1.TypeMeta是schema.ObjecKind的一個實現,API對象類型通過繼承metav1.TypeMeta實現schema.ObjecKind;

2.3.2Object

如果說schema.ObjecKind是所有API對象類型的抽象,配合metav1.Object作爲所有API單體對象公共屬性的抽象,似乎已經找到了所有API對象的根。但是有沒有感覺怪怪的,如果想通過一個基類的指針指向任意API單體對象,schema.ObjecKind和metav1.Object感覺都不合適,因爲他們所能訪問的域是有限。如果有一個函數需要訪問任何API對象的類型和公共屬性,那麼就要傳入同一個對象的兩個指針(schema.ObjecKind和metav1.Object),這就太讓人難以接受了。有沒有一個類型作爲API單體對象的統一的基類呢?這就是本節要討論的:runtime.Object(本章節簡稱 Object)。

// 代碼源自k8s.io/apimachinery/pkg/runtime/interfaces.go
type Object interface {
    // 有了這個函數,就可以訪問對象的類型域
    GetObjectKind() schema.ObjectKind
    // deepcopy是golang深度複製對象的方法,至於什麼是深度複製本文就不解釋了。這是個不錯的函數,
    // 可以通過這個接口複製任何API對象而無需類型依賴。
    DeepCopyObject() Object
    // 就這麼兩個函數了麼?那如果需要訪問對象的公共屬性域怎麼辦?不應該有一個類似GetObjectMeta()
    // 的接口麼?這一點,kubernetes是通過另一個方式實現的,見下面的代碼。
}

// 代碼源自k8s.io/apimachinery/pkg/api/meta/meta.go,注意是api包,不是apis
// Accessor()函數可以把obj安全的轉換爲metav1.Object,這樣也就避免了每個API對象類型都需要實現
// 類似GetObjectMeta()的接口了。有的讀者肯定會問:所有的API對象都繼承了metav1.ObjectMeta,
// 這個類型不是實現了GetObjectMeta()麼?筆者就要在這裏做出說明:筆者提到是類似GetObjectMeta(),
// 如果接口名字是ObjectMeta(),那豈不是繼承metav1.ObjectMeta就沒用了?一個頂層的類型抽象定義不
// 應該依賴於相對底層類型的實現。
func Accessor(obj interface{}) (metav1.Object, error) {
    // 使用了golang的switch type語法
    switch t := obj.(type) {
    // 因爲API對象類型都繼承了metav1.ObjectMeta,也就自然實現了metav1.Object。
    case metav1.Object:
        return t, nil
    // 在ObjectMeta章節筆者提到了,metav1.ObjectMeta實現了metav1.ObjectMetaAccessor,
    // 所以API對象也自然實現了metav1.ObjectMetaAccessor。但是API對象會在上一個case就返回
    // 了,這個case是給誰用的呢?筆者也比較疑惑,筆者感覺是那些沒有直接繼承metav1.ObjectMeta
    // 卻實現了metav1.ObjectMetaAccessor的類型,筆者暫時還沒找到相關類型定義。
    case metav1.ObjectMetaAccessor:
        if m := t.GetObjectMeta(); m != nil {
            return m, nil
        }
        return nil, errNotObject
    default:
        return nil, errNotObject
    }
}

等下,爲什麼沒有看到API對象實現runtime.Object.DeepCopyObject()?那是因爲deep copy是具體API對象類型需要實現的,存在類型依賴,作爲API對象類型的父類不能實現。此處還是以Pod爲例,看看Pod是如何實現DeepCopyObject()的。

// +genclient
// +genclient:method=GetEphemeralContainers,verb=get,subresource=ephemeralcontainers,result=EphemeralContainers
// +genclient:method=UpdateEphemeralContainers,verb=update,subresource=ephemeralcontainers,input=EphemeralContainers,result=EphemeralContainers
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// 上面+k8s:deepcopy-gen:....就是告訴代碼生成工具爲下面的類型生成runtime.Object接口的
// DeepCopyObject()函數實現。因爲所有的API對象類型都要實現DeepCopyObject()函數,這是一個相當
// 大量的重複工作,所以kubernetes用代碼生成工具來實現。至於如何實現的不作爲本文討論重點,只要讀者
// 知道deep copy的目的就可以了。
type Pod struct {
    ......
}

3.總結

至此,前面各章節的總結都可以忘掉了,因爲那些總結都是基於當時的知識背景做的總結,可能缺乏全局性的考慮做出錯誤的結論,所以在此做出通盤的總結,如下圖所示:

 

  1. runtime.Object是所有API單體對象的根類(interface);
  2. schema.ObjectKind是對API對象類型的抽象(interface);
  3. metav1.Object是對API對象公共屬性的抽象(interface);
  4. metav1.ListInterface是對API對象列表公共屬性的抽象(interface);
  5. metav1.TypeMeta是schema.ObjectKind的一個實現,API對象類型繼承之;
  6. metav1.ObjectMeta是metav1.Object的一個實現,API對象類型繼承之;
  7. metav1.ListMeta是metav1.ListInterface的一個實現,API對象列表繼承之;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章