【k8s基础篇】k8s scheme4 之资源数据结构与资源注册

参考

Scheme 源码解析

  • schema 是 kubernetes 资源管理的核心数据结构。由以前文章我们了解到 kubernetes 会将其管理的资源划分为 group/version/kind 的概念,scheme 可以利用进行 GVK 进行一下操作:
    • 可以将资源在内部版本和其他版本中相互转化
    • 可以序列化和反序列化的过程中识别资源类型,创建资源对象,设置默认值等等。
    • 这些 group/version/kind 和资源 model 的对应关系,资源 model 的默认值函数,不同版本之间相互转化的函数等等全部由 schema 维护。可以说 schema 是组织 kubernetes 资源的核心,其数据结构如下:

1 | scheme 的数据结构

9e16267d3b090374aa20bb003da1497e.png

// kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
type Scheme struct {
  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{})
  converter *conversion.Converter
  versionPriority map[string][]string
  observedVersions []schema.GroupVersion
  schemeName string
}

GVK :group、version、kind,GVK 在编码中也是结构体,但只存储 string 信息,对于 Pod 就是 core 、 v1、 pod 这些 string 信息

model 对象类型:就是数据结构 struct,比如 Pod 的数据结构等

版本:比如某种资源存在多个版本,比如 Foo 资源存在 v1、v2、v3 等三个版本,但其实在编码中,存在个内部版本 _internal ,该三个版本都是与内部版本互相转换,减少转换书写的复杂度(比如没有内部版本,则需要写 v1 和 v2,v1 和 v3,v2 和 v3,版本多的话,需要书写更多,有内部版本存在,只需要些 v1、v2、v3 分别和 内部版本的转换即可,v1转v3,就是v1–内部版本–v3)

从上面的图解以及源码来看 schema 结构主要有以下关键点:

  • 包含 map 类型的 gvkToType 属性来维护 GVK 和 model 对象类型的关系。
  • 包含 map 类型的 typeToGVK 属性来维护 model 对象类型和 GVK 的关系。
  • 包含 map 类型的 defaulterFuncs 属性维护 model 对象类型和默认值函数的关系
  • 包含 conversion.Converter 指针类型的 converter 属性实现资源不同版本的转化
  • map 类型的 fieldLabelConversionFuncs 属性维护 GVK label 标签转换函数的关系。
  • 包含 string 类型的 schemaName 属性用来定义 schema 的名称。

2 | scheme 实现的接口

由上面的 schema 的数据结构看,它是一个 struct 的类型,另外它还实现了一些接口,使得 schema 可以创建资源对象,给资源对象赋默认值,识别资源对象类型,完成资源对象本版之间的转换,完成资源的 label 标签转化等。其接口实现如下图:

519c1bea9e5d562712038c2e9b70aaa4.png

接口实现的函数核心代码定义如下:

// kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
func (s *Scheme) Convert(in, out interface{}, context interface{}) error{...}
func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error){...}
func (s *Scheme) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error){...}
 
func (s *Scheme) Default(src Object){...}
 
func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) {...}
 
func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error){...}
func (s *Scheme) Recognizes(gvk schema.GroupVersionKind) bool{...}

从上面的图解以及源码来看 schema 实现接口主要有以下关键点:

  • 实现 runtime.ObjectCreater 接口定义的方法完成资源的创建

  • 实现 runtime.ObjectDefaulter 接口定义的方法完成资源的赋默认值

  • 实现 runtime.ObjectConvert 接口定义的方法完成资源不同版本之间的相互转换

  • 实现 runtime.ObjecTyper 接口定义的方法完成资源的类型识别

3 | 资源注册到 scheme

上两节主要进行介绍了 kubernetes schema 这个重要概念:

  • 从数据结构角度上看,其中包括了它是如何来维护 group/version/kind 和资源 model 的对应关系,资源 model 和默认值函数的对应关系,不同资源版本之间相互转化函数的对应关系等等。
  • 从实现接口角度看, schema 实现了一系列接口,从而具备了创建资源对象,给资源对象赋默认值,识别资源对象类型,完成资源对象本版之间的转换,完成资源的 label 标签转化等功能。

在本篇文章里, 我们主要介绍不同版本的资源到 schema 对象中的注册。

3.1 | SchemeBuilder对象

schemabuilder 对象用以完成资源在 schema 中的注册,其图解和源码如下:

bf40adaeafa90bf49ead31233563a962.png

由下面代码可以看出:

  • schemabuilder 对象本质是一个函数数组集合,其中的函数入参为 schema 类型。

  • schemabuilder 对象含有 Register() 这个方法,其本质是向函数数组集合中添加一个入参为 schema 类型的函数

  • schemabuilder 对象有 AddToSchema() 这个方法,它的入参为 schema 对象,其本质是把该 schema 对象传入函数数组集合中的每一个函数中,然后分别运行

// staging/src/k8s.io/apimachinery/pkg/runtime/scheme_builder.go
 
// schemabuilder 对象本质是一个函数数组集合,其中的函数入参为 schema 类型 
type SchemeBuilder []func(*Scheme) error

//  AddToScheme 函数就是将自己包含的处理函数依次应用到 Scheme 上,
// 给人的感觉好像是过滤器似的,让 Scheme 分别被定义好的函数们给处理一遍,
func (sb *SchemeBuilder) AddToScheme(s *Scheme) error {
  for _, f := range *sb {
    if err := f(s); err != nil {
      return err
    }
  }
  return nil
} 
 
func (sb *SchemeBuilder) Register(funcs ...func(*Scheme) error) {
  for _, f := range funcs {
    *sb = append(*sb, f)
  }
}
  
func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder {
  var sb SchemeBuilder
  sb.Register(funcs...)
  return sb
}

3.2 | 资源的外部版本注册

这里我们以 apps/v1beta1 为例子,介绍该组下的 v1beta1 版本的资源是如何注册到 schema 中的,其图解如下:

2d0100f31282f06ee6fb531d69cf91c8.png

由图解我们发现对于外部资源版本的注册包括资源 model 类型的注册,资源的初始化函数(即默认值函数)的注册,资源的 label 转换函数的注册,和内部版本相互转换函数的注册(下面详细介绍)。

  • 资源 model 类型的注册源码如下:
    • 从源代码之中,我们会发现,在源码文件 k8s.io/api/apps/v1beta1/register.go 中去创建了 schemebuilder 这个对象,
    • 并且设置了组为 apps, 设置了版本为 v1beta1
    • 然后把属于apps/v1beta1 中的所有类型的资源 model 进行注册,例如我们非常熟悉的 deployment, statsfulset 等资源。
// staging/src/k8s.io/api/apps/v1beta1/register.go
 
 
const GroupName = "apps"
 
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} // 设置组 版本
 
var (
  SchemeBuilder      = runtime.NewSchemeBuilder(addKnownTypes)
  localSchemeBuilder = &SchemeBuilder
  AddToScheme        = localSchemeBuilder.AddToScheme
)
 
func addKnownTypes(scheme *runtime.Scheme) error {
  scheme.AddKnownTypes(SchemeGroupVersion,
    &Deployment{},
    &DeploymentList{},
    &DeploymentRollback{},
    &Scale{},
    &StatefulSet{},
    &StatefulSetList{},
    &ControllerRevision{},
    &ControllerRevisionList{},
  )
  metav1.AddToGroupVersion(scheme, SchemeGroupVersion) // 进行设置
  return nil
}
 
func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder {
  var sb SchemeBuilder
  sb.Register(funcs...)
  return sb
}
  • 资源默认值初始化函数和 label 转换函数的注册的源码如下:
    • 在源码文件 pkg/apis/apps/v1beta1/register.go 中来引用了上面介绍的 schemabuilder 对象
    • 然后在这个对象中添加 addDefaultingFuncs 函数作为资源的初始化函数,并添加 addConversionFuncs 这个函数作为资源的 label 转换函数
// pkg/apis/apps/v1beta1/register.go
 
 
var (
  localSchemeBuilder = &appsv1beta1.SchemeBuilder
  AddToScheme        = localSchemeBuilder.AddToScheme
)
 
func init() {
  localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs)
}
 
// pkg/apis/apps/v1beta1/defaults.go
// 设置默认值
func addDefaultingFuncs(scheme *runtime.Scheme) error {
  return RegisterDefaults(scheme)
}
  
// pkg/apis/apps/v1beta1/conversion.go
// label 转换函数  不知道有什么作用????
func addConversionFuncs(scheme *runtime.Scheme) error {
  // Add field label conversions for kinds having selectable nothing but ObjectMeta fields.
  if err := scheme.AddFieldLabelConversionFunc(SchemeGroupVersion.WithKind("StatefulSet"),
    func(label, value string) (string, string, error) {
      switch label {
      case "metadata.name", "metadata.namespace", "status.successful":
        return label, value, nil
      default:
        return "", "", fmt.Errorf("field label not supported for appsv1beta1.StatefulSet: %s", label)
      }
    }); err != nil {
    return err
  } 
  return nil
}
  • 资源外部版本和内部版本相互转换函数注册的源码如下:
    • pkg/apis/apps/v1beta1/zz_generated.conversion.go 中引用了上面介绍的 schemabuilder 对象,然后在对象中添加 RegisterConversions 函数作为内外部本版转换函数,在该函数中定义了各个资源在当前版本和内部版本如何转换。
// pkg/apis/apps/v1beta1/zz_generated.conversion.go
 
func init() {
  localSchemeBuilder.Register(RegisterConversions)
}
// 外部版本和内部版本转换函数  个人理解: *v1beta1.Deployment 为外部版本,*apps.Deployment为内部版本  
func RegisterConversions(s *runtime.Scheme) error {
  ......
  if err := s.AddGeneratedConversionFunc((*v1beta1.Deployment)(nil), (*apps.Deployment)(nil), func(a, b interface{}, scope conversion.Scope) error {
    return Convert_v1beta1_Deployment_To_apps_Deployment(a.(*v1beta1.Deployment), b.(*apps.Deployment), scope)
  }); err != nil {
    return err
  }
  if err := s.AddGeneratedConversionFunc((*apps.Deployment)(nil), (*v1beta1.Deployment)(nil), func(a, b interface{}, scope conversion.Scope) error {
    return Convert_apps_Deployment_To_v1beta1_Deployment(a.(*apps.Deployment), b.(*v1beta1.Deployment), scope)
  }); err != nil {
    return err
  }
  ......
}

3.3 | 资源的内部版本注册

这里我们同样以 apps 组做为例子,介绍该组下内部版本资源是如何注册到 schema 中的,其图解如下:

5a5e299b308cd978a6f4329186fba0b1.png

由图解发现对于内部资源版本注册只包括资源 model 类型的注册,其源码如下:

  • 在源码文件 pkg/apis/apps/register.go 中进行了 schemebuilder 对象的创建,设置了组为 apps, 设置版本为内部版本,并把属于内部版本中的所有类型的资源 model 进行注册,例如我们非常熟悉的 deployment 资源, statsfulset 资源等等。
// pkg/apis/apps/register.go
 
 
const GroupName = "apps"
  
// 设置版本为内部版本 APIVersionInternal
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
 
var (
  SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes).
  AddToScheme = SchemeBuilder.AddToScheme
)
  
func addKnownTypes(scheme *runtime.Scheme) error {
  scheme.AddKnownTypes(SchemeGroupVersion,
    &DaemonSet{},
    &DaemonSetList{},
    &Deployment{},
    &DeploymentList{},
    &DeploymentRollback{},
    &autoscaling.Scale{},
    &StatefulSet{},
    &StatefulSetList{},
    &ControllerRevision{},
    &ControllerRevisionList{},
    &ReplicaSet{},
    &ReplicaSetList{},
  )
  return nil
}
 
 
// k8s.io/apimachinery/pkg/runtime/interfaces.go
 
const (
  APIVersionInternal = "__internal"
)

3.4 | 资源的内外部版本注册的驱动

这里我们同样以 apps 组作为例子从源码角度看,驱动整个内部版本资源和外部版本资源的注册如下:

// pkg/apis/apps/install/install.go
 
 
func init() {
  Install(legacyscheme.Scheme) // 由 legacyscheme.Scheme 操作来得到一个 schema 对象
}

// 把 app 组下的所有版本的资源都进行注册,包括内部版本,以及 v1/v1beta1/v1beta2 等所有外部版本
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))
}

在源代码文件 pkg/apis/apps/install/install.go 中,由 legacyscheme.Scheme 操作来得到一个 schema 对象,然后把 app 组下的所有版本的资源都进行注册,包括内部版本,以及 v1/v1beta1/v1beta2 等所有外部版本,如下图解展示了各个组下不同版本资源到 schema 中的注册。

cbb112455a5f56cc8a89cc4bc99418ac.png

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值