开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
记得看代码中的注释哈,理解都在里面 源码基于v1.24
这篇文章先铺垫GVK和GVR这两个在Kubernetes中非常重要的概念的基本认知,以便于更进一步的深入学习内部的机制
GVK 和 GVR 是什么?
- GVK 就是 group、verison、kind
- GVR 就是 group、version、resource
为什么有 kind 和 resouce 两个相似概念 首先我们要明确几个概念:
- 在编码过程中,资源数据的存储都是以结构体存储(称为 Go type) 由于多版本version的存在(alpha1,beta1,v1等),不同版本中存储结构体的存在着差异,但是我们都会给其相同的 Kind 名字(比如 Deployment)
- 因此,我们编码中只用 Kind 名(如 Deployment),并不能准确获取到其使用哪个版本结构体
- 所以,采用 GVK 获取到一个具体的 存储结构体,也就是 GVK 的三个信息(group/verion/kind) 确定一个 Go type(结构体)
- 如何获取呢? —— 通过 Scheme, Scheme 存储了 GVK 和 Go type 的映射关系
-
- 在创建资源过程中,我们编写 yaml,提交请求: 编写 yaml 过程中,我们会写 apiversion 和 kind,其实就是 GVK 而客户端(也就是我们)与 apiserver 通信是 http 形式,就是将请求发送到某一 http path
-
- 发送到哪个 http path 呢? 这个 http path 其实就是 GVR /apis/batch/v1/namespaces/default/job 这个就是表示 default 命名空间的 job 资源 我们 kubectl get po 时 也是请求的路径 也可以称之为 GVR 其实 GVR 是由 GVK 转化而来 —— 通过REST映射的RESTMappers实现
- 总结 同 Kind 由于多版本会存在 多个数据结构(Go type) GVK 可以确定一个具体的 Go Type(映射关系由 Scheme 维护) GVK 可以转换 http path 请求路径(也就是 GVR)(映射由RESTMappers实现) GVK和GVR是相关的。GVK在GVR标识的HTTP路径下提供服务。将GVK映射到GVR的过程称为REST映射。我们将在“ REST Mapping”中看到在Golang中实现REST映射的RESTMappers。
scheme的定义
定义在 k8s.io/apimachinery/pkg/runtime/scheme.go 中
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
// unversionedTypes are transformed without conversion in ConvertToVersion.
unversionedTypes map[reflect.Type]schema.GroupVersionKind
// unversionedKinds are the names of kinds that can be created in the context of any group
// or version
unversionedKinds map[string]reflect.Type
// Map from version and resource to the corresponding func to convert
// resource field labels in that version to internal version.
fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc
// defaulterFuncs is a map to funcs to be called with an object to provide defaulting
// the provided object must be a pointer.
defaulterFuncs map[reflect.Type]func(interface{})
// converter stores all registered conversion functions. It also has
// default converting behavior.
converter *conversion.Converter
// versionPriority is a map of groups to ordered lists of versions for those groups indicating the
// default priorities of these versions as registered in the scheme
versionPriority map[string][]string
// observedVersions keeps track of the order we've seen versions during type registration
observedVersions []schema.GroupVersion
// schemeName is the name of this scheme. If you don't specify a name, the stack of the NewScheme caller will be used.
// This is useful for error reporting to indicate the origin of the scheme.
schemeName string
}
对象的scheme是在哪里注册的呢?
在cmd/apiserver/app/server.go中有一个
import "k8s.io/kubernetes/pkg/controlplane"
在这个文件下全是import一些包很明显是通过go语言的init机制来操作的
package controlplane
import (
// These imports are the API groups the API server will support.
_ "k8s.io/kubernetes/pkg/apis/admission/install"
_ "k8s.io/kubernetes/pkg/apis/admissionregistration/install"
_ "k8s.io/kubernetes/pkg/apis/apiserverinternal/install"
_ "k8s.io/kubernetes/pkg/apis/apps/install"
_ "k8s.io/kubernetes/pkg/apis/authentication/install"
_ "k8s.io/kubernetes/pkg/apis/authorization/install"
_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
_ "k8s.io/kubernetes/pkg/apis/batch/install"
_ "k8s.io/kubernetes/pkg/apis/certificates/install"
_ "k8s.io/kubernetes/pkg/apis/coordination/install"
_ "k8s.io/kubernetes/pkg/apis/core/install"
_ "k8s.io/kubernetes/pkg/apis/discovery/install"
_ "k8s.io/kubernetes/pkg/apis/events/install"
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
_ "k8s.io/kubernetes/pkg/apis/flowcontrol/install"
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
_ "k8s.io/kubernetes/pkg/apis/networking/install"
_ "k8s.io/kubernetes/pkg/apis/node/install"
_ "k8s.io/kubernetes/pkg/apis/policy/install"
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
_ "k8s.io/kubernetes/pkg/apis/scheduling/install"
_ "k8s.io/kubernetes/pkg/apis/storage/install"
)
在apps这个APIGroup,发现init的中把不同版本的APIGroup都注册到了scheme里
// Package install installs the apps API group, making it available as
// an option to all of the API encoding/decoding machinery.
package install
import (
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/v1"
"k8s.io/kubernetes/pkg/apis/apps/v1beta1"
"k8s.io/kubernetes/pkg/apis/apps/v1beta2"
)
func init() {
Install(legacyscheme.Scheme)
}
// Install registers the API group and adds types to a scheme
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))
}
对象的scheme是怎么注册的呢?
以下代码在register.go中
package apps
import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/pkg/apis/autoscaling"
)
/*
定义了需要导出的变量
*/
var (
// SchemeBuilder stores functions to add things to a scheme.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
// AddToScheme applies all stored functions t oa scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
// GroupName is the group name use in this package
const GroupName = "apps"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
// Kind takes an unqualified kind and returns a Group qualified GroupKind
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
// TODO this will get cleaned up with the scheme types are fixed
scheme.AddKnownTypes(SchemeGroupVersion,
&DaemonSet{},
&DaemonSetList{},
&Deployment{},
&DeploymentList{},
&DeploymentRollback{},
&autoscaling.Scale{},
&StatefulSet{},
&StatefulSetList{},
&ControllerRevision{},
&ControllerRevisionList{},
&ReplicaSet{},
&ReplicaSetList{},
)
return nil
}
以上代码中使用了builder的设计模式,逻辑如下图所示
scheme分为两类,internal和external,interna是对内使用,external是对外使用,以上是internal的注册方式,下面是external的注册方式,逻辑大致是一致的,只是实现部分的代码位置不同
再用一张图来看看Converter和Defaulter的代码生成
Summary
- scheme可以理解为资源对象的注册表,分为对内对外两类,内含处理内外部Version之间的转换,GVK和GoType之间的转换,默认值的填充
- scheme使用了
builder的设计模式进行代码的解耦和优化,无处不彰显着优雅二字