在進入 kube-apiserver
原始碼分析前,有一個非常重要的概念需要了解甚至熟悉的:資源登入檔(scheme)。
Kubernetes
中一切皆資源,管理的是資源,建立、更新、刪除的是資源。如何對龐雜的資源進行管理就成了一件大事。Kubernetes
通過引入 scheme
資源登入檔,將資源資訊註冊到資源登入檔,各個元件通過索引資源登入檔實現資源的管理。
直接開始閱讀 kube-apiserver
scheme
部分的原始碼是比較困難的,這裡舉程式碼範例如下:
package main
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func main() {
// KnownType external
coreGV := schema.GroupVersion{Group: "", Version: "v1"}
extensionsGV := schema.GroupVersion{Group: "extensions", Version: "v1beta1"}
// KnownType internal
coreInternalGV := schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}
// UnversionedType
Unversioned := schema.GroupVersion{Group: "", Version: "v1"}
schema := runtime.NewScheme()
schema.AddKnownTypes(coreGV, &corev1.Pod{})
schema.AddKnownTypes(extensionsGV, &appsv1.DaemonSet{})
schema.AddKnownTypes(coreInternalGV, &corev1.Pod{})
schema.AddUnversionedTypes(Unversioned, &metav1.Status{})
fmt.Println(*schema)
fmt.Println(schema.KnownTypes(coreGV))
}
範例中有幾點需要關注。
runtime.NewScheme
建立 scheme
範例:func NewScheme() *Scheme {
s := &Scheme{
gvkToType: map[schema.GroupVersionKind]reflect.Type{},
typeToGVK: map[reflect.Type][]schema.GroupVersionKind{},
unversionedTypes: map[reflect.Type]schema.GroupVersionKind{},
unversionedKinds: map[string]reflect.Type{},
fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{},
defaulterFuncs: map[reflect.Type]func(interface{}){},
versionPriority: map[string][]string{},
schemeName: naming.GetNameFromCallsite(internalPackages...),
}
s.converter = conversion.NewConverter(nil)
...
return s
}
Scheme
的結構體有四個 field
需要重點關注:
gvkToType
: 記錄的是 schema.GroupVersionKind
和資源型別的對映關係。typeToGVK
: 記錄的是資源型別和 schema.GroupVersionKind
的對映關係。unversionedTypes
: 記錄的是無版本資源型別和 schema.GroupVersionKind
的對映關係。unversionedKinds
: 記錄的是資源種類 Kind
和無版本資源型別的對映關係。還有一個 Scheme.converter
也挺重要,這裡先跳過不講。
Scheme
的 AddKnownTypes
和 AddUnversionedTypes
方法建立資源 Group/Version/Kind
和資源型別的相互對映關係。以 AddKnownTypes
為例,函數通過反射 reflect.TypeOf(obj)
獲得物件 runtime.Object
的資源型別。這裡,物件是資源的介面,可實現資源和物件的相互轉換。
func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
s.addObservedVersion(gv)
for _, obj := range types {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Pointer {
panic("All types must be pointers to structs.")
}
t = t.Elem()
s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
}
}
func (gv GroupVersion) WithKind(kind string) GroupVersionKind {
return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind}
}
func (s *Scheme) AddKnownTypeWithName(gvk schema.GroupVersionKind, obj Object) {
...
t := reflect.TypeOf(obj)
s.gvkToType[gvk] = t
}
然後呼叫 gv.WithKind(t.Name())
轉換為 GroupVersionKind
,可以看出上面反射的資源型別作為 kind
賦給 GroupVersionKind
。
最後呼叫 AddKnownTypeWithName
將 schema.GroupVersionKind
和資源型別寫入 Scheme.gvkToType
。
執行程式碼結果如下:
{
map[
{ __internal Pod}:0xb46180
{ v1 Pod}:0xb46180
{ v1 Status}:0xb56d40
{extensions v1beta1 DaemonSet}:0xb44e40
]
map[
0xb44e40:[{extensions v1beta1 DaemonSet}]
0xb46180:[{ v1 Pod} { __internal Pod}]
0xb56d40:[{ v1 Status}]
]
map[
0xb56d40:{ v1 Status}
]
map[
Status:0xb56d40
]
map[]
map[]
0xc000124888
map[]
[{ v1} {extensions v1beta1}]
pkg/runtime/scheme.go:100
}
map[Pod:v1.Pod Status:v1.Status]
Kubernetes
資源分為外部資源和內部資源。外部資源是對外可存取的,其版本為 v1/v1beta1
等,內部資源是 Kubernetes
內部存取的資源,其版本為 __internal
。為什麼會有內部版本呢?
為了適配版本間的 convert
。
試想,如果 v1
、v1apha1
、v1beta1
、v1beta2
要相互轉換需要建立十二種轉換關係:
v1 ----> v1alpha1
v1 ----> v1beta1
v1 ----> v1beta2
v1alpha1 ----> v1
v1alpha1 ----> v1beta1
v1alpha1 ----> v1beta2
v1beta1 ----> v1
v1beta1 ----> v1alpha1
v1beta1 ----> v1beta2
v1beta2 ----> v1
v1beta2 ----> v1alpha1
v1beta2 ----> v1beta1
可以看到這種轉換已經比較複雜了,隨著資源增多,這種資源版本之間的轉換會越來越複雜且難以維護。Kubernetes
通過引入內部版本 __internl
建立每種資源到 __internal
的轉換,從而實現不同資源版本的相互轉換。
v1 ----> __internal
__internal ----> v1
v1alpha1 ----> __internal
__internal ----> v1alpha1
v1beta1 ----> __internal
__internal ----> v1beta1
v1beta2 ----> __internal
__internal ----> v1beta2
轉換示意圖如下:
可以看到,相當乾淨,清爽。
第一節介紹了 scheme
結構及部分特性。繼續看 kube-apiserver
中 scheme
的應用。
kube-apiserver
中資源的註冊通過匯入包的形式實現。Kubernetes
將資源拆分為三類,並且由三種 HTTP Server
負責處理這三類資源,架構如下。
關於這幅圖,這裡不多講,後續會逐層介紹。
要知道的是,三類資源分別註冊到 extensionsapiserver.Scheme
,legacyscheme.Scheme
和 aggregatorscheme.Scheme
三種資源登入檔中。
以 legacyscheme.Scheme
資源登入檔為例。
kube-apiserver
的啟動 app
包中匯入 "k8s.io/kubernetes/pkg/api/legacyscheme"
和 "k8s.io/kubernetes/pkg/controlplane"
包:
package app
import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/controlplane"
)
legacyscheme
包中建立了 Scheme
資源登入檔,Codecs
編碼器和 ParameterCodec
引數編碼器。
# kubernetes/pkg/api/legacyscheme/scheme.go
package legacyscheme
var (
Scheme = runtime.NewScheme()
Codecs = serializer.NewCodecFactory(Scheme)
ParameterCodec = runtime.NewParameterCodec(Scheme)
)
controlplane
包中匯入需要註冊的資源組:
# kubernetes/pkg/controlplane/import_known_versions.go
package controlplane
import (
_ "k8s.io/kubernetes/pkg/apis/apps/install"
)
以註冊 apps
資源組為例:
# kubernetes/pkg/apis/apps/install/install.go
package install
import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/v1"
"k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/apps/v1beta2"
)
func init() {
Install(legacyscheme.Scheme)
}
func Install(scheme *runtime.Scheme) {
utilruntime.Must(apps.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
utilruntime.Must(v1beta2.AddToScheme(scheme))
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
}
資源組中呼叫 apps.AddToScheme
,v1beta1.AddToScheme
,v1beta2.AddToScheme
和 v1.AddToScheme
函數將同一資源組不同版本的資源註冊到 legacyscheme.Scheme
資源登入檔中。
其中 apps.AddToScheme
註冊的是內部版本的資源:
package apps
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
const GroupName = "apps"
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&DaemonSet{},
&DaemonSetList{},
&Deployment{},
&DeploymentList{},
&DeploymentRollback{},
&autoscaling.Scale{},
&StatefulSet{},
&StatefulSetList{},
&ControllerRevision{},
&ControllerRevisionList{},
&ReplicaSet{},
&ReplicaSetList{},
)
return nil
}
v1beta1.AddToScheme
,v1beta2.AddToScheme
和 v1.AddToScheme
註冊的是外部版本的資源,以 v1.AddToScheme
為例:
package v1
const GroupName = "apps"
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Deployment{},
&DeploymentList{},
&StatefulSet{},
&StatefulSetList{},
&DaemonSet{},
&DaemonSetList{},
&ReplicaSet{},
&ReplicaSetList{},
&ControllerRevision{},
&ControllerRevisionList{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
需要注意的是,內部版本和外部版本註冊的資源並不一樣。比如 Deployment
資源,外部版本參照的是 kubernetes/vendor/k8s.io/api/apps/v1/types.go
中的資源定義:
type Deployment struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
資源宣告了 json
和 protobuf
tag,以便於外部存取的序列化,反序列化操作。
內部版本參照的是 kubernetes/pkg/apis/apps/types.go
中的資源定義:
type Deployment struct {
metav1.TypeMeta
metav1.ObjectMeta
Spec DeploymentSpec
Status DeploymentStatus
}
內部資源不需要被外部存取,沒有 tag 宣告。
到這裡,kube-apiserver 關於資源的註冊就差不多了。下一篇將進一步介紹 scheme
的 convert
,kube-apiserver
中如何使用 scheme
及 scheme
和 object
相互轉換等內容。