前言
在进入 kube-apiserver 源码分析前,有一个非常重要的概念需要了解甚至熟悉的:资源注册表(scheme)。
Kubernetes 中一切皆资源,管理的是资源,创建、更新、删除的是资源。如何对庞杂的资源进行管理就成了一件大事。Kubernetes 通过引入 scheme 资源注册表,将资源信息注册到资源注册表,各个组件通过索引资源注册表实现资源的管理。
介绍
直接开始阅读 kube-apiserver scheme 部分的源码是比较困难的,这里举代码示例如下:
1package main
2
3import (
4 "fmt"
5
6 appsv1 "k8s.io/api/apps/v1"
7 corev1 "k8s.io/api/core/v1"
8 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9 "k8s.io/apimachinery/pkg/runtime"
10 "k8s.io/apimachinery/pkg/runtime/schema"
11)
12
13func main() {
14 // KnownType external
15 coreGV := schema.GroupVersion{Group: "", Version: "v1"}
16 extensionsGV := schema.GroupVersion{Group: "extensions", Version: "v1beta1"}
17
18 // KnownType internal
19 coreInternalGV := schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}
20
21 // UnversionedType
22 Unversioned := schema.GroupVersion{Group: "", Version: "v1"}
23
24 schema := runtime.NewScheme()
25 schema.AddKnownTypes(coreGV, &corev1.Pod{})
26 schema.AddKnownTypes(extensionsGV, &appsv1.DaemonSet{})
27 schema.AddKnownTypes(coreInternalGV, &corev1.Pod{})
28 schema.AddUnversionedTypes(Unversioned, &metav1.Status{})
29
30 fmt.Println(*schema)
31 fmt.Println(schema.KnownTypes(coreGV))
32}
示例中有几点需要关注。
- 通过
runtime.NewScheme创建scheme实例:
1func NewScheme() *Scheme {
2 s := &Scheme{
3 gvkToType: map[schema.GroupVersionKind]reflect.Type{},
4 typeToGVK: map[reflect.Type][]schema.GroupVersionKind{},
5 unversionedTypes: map[reflect.Type]schema.GroupVersionKind{},
6 unversionedKinds: map[string]reflect.Type{},
7 fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{},
8 defaulterFuncs: map[reflect.Type]func(interface{}){},
9 versionPriority: map[string][]string{},
10 schemeName: naming.GetNameFromCallsite(internalPackages...),
11 }
12 s.converter = conversion.NewConverter(nil)
13
14 ...
15 return s
16}
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 的资源类型。这里,对象是资源的接口,可实现资源和对象的相互转换。
1func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
2 s.addObservedVersion(gv)
3 for _, obj := range types {
4 t := reflect.TypeOf(obj)
5 if t.Kind() != reflect.Pointer {
6 panic("All types must be pointers to structs.")
7 }
8 t = t.Elem()
9 s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
10 }
11}
12
13func (gv GroupVersion) WithKind(kind string) GroupVersionKind {
14 return GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind}
15}
16
17func (s *Scheme) AddKnownTypeWithName(gvk schema.GroupVersionKind, obj Object) {
18 ...
19 t := reflect.TypeOf(obj)
20 s.gvkToType[gvk] = t
21}
然后调用 gv.WithKind(t.Name()) 转换为 GroupVersionKind,可以看出上面反射的资源类型作为 kind 赋给 GroupVersionKind。
最后调用 AddKnownTypeWithName 将 schema.GroupVersionKind 和资源类型写入 Scheme.gvkToType。
执行代码结果如下:
1{
2 map[
3 { __internal Pod}:0xb46180
4 { v1 Pod}:0xb46180
5 { v1 Status}:0xb56d40
6 {extensions v1beta1 DaemonSet}:0xb44e40
7 ]
8 map[
9 0xb44e40:[{extensions v1beta1 DaemonSet}]
10 0xb46180:[{ v1 Pod} { __internal Pod}]
11 0xb56d40:[{ v1 Status}]
12 ]
13 map[
14 0xb56d40:{ v1 Status}
15 ]
16 map[
17 Status:0xb56d40
18 ]
19 map[]
20 map[]
21 0xc000124888
22 map[]
23 [{ v1} {extensions v1beta1}]
24 pkg/runtime/scheme.go:100
25}
26map[Pod:v1.Pod Status:v1.Status]
Kubernetes资源分为外部资源和内部资源。外部资源是对外可访问的,其版本为v1/v1beta1等,内部资源是Kubernetes内部访问的资源,其版本为__internal。
为什么会有内部版本呢?
为了适配版本间的 convert。
试想,如果 v1、v1apha1、v1beta1、v1beta2 要相互转换需要建立十二种转换关系:
1v1 ----> v1alpha1
2v1 ----> v1beta1
3v1 ----> v1beta2
4v1alpha1 ----> v1
5v1alpha1 ----> v1beta1
6v1alpha1 ----> v1beta2
7v1beta1 ----> v1
8v1beta1 ----> v1alpha1
9v1beta1 ----> v1beta2
10v1beta2 ----> v1
11v1beta2 ----> v1alpha1
12v1beta2 ----> v1beta1
可以看到这种转换已经比较复杂了,随着资源增多,这种资源版本之间的转换会越来越复杂且难以维护。Kubernetes 通过引入内部版本 __internl 建立每种资源到 __internal 的转换,从而实现不同资源版本的相互转换。
1v1 ----> __internal
2__internal ----> v1
3v1alpha1 ----> __internal
4__internal ----> v1alpha1
5v1beta1 ----> __internal
6__internal ----> v1beta1
7v1beta2 ----> __internal
8__internal ----> v1beta2
转换示意图如下:

可以看到,相当干净,清爽。
kube-apiserver scheme
第一节介绍了 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"包:
1package app
2
3import (
4 "k8s.io/kubernetes/pkg/api/legacyscheme"
5 "k8s.io/kubernetes/pkg/controlplane"
6)
legacyscheme 包中创建了 Scheme 资源注册表,Codecs 编码器和 ParameterCodec 参数编码器。
1# kubernetes/pkg/api/legacyscheme/scheme.go
2
3package legacyscheme
4
5var (
6 Scheme = runtime.NewScheme()
7
8 Codecs = serializer.NewCodecFactory(Scheme)
9
10 ParameterCodec = runtime.NewParameterCodec(Scheme)
11)
controlplane 包中导入需要注册的资源组:
1# kubernetes/pkg/controlplane/import_known_versions.go
2
3package controlplane
4
5import (
6 _ "k8s.io/kubernetes/pkg/apis/apps/install"
7)
以注册 apps 资源组为例:
1# kubernetes/pkg/apis/apps/install/install.go
2
3package install
4
5import (
6 "k8s.io/apimachinery/pkg/runtime"
7 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
8 "k8s.io/kubernetes/pkg/api/legacyscheme"
9 "k8s.io/kubernetes/pkg/apis/apps"
10 "k8s.io/kubernetes/pkg/apis/apps/v1"
11 "k8s.io/kubernetes/pkg/apis/apps/v1beta1"
12 "k8s.io/kubernetes/pkg/apis/apps/v1beta2"
13)
14
15func init() {
16 Install(legacyscheme.Scheme)
17}
18
19func Install(scheme *runtime.Scheme) {
20 utilruntime.Must(apps.AddToScheme(scheme))
21 utilruntime.Must(v1beta1.AddToScheme(scheme))
22 utilruntime.Must(v1beta2.AddToScheme(scheme))
23 utilruntime.Must(v1.AddToScheme(scheme))
24 utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
25}
资源组中调用 apps.AddToScheme,v1beta1.AddToScheme,v1beta2.AddToScheme 和 v1.AddToScheme 函数将同一资源组不同版本的资源注册到 legacyscheme.Scheme 资源注册表中。
其中 apps.AddToScheme 注册的是内部版本的资源:
1package apps
2
3var (
4 SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
5 AddToScheme = SchemeBuilder.AddToScheme
6)
7
8const GroupName = "apps"
9
10var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
11
12func addKnownTypes(scheme *runtime.Scheme) error {
13 scheme.AddKnownTypes(SchemeGroupVersion,
14 &DaemonSet{},
15 &DaemonSetList{},
16 &Deployment{},
17 &DeploymentList{},
18 &DeploymentRollback{},
19 &autoscaling.Scale{},
20 &StatefulSet{},
21 &StatefulSetList{},
22 &ControllerRevision{},
23 &ControllerRevisionList{},
24 &ReplicaSet{},
25 &ReplicaSetList{},
26 )
27 return nil
28}
v1beta1.AddToScheme,v1beta2.AddToScheme 和 v1.AddToScheme 注册的是外部版本的资源,以 v1.AddToScheme 为例:
1package v1
2
3const GroupName = "apps"
4
5var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
6
7var (
8 SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
9 localSchemeBuilder = &SchemeBuilder
10 AddToScheme = localSchemeBuilder.AddToScheme
11)
12
13func addKnownTypes(scheme *runtime.Scheme) error {
14 scheme.AddKnownTypes(SchemeGroupVersion,
15 &Deployment{},
16 &DeploymentList{},
17 &StatefulSet{},
18 &StatefulSetList{},
19 &DaemonSet{},
20 &DaemonSetList{},
21 &ReplicaSet{},
22 &ReplicaSetList{},
23 &ControllerRevision{},
24 &ControllerRevisionList{},
25 )
26 metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
27 return nil
28}
需要注意的是,内部版本和外部版本注册的资源并不一样。比如 Deployment 资源,外部版本引用的是 kubernetes/vendor/k8s.io/api/apps/v1/types.go 中的资源定义:
1type Deployment struct {
2 metav1.TypeMeta `json:",inline"`
3 metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
4 Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
5 Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
6}
资源声明了 json 和 protobuf tag,以便于外部访问的序列化,反序列化操作。
内部版本引用的是 kubernetes/pkg/apis/apps/types.go 中的资源定义:
1type Deployment struct {
2 metav1.TypeMeta
3 metav1.ObjectMeta
4 Spec DeploymentSpec
5 Status DeploymentStatus
6}
内部资源不需要被外部访问,没有 tag 声明。
到这里,kube-apiserver 关于资源的注册就差不多了。下一篇将进一步介绍 scheme 的 convert,kube-apiserver 中如何使用 scheme 及 scheme 和 object 相互转换等内容。