4

Kubernetes 代码笔记 -- 2

 1 year ago
source link: https://diabloneo.github.io//2023/02/13/kubernetes-code-note-2/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

apimachinery 中的概念

Kubernetes 的 api 相关代码中有很多概念都是 k8s 独有的,需要专门理解一下,才方便研究 k8s 代码。

Kubebuilder 项目有一篇文章比较好的介绍了这些关键概念的理解,可以先阅读一下:https://book.kubebuilder.io/cronjob-tutorial/gvks.html。我这里写的是我个人的理解。

GVK: GroupVersionKind

k8s.io/apimachinery/pkg/runtime/schema/group_version.go

// GroupVersionKind unambiguously identifies a kind.  It doesn't anonymously include GroupVersion
// to avoid automatic coercion.  It doesn't use a GroupVersion to avoid custom marshalling
type GroupVersionKind struct {
	Group   string
	Version string
	Kind    string
}

这个结构体包含了 API 的 group, version 和 kind 信息。这里的 kind 是对应的 Go 结构体的 type 名称。比如 StatefulSet 就是:

GroupVersionKind{Group: "apps", Version: "v1", Kind: "StatefulSet"}

GVR: GroupVersionResource

k8s.io/apimachinery/pkg/runtime/schema/group_version.go

// GroupVersionResource unambiguously identifies a resource.  It doesn't anonymously include GroupVersion
// to avoid automatic coercion.  It doesn't use a GroupVersion to avoid custom marshalling
type GroupVersionResource struct {
	Group    string
	Version  string
	Resource string
}
GroupVersionResource{Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterroles"}

这个结构体包含了 API 的 group, version 和 resource 信息。这里的 resource 对应的是 API 路径里的名字。很容易会搞混 resource 和 kind 的区别,我觉得可以这么理解:

  • Resource 是 API 侧的概念,是根据 API 路径推导出来的资源类型名称,例如 pods, deployments 等(下面会说单复数的问题)。
  • Kind 是 API 路径里得到这个资源类型名称所对应的 Go 的结构体的 type 名称。

在现有的代码中,GVR 在 apiserver 端是比较少使用的,反而是在 controller 和 client 中会用得多一些。

apiserver 中的使用

下面这个函数中会添加 API 请求的 handler。

-> k8s.io/apiserver/pkg/endpoints/installer.go: func (a *APIInstaller) registerResourceHandlers()

因为 APIInstaller 中已经包含了 APIGroupVersion,所以在添加的过程中,可以根据 GroupVersion 直接得到 GVK:

	fqKindToRegister, err := GetResourceKind(a.group.GroupVersion, storage, a.group.Typer)
	if err != nil {
		return nil, nil, err
	}

	...

	reqScope := handlers.RequestScope{
		# 这里也生成了 GVR
		Resource:    a.group.GroupVersion.WithResource(resource),
	}

RESTMapper

其他地方的使用更多的是依赖于 RESTMapper 来根据 GVR 获得 GVK。

有好几种 RESTMapper,默认的如下:

k8s.io/apimachinery/pkg/api/meta/restmapper.go

// DefaultRESTMapper exposes mappings between the types defined in a
// runtime.Scheme. It assumes that all types defined the provided scheme
// can be mapped with the provided MetadataAccessor and Codec interfaces.
//
// The resource name of a Kind is defined as the lowercase,
// English-plural version of the Kind string.
// When converting from resource to Kind, the singular version of the
// resource name is also accepted for convenience.
//
// TODO: Only accept plural for some operations for increased control?
// (`get pod bar` vs `get pods bar`)
type DefaultRESTMapper struct {
	defaultGroupVersions []schema.GroupVersion

	resourceToKind       map[schema.GroupVersionResource]schema.GroupVersionKind
	kindToPluralResource map[schema.GroupVersionKind]schema.GroupVersionResource
	kindToScope          map[schema.GroupVersionKind]RESTScope
	singularToPlural     map[schema.GroupVersionResource]schema.GroupVersionResource
	pluralToSingular     map[schema.GroupVersionResource]schema.GroupVersionResource
}

从它的内容可以看出,它是在 resource 和 kind 之间做映射的。同时,它还指出了,resource name 是根据 kind 来的,小写且是复数。不过,为了方便,也支持单数形式的 resource name。

DefaultRESTMapper 实现了 RESTMapper interface。这个 interface 定义了一些方法用来实现转换,比如 KindFor 根据 GVR 得到 GVK:

	// KindFor takes a partial resource and returns the single match.  Returns an error if there are multiple matches
	KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error)

根据使用场景补充,k8s 中还实现了好几个不同的 RESTMapper,比如 MultiRESTMapper, DefferedDiscoveryRESTMapper 等。

Scheme

k8s.io/apimachinery/pkg/runtime/scheme.go

Scheme 的主要工作就是保存 Go 类型和对应的 API 信息之间的关系。通过它的一些成员可以看出它的设计目标就是保存这种映射关系:

type Scheme struct {
	// gvkToType allows one to figure out the go type of an object with
	// the given version and name.
	gvkToType map[schema.GroupVersionKind]reflect.Type

	// typeToGVK allows one to find metadata for a given go object.
	// The reflect.Type we index by should *not* be a pointer.
	typeToGVK map[reflect.Type][]schema.GroupVersionKind

	...
}

一般来说,一大堆的 API 可以共用一个 Scheme,比如 legacy API 都是共用下面这个文件中的 Scheme 对象:pkg/api/legacyscheme/scheme.go

代码中一般是使用 Scheme 对象的 AddKnownTypes 方法把 Go 对象添加到 Scheme 中的。搜索这个方法可以找到 API 对象被添加的路径。以 rbac 为例:

pkg/apis/rbac/register.go

// GroupName is the name of this API group.
const GroupName = "rbac.authorization.k8s.io"

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

// SchemeBuilder is a function that calls Register for you.
var (
	SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
	AddToScheme   = SchemeBuilder.AddToScheme
)

// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
	scheme.AddKnownTypes(SchemeGroupVersion,
		&Role{},
		&RoleBinding{},
		&RoleBindingList{},
		&RoleList{},

		&ClusterRole{},
		&ClusterRoleBinding{},
		&ClusterRoleBindingList{},
		&ClusterRoleList{},
	)
	return nil
}

另外,你可以根据上面代码中的 AddToScheme 方法推导出:当这个方法被调用时,就会执行这些添加操作。因此,也可以在代码中搜索 rbac.*AddToScheme 来找到添加的地方:

pkg/apis/rbac/install/install.go

func init() {
	Install(legacyscheme.Scheme)
}

// Install registers the API group and adds types to a scheme
func Install(scheme *runtime.Scheme) {
	utilruntime.Must(rbac.AddToScheme(scheme))
	utilruntime.Must(v1.AddToScheme(scheme))
	utilruntime.Must(v1beta1.AddToScheme(scheme))
	utilruntime.Must(v1alpha1.AddToScheme(scheme))
	utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion, v1alpha1.SchemeGroupVersion))
}

所以,只要这个 pkg 被 import,rbac 的这些信息就会被注册到 legacy 的 Scheme 中。在这个 API Group 的 storage 被初始化的时候,这个 pkg 就会被 import:

-> pkg/registry/rbac/rest/storage_rest.go: func (p RESTStorageProvider) NewRESTStorage()

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK