Kubernetes 代码笔记 -- 2
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()