0%

kubernetes核心数据结构

Kubernetes是一个完全以资源为中心的系统,资源是Kubernetes中最重要的概念,Kubernetes的生态系统都围绕着资源进行运作。

Kubernetes将资源再次分组和版本化,形成Group、Version、Resource。

Group被称为资源组,在Kubernetes API Server中也称其为APIGroup。

Version被称为资源版本,在Kubernetes API Server中也可以被称为APIVersions。

Resource被称为资源,在Kubernetes API Server中也可称其为APIResource。

Kind被称为资源种类,描述Resource的种类,与Resource为同一个级别。

Kubernetes系统支持多个Group,每个Group支持多个Version,每个Version支持多个Resource,其中部分资源同时会拥有自己的子资源(SubResource),例如Deployment资源拥有Status子资源。

资源组、资源版本、资源、子资源的完整表现形式为<group>/<version>/<resource>/<subresource>。常用的Deployment资源的完整表现形式就是apps/v1/deployments/status。

每一个资源都拥有一定数量的资源操作方法,用于Etcd集群存储中对资源对象的增删改查操作。Kubernetes系统支持8种资源操作方法,分别是create、delete、deletecollection、get、list、patch、update、watch。每一个资源都至少有两个版本,分别为外部版本和内部版本。开发者也可以通过CRD实现自定义资源,允许用户将自己定义的资源添加到Kubernetes系统当中。

Group

Group(资源组)在Kubernetes API Server中可以称其为APIGroup。Kubernetes系统中定义了许多资源组,并按照不同的功能进行划分。资源组的数据结构代码如下

1
2
3
4
5
6
7
8
9
10
type APIGroup struct {
TypeMeta `json:",inline"`
// 资源组名称
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// 资源组下所支持的资源版本
Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"`
// 首选版本
PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty" protobuf:"bytes,3,opt,name=preferredVersion"`
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
}

在Kubernetes系统中,支持两类资源组,分别是拥有组名的资源组和没有组名的资源组。拥有组名的资源组其表现形式为<group>/<version>/<resource>,例如apps/v1/deployments。没有组名的资源组又被称为核心资源组,其表现形式为/<version>/<resource>。

Version

每当资源的新版本发布时,都需要设置版本号,目的是为了在兼容旧版本的同时不断升级新版本。Kubernetes的资源版本控制可分为3种,分别是Alpha、Beta和Stable。

Alpha版本为内部测试版本,用于Kubernetes开发者内部测试,该版本是不稳定的,可能存在很多缺陷和漏洞。Beta版本为相对稳定的版本,经过社区和官方的多次讨论。Stable版本为正式发布的版本,Stable版本基本形成了产品,不会被删除。

资源版本数据结构代码如下:

1
2
3
4
5
6
type APIVersions struct {
TypeMeta `json:",inline"`
// 所支持的资源版本列表
Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
}

Resource

在Kubernetes体系架构中,资源是Kubernetes最重要的概念,Kubernetes本质上是一个资源控制系统。

一个资源被实例化以后会表达为一个资源对象(Resource Object)。在Kubernetes系统中定义并运行着各式各样的资源对象,所有的资源对象都是Entity(实体),Kubernetes用这些Entity来表示当前状态。Kubernetes目前支持持久化实体和短暂性实体两种实体。

持久性实体在资源创建后会持久确保该对象存在,例如Deployment,短暂性实体在资源对象创建后,如果出现故障或者调度失败,不会重新创建该对象,例如Pod。资源数据结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
type APIResource struct {
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"`
Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"`
Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"`
Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"`
}

在Kubernetes中,同一资源对应两个版本号,分别为内部和外部版本号。External Object(外部版本资源对象)用于对外暴露给用户请求的接口所使用的资源对象,Internal Object(内部版本使用对象)不对外暴露,仅在Kubernetes API Server内部使用,用于多资源版本的转换。

资源的内部版本和外部版本是需要相互进行转换的,用于转换的函数需要事先初始化到资源注册表中,多个外部版本之间的资源进行相互转换需要通过内部版本进行中转,这也是Kubernetes实现多资源版本转换的关键。

资源的内部版本定义了所支持的资源类型(types.go)、资源验证方法(validation.go)、资源注册至资源注册表的方法(install.go)等。资源的外部版本定义了资源的转换方法(conversion.go),资源的默认值(defaults.go)等。

以Deployment资源为例,内部版本定义在pkg/apis/apps目录下,其中register.go代码文件定义了所属的资源组和资源版本,内部版本通过runtime.APIVersionInternal标识。

1
2
3
4
// GroupName is the group name use in this package
const GroupName = "apps"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}

每一个Kubernetes资源目录,都通过type.go代码文件定义当前资源组/资源版本下所支持的资源类型。

在每一个Kubernetes资源组目录中,都有一个install.go代码文件,负责将资源信息注册到资源注册表中。

一个资源组下拥有多个资源版本,例如apps资源组用于v1,v1beta1,v1beta2等资源版本。当使用apps资源组下的Deployment资源时,在一些场景下,如果不指定资源版本,则使用该资源的首选版本。以apps资源组为例,注册资源时会注册多个资源版本,

在Kubernetes系统中,针对每一个资源都有一定的操作方法,不同的资源对象拥有不同的操作方法,例如对于Pod资源,可以执行create、delete、get等操作。资源操作方法可以通过Verbs数据结构进行描述,代码如下

1
2
3
4
5
type Verbs []string

func (vs Verbs) String() string {
return fmt.Sprintf("%v", []string(vs))
}

Kubernetes系统拥有强大的高扩展功能,其中自定义资源是一种常见的扩展方式,可以将自己定义的资源添加到Kubernetes当中。

无论内置资源还是自定义资源,都通过资源对象描述文件进行定义。一个资源对象需要用5个字段来进行描述,分别是Group/Version、Kind、MetaData、Spec、Status。这些字段定义在YAML或JSON文件中。Kubernetes系统中的所有资源对象都可以通过YAML或者JSON来进行定义。

runtime.Object

Runtime(运行时)一般指程序或者语言核心库的实现。runtime.Object是Kubernetes类型系统的基石,Kubernetes上所有的资源对象实际上就是一种Go语言的Struct类型,都有一个共同的结构叫做runtime.Object。其结构如下

1
2
3
4
type Object interface {
GetObjectKind() schema.ObjectKind
DeepCopyObject() Object
}

其提供了两个方法,分别是GetObjectKind用于设置并返回GroupVersionKind和DeepCopyObject用于深复制当前资源对象。

1
2
3
4
5
6
7
8
type ObjectKind interface {
// SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil
// should clear the current setting.
SetGroupVersionKind(kind GroupVersionKind)
// GroupVersionKind returns the stored group, version, and kind of an object, or an empty struct
// if the object does not expose or provide these fields.
GroupVersionKind() GroupVersionKind
}

Kubernetes中任意资源对象都可以通过runtime.Object来存储它的类型并且进行深拷贝操作。实例代码将Pod资源对象转换为了runtime.Object之后再次转换为资源对象,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/apis/core"
"reflect"
)
func main() {
pod := &core.Pod{
TypeMeta: v1.TypeMeta{Kind: "Pod"},
ObjectMeta: v1.ObjectMeta{Labels: map[string]string{"name": "foo"}},
}
pod.DeepCopyObject()
pod.GroupVersionKind()
obj := runtime.Object(pod)
pod2, ok := obj.(*core.Pod)
if !ok {
panic("unexpected")
}
if !reflect.DeepEqual(pod, pod2) {
panic("unexpected")
}
}

Scheme资源注册表

Kubernetes系统拥有众多的资源,每一个资源就是一个资源类型,这些资源需要一个统一的注册、存储、查询、管理机制。Kubernetes系统中所有的资源类型都需要注册到Scheme资源注册表中,其是一个内存型的资源注册表,拥有以下特点:

  1. 支持注册多种资源类型,包括内部和外部版本
  2. 支持多版本的转换机制
  3. 支持不同资源的序列化反序列化机制

Scheme资源注册表数据结构主要由4个map结构组成,分别是gvkToType、typeToGVK、unversionedTypes、unversionedKinds,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Scheme struct {
// 存储GVK与type的映射关系
gvkToType map[schema.GroupVersionKind]reflect.Type
// 存储Type与GVK的映射关系
typeToGVK map[reflect.Type][]schema.GroupVersionKind
// 存储UnversionedType与GVK的映射关系
unversionedTypes map[reflect.Type]schema.GroupVersionKind
// 存储Kind与UnversionedType的映射关系
unversionedKinds map[string]reflect.Type
fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc
defaulterFuncs map[reflect.Type]func(interface{})
converter *conversion.Converter
versionPriority map[string][]string
observedVersions []schema.GroupVersion
schemeName string
}

在Scheme资源注册表中,不同的资源类型使用的注册方法不同

  • scheme.AddUnversionedTypes:注册UnversionedType资源类型。

  • scheme.AddKnownTypes:注册KnownTypes资源类型

  • scheme.AddKnownTypeWithName:注册KnownType资源类型,须指定资源的Kind资源种类名称

AddKnownTypes方法在注册资源类型时,无需指定Kind名称,而是通过reflect机制来获取资源类型的名称作为种类名称。

1
2
3
4
5
6
7
8
9
10
11
func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
s.addObservedVersion(gv)
for _, obj := range types {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Ptr {
panic("All types must be pointers to structs.")
}
t = t.Elem()
s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
}
}

Codec编解码器

Codec编解码器接口定义如下

1
2
3
4
5
6
7
8
9
10
11
12
type Encoder interface {
Encode(obj Object, w io.Writer) error
Identifier() Identifier
}
type Decoder interface {
Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
}
type Serializer interface {
Encoder
Decoder
}
type Codec Serializer

kubernetes目前支持3种主要的序列化器,分别是jsonSerializer、yamlSerializer、protobufSerializer。

编解码器通过NewCodecFactory函数实例化,在实例化过程中会将上述三种序列化器全部实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
func NewCodecFactory(scheme *runtime.Scheme, mutators ...CodecFactoryOptionsMutator) CodecFactory {
options := CodecFactoryOptions{Pretty: true}
for _, fn := range mutators {
fn(&options)
}

serializers := newSerializersForScheme(scheme, json.DefaultMetaFactory, options)
return newCodecFactory(scheme, serializers)
}

func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, options CodecFactoryOptions) []serializerType {
jsonSerializer := json.NewSerializerWithOptions(
mf, scheme, scheme,
json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict},
)
jsonSerializerType := serializerType{
AcceptContentTypes: []string{runtime.ContentTypeJSON},
ContentType: runtime.ContentTypeJSON,
FileExtensions: []string{"json"},
EncodesAsText: true,
Serializer: jsonSerializer,

Framer: json.Framer,
StreamSerializer: jsonSerializer,
}
if options.Pretty {
jsonSerializerType.PrettySerializer = json.NewSerializerWithOptions(
mf, scheme, scheme,
json.SerializerOptions{Yaml: false, Pretty: true, Strict: options.Strict},
)
}

yamlSerializer := json.NewSerializerWithOptions(
mf, scheme, scheme,
json.SerializerOptions{Yaml: true, Pretty: false, Strict: options.Strict},
)
protoSerializer := protobuf.NewSerializer(scheme, scheme)
protoRawSerializer := protobuf.NewRawSerializer(scheme, scheme)

serializers := []serializerType{
jsonSerializerType,
{
AcceptContentTypes: []string{runtime.ContentTypeYAML},
ContentType: runtime.ContentTypeYAML,
FileExtensions: []string{"yaml"},
EncodesAsText: true,
Serializer: yamlSerializer,
},
{
AcceptContentTypes: []string{runtime.ContentTypeProtobuf},
ContentType: runtime.ContentTypeProtobuf,
FileExtensions: []string{"pb"},
Serializer: protoSerializer,

Framer: protobuf.LengthDelimitedFramer,
StreamSerializer: protoRawSerializer,
},
}

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

Converter资源版本转换器

在Kubernetes中,同一资源拥有多个资源版本,Kubernetes系统允许同一资源的不同资源版本进行转换。Converter资源版本转换器主要用于解决多资源版本的转换问题,Kubernetes系统中的一个资源支持多个资源版本,Kubernetes通过内部版本机制实现资源的版本转换。

Converter转换器数据结构主要存放转换函数,转换器数据结构代码如下:

1
2
3
4
5
6
7
8
9
10
11
type Converter struct {
// 默认转换函数,一般定义在资源目录下的conversion.go代码文件中
conversionFuncs ConversionFuncs
// 自动生成的转换函数
generatedConversionFuncs ConversionFuncs
// 若资源对象注册到此字段,忽略此资源对象的转换操作
ignoredConversions map[typePair]struct{}
ignoredUntypedConversions map[typePair]struct{}
// 在转换过程中用于获取资源对象种类的名称
nameFunc func(t reflect.Type) string
}

Converter转换函数需要通过注册才能在Kubernetes中进行使用,需要通过以下的注册转换函数进行注册

  • scheme.AddIgnoredConversionType:注册忽略的资源类型
  • scheme.AddConversionFuncs:注册多个Conversion Func转换函数
  • scheme.AddConversionFunc:注册单个Conversion Func转换函数
  • scheme.AddGeneratedConversionFunc:注册自动生成的转换函数
  • scheme.AddFieldLabelConversionFunc:注册字段标签的转换函数

Converter转换器在Kubernetes中使用十分广泛,例如Deployment资源对象,起初使用v1beta1资源版本,后来推出稳定的v1版本,因此会将v1beta1版本转换为v1版本,Converter转换器通过Converter函数进行资源版本的转换。