【k8s深入理解之 Scheme 补充-1】理解 Scheme 中资源的注册以及 GVK 和 go 结构体的映射

发布于:2024-12-07 ⋅ 阅读:(130) ⋅ 点赞:(0)

代码阅读引出的问题 —— 附录是详解

  • addKnownTypes、AddKnownTypeWithName、AddToGroupVersion 都是什么?

  • 了解资源注册流程

    • 下面代码是以 apps/v1 为例
    • addKnownTypes 注册多个资源,实际上是调用 AddKnownTypeWithName 进行单个资源注册
    • AddKnownTypeWithName 会利用反射,获取 go 结构体名称作为 Kind,建立 GVK 和 go 结构体的映射
    • addKnownTypes 一般还会包含 AddToGroupVersion,注册一些无版本资源(可以理解为辅助资源,如Status 等)以及相关的转换、默认值填充函数等
    • 下面的附录是详细介绍
// 路径-1 mod/k8s.io/api@v0.29.0/apps/v1/register.go

// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}

// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
  // 注册多个资源类型,跳转 路径-2
	scheme.AddKnownTypes(SchemeGroupVersion,
		&Deployment{},
		&DeploymentList{},
		&StatefulSet{},
		&StatefulSetList{},
		&DaemonSet{},
		&DaemonSetList{},
		&ReplicaSet{},
		&ReplicaSetList{},
		&ControllerRevision{},
		&ControllerRevisionList{},
	)
  // 注册 GroupVersion,跳转 路径-3
	metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
	return nil
}

// 路径-2  mod/k8s.io/apimachinery@v0.29.0/pkg/runtime/scheme.go
// AddKnownTypes registers all types passed in 'types' as being members of version 'version'.
// All objects passed to types should be pointers to structs. The name that go reports for
// the struct becomes the "kind" field when encoding. Version may not be empty - use the
// APIVersionInternal constant if you have a type that does not have a formal version.
func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
	s.addObservedVersion(gv)
	for _, obj := range types {
    // 获取 obj 的类型,此处可能是指针类型 如 *Deployment
		t := reflect.TypeOf(obj)
		if t.Kind() != reflect.Pointer {
			panic("All types must be pointers to structs.")
		}
    // 获取指针指向的类型,如 Deployment 结构体
		t = t.Elem()
    // .Name 返回的是,结构体名称,也就是 Deployment —— 所以一般来说结构体名称就是 GVK 中的 Kind
		s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
	}
}


// 路径-3 mod/k8s.io/apimachinery@v0.29.0/pkg/apis/meta/v1/register.go
// 注册一些 无版本资源(可以理解为 稳定不易变化的版本,公共资源,其他 Group 都可以使用,主要用于辅助记录和查询作用 如 Status)
// 并注册一些,这些无版本资源或一些基础资源 的转换函数和默认值填充函数
// AddToGroupVersion registers common meta types into schemas.
func AddToGroupVersion(scheme *runtime.Scheme, groupVersion schema.GroupVersion) {
	scheme.AddKnownTypeWithName(groupVersion.WithKind(WatchEventKind), &WatchEvent{})
	scheme.AddKnownTypeWithName(
		schema.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal}.WithKind(WatchEventKind),
		&InternalEvent{},
	)
	// Supports legacy code paths, most callers should use metav1.ParameterCodec for now
	scheme.AddKnownTypes(groupVersion, optionsTypes...)
	// Register Unversioned types under their own special group
	scheme.AddUnversionedTypes(Unversioned,
		&Status{},
		&APIVersions{},
		&APIGroupList{},
		&APIGroup{},
		&APIResourceList{},
	)

	// register manually. This usually goes through the SchemeBuilder, which we cannot use here.
	utilruntime.Must(RegisterConversions(scheme))
	utilruntime.Must(RegisterDefaults(scheme))
}

附录1 | AddKnownTypes 和 AddKnownTypeWithName 和 AddToGroupVersion 有什么关系

在 Kubernetes 的代码中,AddKnownTypesAddKnownTypeWithNameAddToGroupVersion 是与 runtime.Scheme 相关的函数,主要用于注册自定义资源或类型,但它们关注的粒度和作用不同。


1. AddKnownTypes

  • 功能:批量注册某个 GroupVersion 下的资源类型到 runtime.Scheme
  • 内部实现:会将所有提供的类型注册到 GroupVersionKind (GVK) 中,其中 Kind 默认是类型的结构体名。
  • 调用场景:一般用于 API 类型的注册,比如注册 CRD 的 Go 类型。
  • 特点
    • 简化了多个类型的注册。
    • 默认使用结构体名作为类型的 Kind
  • 底层关联AddKnownTypes 内部会调用 AddKnownTypeWithName 来完成注册。

