《kubernetes源码剖析》- k8s核心数据结构


| 阅读 |,阅读约 10 分钟
| 复制链接:

Overview

《kubernetes源码剖析》- k8s核心数据结构

概述

k8s是一个完全以资源为中心的系统,本质是一个资源控制系统。包括:注册、管理、调度资源并维护资源的状态。文章围绕资源展开,介绍k8s核心的数据结构

  • Object:所有资源的父类
  • Group:资源组
  • Version:资源版本
  • Resource:资源
  • Kind:资源种类
  • Codec:编解码器
  • Converter:资源转换器

k8s系统支持多个Group,每个Group支持多个Version,每个Version支持多个Resource,部分资源同时会拥有自己的子资源。完整资源表示:<group>/<version>/<resource>/<subresource>

资源操作的8中方法:Verbs

  • create
  • delete
  • deletecollection
  • get
  • list
  • path
  • update
  • watch

资源的分类

每一个资源至少有两个版本,分别是外部版本和内部版本:

  • 外部版本:用于对外暴露给用户请求的接口所使用的资源对象
  • 内部版本:不对外暴露,仅在api-server内部使用

从另外一个维度,也可以把资源分为:

  • 内置资源:k8s默认自带的资源
  • 自定义资源:用户通过CRD自己实现

查看资源

1# 获取所有的资源组和版本
2kubectl api-versions
3# 获取所有的资源
4kubectl api-resources

资源数据结构

使用的是k8s的1.14版本的代码

 1mkdir $GOPATH/src/k8s.io
 2cd $GOPATH/src/k8s.io
 3
 4# 通过go get获取
 5GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -d -v k8s.io/kubernetes@v1.14.0
 6
 7# 通过git克隆
 8git clone  https://github.com/kubernetes/kubernetes -b release-1.14
 9
10# 编译
11cd kubernetes
12# 指定编译平台、编译所有组件、开启verbose日志、禁止编译优化
13KUBE_BUILD_PLATFORMS=linux/amd64 make all GOFLAGS=-v GOGCFLAGS="-N -l"

资源父类:runtime.Object

runtime.Object是k8s类型系统的基石,所有的资源类型都有一个共同的结构叫做runtime.Object, 它被设计为接口类型,作为资源对象的通用资源类型。资源对象可以转换为runtime.Object对象,也可以反向转换

源码位置:vendor/k8s.io/apimachinery/pkg/runtime/interface.go

 1type Object interface {
 2  // 设置并返回ObjectKind
 3  GetObjectKind() schema.ObjectKind
 4  // 深拷贝当前资源对象
 5  DeepCopyObject() Object
 6}
 7
 8type ObjectKind interface {
 9  SetGroupVersionKind(kind GroupVersionKind)
10  GroupVersionKind() GroupVersionKind
11}

单个资源信息:APIResource

k8s中每个资源都可以使用metav1.APIResource结构进行描述

源码位置:vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go

 1type APIResource struct {
 2  // 资源名称(复数形式)
 3  Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
 4
 5  // 资源名称(单数形式)
 6  SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"`
 7
 8  // 命名空间
 9  Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
10
11  // 资源分组
12  Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"`
13
14  // 资源版本
15  Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"`
16
17  // 资源类型
18  Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
19
20  // 资源可操作的方法列表
21  Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
22
23  // 资源的简称
24  ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
25
26  // 资源所属的类别列表
27  Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
28
29  // 存储版本的hash值,这只是一个试用的功能,将来可能改变或者移除
30  StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"`
31}

所有的资源列表:APIResourceList

1type APIResourceList struct {
2  TypeMeta `json:",inline"`
3  // groupVersion is the group and version this APIResourceList is for.
4  GroupVersion string `json:"groupVersion" protobuf:"bytes,1,opt,name=groupVersion"`
5  // resources contains the name of the resources and if they are namespaced.
6  APIResources []APIResource `json:"resources" protobuf:"bytes,2,rep,name=resources"`
7}

最常用的资源对象:GVR

源码位置:vendor/k8s.io/apimachinery/pkg/runtime/schema/group_version.go

1type GroupVersionResource struct {
2  Group    string
3  Version  string
4  Resource string
5}

资源组:APIGroup

资源组按照不同功能将资源进行划分,资源组的特征:

  • 允许单独启动、禁用资源组,也允许单独启用、禁用资源组内的资源
  • 支持同名资源在不同的资源组内
  • 支持CRD自定义资源扩展

源码位置:vendor/k8s.io/apimachinery/pkg/apis/meta/v1/types.go

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

版本:APIVersions

资源的版本控制。k8s的资源版本分为三种:

  • Alpha:内部测试版本。
  • Beta:修复大部分bug,给特定用户群体使用的版本
  • Stable:稳定运行版本

Alpha版本

  • 该版本不稳定,可能存在很多缺陷和漏洞
  • 官方随时可能放弃支持该版本。
  • 默认处于该版本的功能会被禁用。
  • 版本名称一般为:v1alpha1、v1alpha2、v2alpha1

Beta版本

  • 相对稳定的版本,经过官方和社区很多次测试
  • 功能迭代时,版本会有改变,但是不会被删除
  • 默认该版本的功能会被开启
  • 版本名称一般为:v1beta1、v1beta2、v2beta1

Stable版本

  • 正式发布的版本
  • 基本形成的产品,版本不会被删除
  • 功能全部被开启
  • 版本名称一般为:v1、v2、v3
1type APIVersions struct {
2  TypeMeta `json:",inline"`
3  // 所支持的资源版本列表
4  Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
5
6  ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
7}

资源外部版本与内部版本

资源代码定义在pkg/apis目录下。在k8s中,同一资源对应两个版本,分别是外部版本和内部版本。

  • 外部版本:用于对外暴露给用户请求的接口使用的资源对象,比如通过yaml创建资源对象
  • 内部版本:不对外暴露,用于多资源版本的转换。比如v1beta1 -> v1, 需要经过v1beta1 -> internal -> v1 的转换

代码存放路径:

  • 外部版本:vendor/k8s.io/api/core/v1/types.go
    • 有tag信息:proto、json等
  • 内部版本:pkg/apis/core/types.go
    • 没有tag信息
 1// 外部版本
 2type Pod struct {
 3  metav1.TypeMeta `json:",inline"`
 4  // Standard object's metadata.
 5  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
 6  // +optional
 7  metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
 8
 9  // Specification of the desired behavior of the pod.
10  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
11  // +optional
12  Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
13
14  // Most recently observed status of the pod.
15  // This data may not be up to date.
16  // Populated by the system.
17  // Read-only.
18  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
19  // +optional
20  Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
21}
 1// 内部版本
 2type Pod struct {
 3  metav1.TypeMeta
 4  // +optional
 5  metav1.ObjectMeta
 6
 7  // Spec defines the behavior of a pod.
 8  // +optional
 9  Spec PodSpec
10
11  // Status represents the current information about a pod. This data may not be up
12  // to date.
13  // +optional
14  Status PodStatus
15}

资源源码

代码定义

资源代码定义在pkg/apis目录下,同一资源对应内部版本和外部版本,代码结构并不相同。

  • 内部版本:
    • 支持的资源类型:types.go
    • 资源验证方法:validation.go
    • 注册方法:install.go
  • 外部版本:
    • 转换方法:conversion.go
    • 默认值:default.go
 1├── BUILD
 2├── OWNERS
 3├── doc.go // 定义包注释信息
 4├── fuzzer
 5├── install
 6├── register.go // 定义了资源组、资源版本、资源注册信息
 7├── types.go // 定了了在当前资源组、资源版本下支持的资源信息
 8├── v1       // 定义了资源组下拥有的v1版本资源
 9│   ├── BUILD
10│   ├── conversion.go  // 定义了资源转换函数(默认的),并将转换函数注册到资源注册表中
11│   ├── conversion_test.go
12│   ├── defaults.go    // 定义了资源默认函数
13│   ├── defaults_test.go
14│   ├── doc.go
15│   ├── register.go
16│   ├── zz_generated.conversion.go // 定义了资源转换函数(自动生成的),由代码生成器生成
17│   └── zz_generated.defaults.go   // 定义了资源默认值(自动生成的)
18├── v1beta1  // 定义了资源组下拥有的v1beta1版本资源
19├── v1beta2  // 定义了资源组下拥有的v1beta2版本资源
20├── validation // 定义了资源的验证方法
21└── zz_generated.deepcopy.go // 定义了资源的深拷贝操作,该文件由代码生成器自动生成

资源注册表

K8s的资源注册表,类似于windows的注册表。负责资源类型的统一注册、存储、查询、管理等机制。目前k8s中所有资源类型都已经注册到Scheme资源注册表中,是一个内存型的资源注册表。 注册表支持两种资源类型的注册:

  • 有版本资源类型:
  • 无版本资源类型:早期k8s中的资源类型,现在逐渐被弱化

注册表数据结构

源码位置:vendor/k8s.io/apimachinery/pkg/runtime/scheme.go

 1type Scheme struct {
 2  // 存储GVK与type的映射关系
 3  gvkToType map[schema.GroupVersionKind]reflect.Type
 4
 5  // 存储type与GVK的映射关系,一个type会对应一个或多个gvk
 6  typeToGVK map[reflect.Type][]schema.GroupVersionKind
 7
 8  // 无版本类型与gvk的映射关系
 9  unversionedTypes map[reflect.Type]schema.GroupVersionKind
10
11  // 资源名称与类型的映射关系
12  unversionedKinds map[string]reflect.Type
13
14  ...
15}

注册方法

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

 1
 2func init() {
 3  // legacyscheme.Scheme 是api-server的全局资源注册表
 4  Install(legacyscheme.Scheme)
 5}
 6
 7// Install registers the API group and adds types to a scheme
 8func Install(scheme *runtime.Scheme) {
 9  // 注册内部版本的资源
10  utilruntime.Must(core.AddToScheme(scheme))
11  // 注册外部版本的资源
12  utilruntime.Must(v1.AddToScheme(scheme))
13  // 注册资源组的版本顺序,第一个为首选版本
14  utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion))
15}

资源首选版本

一个资源组下拥有多个资源版本,在一些场景下,如不指定资源版本,则使用该资源的首选版本

Codec编解码器

Codec编解码器将etcd集群中的数据进行编解码操作。

编解码器与序列化的差别:

Serializer:序列化器。序列化:数据 -> 字符串。反序列化:字符串 -> 数据。 Codec:编解码器。可以表示数据的任何格式。serializer可以理解为codec的一种。

源码位置:vendor/k8s.io/apimachinery/pkg/runtime/interfaces.go

 1// 编码
 2type Encoder interface {
 3  Encode(obj Object, w io.Writer) error
 4  Identifier() Identifier
 5}
 6
 7// 解码
 8type Decoder interface {
 9  Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
10}
11
12// 序列化器都实现了Encoder和Decoder,属于编解码器的一种
13type Serializer interface {
14  Encoder
15  Decoder
16}
17
18// 编解码器
19type Codec Serializer

三种序列化器

Coderc编解码器包含3种序列化器。每种都分别实现了编码、解码方法。

  • jsonSerializer
  • yamlSerializer
  • protobufSerializer

Codec实例化

Codec实例化时,通过工厂方法,一次性将三个序列化器都进行实例化。其中:

源码位置:vendor/k8s.io/apimachinery/pkg/runtime/serializer/codec_factory.go

 1// 该函数进行json和yaml两种格式的序列化器初始化
 2func newSerializersForScheme(scheme *runtime.Scheme, mf json.MetaFactory, options CodecFactoryOptions) []serializerType {
 3  jsonSerializer := json.NewSerializerWithOptions(
 4    mf, scheme, scheme,
 5    json.SerializerOptions{Yaml: false, Pretty: false, Strict: options.Strict},
 6  )
 7  jsonSerializerType := serializerType{
 8    AcceptContentTypes: []string{runtime.ContentTypeJSON},
 9    ContentType:        runtime.ContentTypeJSON,
10    FileExtensions:     []string{"json"},
11    EncodesAsText:      true,
12    Serializer:         jsonSerializer,
13
14    Framer:           json.Framer,
15    StreamSerializer: jsonSerializer,
16  }
17  if options.Pretty {
18    jsonSerializerType.PrettySerializer = json.NewSerializerWithOptions(
19      mf, scheme, scheme,
20      json.SerializerOptions{Yaml: false, Pretty: true, Strict: options.Strict},
21    )
22  }
23
24  yamlSerializer := json.NewSerializerWithOptions(
25    mf, scheme, scheme,
26    json.SerializerOptions{Yaml: true, Pretty: false, Strict: options.Strict},
27  )
28  protoSerializer := protobuf.NewSerializer(scheme, scheme)
29  protoRawSerializer := protobuf.NewRawSerializer(scheme, scheme)
30
31  serializers := []serializerType{
32    jsonSerializerType,
33    {
34      AcceptContentTypes: []string{runtime.ContentTypeYAML},
35      ContentType:        runtime.ContentTypeYAML,
36      FileExtensions:     []string{"yaml"},
37      EncodesAsText:      true,
38      Serializer:         yamlSerializer,
39    },
40    {
41      AcceptContentTypes: []string{runtime.ContentTypeProtobuf},
42      ContentType:        runtime.ContentTypeProtobuf,
43      FileExtensions:     []string{"pb"},
44      Serializer:         protoSerializer,
45
46      Framer:           protobuf.LengthDelimitedFramer,
47      StreamSerializer: protoRawSerializer,
48    },
49  }
50
51  for _, fn := range serializerExtensions {
52    if serializer, ok := fn(scheme); ok {
53      serializers = append(serializers, serializer)
54    }
55  }
56  return serializers
57}

资源版本转换器

k8s中,同一资源拥有多个资源版本,允许同一资源的不同版本进行转换。Conveter资源版本转换器主要用于解决多资源版本转换问题。当版本越来越多,互相转换会成指数增长,为了解决这个问题,k8s通过内部版本实现资源多版本的统一转换。

转换器数据结构

主要存放转换函数

源码位置:vendor/k8s.io/apimachinery/pkg/conversion/converter.go

 1// 转换函数结构
 2// a:转换源 b:转换目标 scope处理递归调用
 3type ConversionFunc func(a, b interface{}, scope Scope) error
 4
 5type ConversionFuncs struct {
 6  untyped map[typePair]ConversionFunc
 7}
 8
 9type Converter struct {
10  // 默认转换函数
11  conversionFuncs          ConversionFuncs
12  // 自动生成的转换函数
13  generatedConversionFuncs ConversionFuncs
14
15  // 忽略转换的字段
16  ignoredConversions        map[typePair]struct{}
17  ignoredUntypedConversions map[typePair]struct{}
18
19  // This is a map from a source field type and name, to a list of destination
20  // field type and name.
21  structFieldDests map[typeNamePair][]typeNamePair
22
23  // Allows for the opposite lookup of structFieldDests. So that SourceFromDest
24  // copy flag also works. So this is a map of destination field name, to potential
25  // source field name and type to look for.
26  structFieldSources map[typeNamePair][]typeNamePair
27
28  // Map from an input type to a function which can apply a key name mapping
29  inputFieldMappingFuncs map[reflect.Type]FieldMappingFunc
30
31  // Map from an input type to a set of default conversion flags.
32  inputDefaultFlags map[reflect.Type]FieldMatchingFlags
33
34  // If non-nil, will be called to print helpful debugging info. Quite verbose.
35  Debug DebugLogger
36
37  // 转换过程中获取资源种类名称
38  nameFunc func(t reflect.Type) string
39}

注册转换函数

Converter转换函数需要注册才能在k8s中使用,目前k8s支持5个注册转换函数。

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

资源版本转换流程

  1. 实例化空的Scheme资源注册,并将不同版本的资源(包括内部版本)注册到资源注册表中
  2. 实例化源资源版本,并调用scheme.ConvertToVersion转换为内部版本的资源对象
  3. 同样调用scheme.ConvertToVersion将内部资源对象转换为目标资源对象