2. AddKnownTypeWithName

  • 功能:注册单个 runtime.Object 类型到特定的 GroupVersionKind
  • 特点
    • 允许为类型指定自定义的 Kind 名称。
    • 是更底层的实现,提供更精细的控制。
  • 调用场景:需要为单个类型注册自定义的 Kind 时。
  • 内部关联AddKnownTypes 只是对多个 AddKnownTypeWithName 调用的封装。

3. AddToGroupVersion

  • 功能:为一个 GroupVersion 添加元信息(GroupVersion 本身的注册),并且是类型注册的入口点。

  • 典型实现

    func AddToGroupVersion(scheme *runtime.Scheme, gv schema.GroupVersion) error {
        return scheme.AddGeneratedDeepCopyFuncs(
            deepCopyFuncs...
        )
    }
    
  • 作用

    • 用于声明某个 GroupVersion
    • 通常用于为 API 组注册基础信息(比如默认版本和元数据支持)。
  • 调用场景

    • 用于 API 组的初始配置,确保该 GroupVersion 被声明并可以被进一步扩展(如通过 AddKnownTypes 注册资源类型)。

三者之间的关系

函数名 粒度 主要功能 调用链关系
AddToGroupVersion GroupVersion 级别 注册 GroupVersion 的元信息,为资源类型的注册提供入口点。 一般最先调用(但也不是必须在第一位),用于初始化 GroupVersion
AddKnownTypes 批量类型级别 注册某个 GroupVersion 下的多个类型资源到 runtime.Scheme,简化注册流程。 内部调用 AddKnownTypeWithName
AddKnownTypeWithName 单个类型级别 注册单个类型资源到 runtime.Scheme,允许为资源指定自定义的 Kind 名称。 最底层的注册实现

示例:三者配合

假设你有一个自定义 API 组 example.com/v1,其中包含一种资源 MyCustomResource

  1. 使用 AddToGroupVersion

    • 声明

      example.com/v1
      

      的元信息:

      func AddToScheme(scheme *runtime.Scheme) error {
          gv := schema.GroupVersion{Group: "example.com", Version: "v1"}
          return AddToGroupVersion(scheme, gv)
      }
      
  2. 使用 AddKnownTypes

    • 注册所有相关资源类型:

      func AddToScheme(scheme *runtime.Scheme) error {
          gv := schema.GroupVersion{Group: "example.com", Version: "v1"}
          scheme.AddKnownTypes(gv, &MyCustomResource{}, &MyCustomResourceList{})
          return nil
      }
      
  3. 使用 AddKnownTypeWithName

    • 在需要时为资源指定自定义 Kind名称:

      func AddToScheme(scheme *runtime.Scheme) error {
          gv := schema.GroupVersion{Group: "example.com", Version: "v1"}
          scheme.AddKnownTypeWithName(gv.WithKind("SpecialResource"), &MyCustomResource{})
          return nil
      }
      

总结

  • AddToGroupVersion 是对 API GroupVersion 的初始化,通常是注册流程的起点。
  • AddKnownTypes 是高层次的封装,用于批量注册资源类型。
  • AddKnownTypeWithName 是低层次的实现,用于更精细地注册单个资源类型,特别是需要自定义 Kind 的场景。

附录2 | reflect.TypeOf(obj).Elem()获取指针指向的go类型

  • reflect.TypeOf(obj).Elem().Name() 获取的是 obj 指向的实际类型的名称。具体来说,它返回的是 指针所指向的类型的名称

    解释:

    • reflect.TypeOf(obj) 获取的是 obj 的反射类型,如果 obj 是指针类型,reflect.TypeOf(obj) 返回的是指针类型。
    • .Elem() 用来获取指针所指向的元素类型,即返回指针所指向的实际类型。
    • .Name() 返回该类型的名称。

    举个例子:

    假设你有以下代码:

    package main
    
    import (
    	"fmt"
    	"reflect"
    )
    
    type MyStruct struct {
    	Name string
    }
    
    func main() {
    	obj := &MyStruct{Name: "K8s"}
    
    	// 获取 obj 的反射类型,即 *MyStruct 类型
    	ptrType := reflect.TypeOf(obj)
    	
    	// 获取 obj 指针所指向的元素类型,即 MyStruct 类型
    	elemType := ptrType.Elem()
    	
    	// 获取元素类型的名称
    	fmt.Println("elemType.Name():", elemType.Name()) // 输出 MyStruct
    }
    

    输出:

    elemType.Name(): MyStruct
    

    解释:

    • reflect.TypeOf(obj) 获取的是 *MyStruct 类型。
    • Elem() 获取的是 MyStruct 类型(即 *MyStruct 指向的类型)。
    • Name() 获取的是 MyStruct 的类型名称,因此输出 MyStruct

    总结:

    reflect.TypeOf(obj).Elem().Name() 获取的是指针类型所指向的元素类型的名称,通常用于获取结构体类型的名称。


网站公告

今日签到

点亮在社区的每一天
去签到