云原生学习路线导航页(持续更新中)
- 本文是 Kubernetes operator学习 系列第五篇,主要对 k8s.io/api 和 k8s.io/apimachinery 两个项目 进行学习
- Kubernetes operator学习系列 快捷链接
- Kubernetes operator(一)client-go篇
- Kubernetes operator(二)CRD篇
- Kubernetes operator(三)code-generator 篇
- Kubernetes operator(四)controller-tools 篇
- Kubernetes operator(五)api 和 apimachinery 篇
1.k8s.io/api 项目
1.1.k8s.io/api 项目是什么
- 一开始,kubernetes的 内建资源 还不太多,内建资源的 结构定义,都是放在项目里维护的。
- 后来为了方便资源的管理和扩展,将 所有内建资源 的结构定义文件、scheme注册文件、deepcopy等文件,放入了 staging/src 目录下,作为一个单独的项目维护。该项目的名称就是
k8s.io/api
- 因此,简单来说,
k8s.io/api
项目,维护着 Kubernetes 所有内建资源 的 struct定义。
1.2.k8s.io/api 的源码分析
- 下图中的每一个目录,都代表一个group
- 一个 Group 下,可能会存在多个 Version
- 每个version下,都会包含三个文件:
doc.go、register.go、types.go
。- doc.go:声明了按照 package 维度,为所有 structs 提供生成的声明
- types.go:编写资源的详细结构,一般包括:资源、资源List、资源Spec、资源Status 的详细定义
- register.go:提供注册到 runtime.Scheme 的函数
- 以
apps/v1/types.go
为例,查看其内容,发现包含GroupVersion=apps/v1
下的所有Resource结构定义。- 下图只截出了一部分,没有截出全部。
- 下图只截出了一部分,没有截出全部。
- 因此,我们操作内建资源的时候,所有 GVK 内建资源的结构,都是由
k8s.io/api
这个项目提供的。
2.k8s.io/apimachinery 项目
2.1.k8s.io/apimachinery 项目是什么
k8s.io/apimachinery
项目是一个关于Kubernetes API资源的工具集,为k8s.io/api
项目所有的资源,提供下列能力。- ObjectMeta与TypeMeta
- Scheme
- RESTMapper
- 编码与解码
- 版本转换
- …
- 有了
k8s.io/apimachinery
,就可以很方便的操作 kubernetes API。
2.2.k8s.io/apimachinery 提供 TypeMeta 与 ObjectMeta
- TypeMeta 与 ObjectMeta 是特别常用的两个数据结构。kubernetes 的每一个资源,都会包含一个 TypeMeta、一个ObjectMeta。
- TypeMeta是内嵌的,转json的时候不会有嵌套结构
- ObjectMeta,json标签就是 metadata
type Pod struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` }
- TypeMeta:位于
apimachinery/pkg/runtime/types.go
type TypeMeta struct { // +optional APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty" protobuf:"bytes,1,opt,name=apiVersion"` // +optional Kind string `json:"kind,omitempty" yaml:"kind,omitempty" protobuf:"bytes,2,opt,name=kind"` }
- ObjectMeta:位于
apimachinery/pkg/apis/meta/v1/types.go
type ObjectMeta struct { Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"` GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"` Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"` SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"` UID types.UID `json:"uid,omitempty" protobuf:"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID"` ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"` CreationTimestamp Time `json:"creationTimestamp,omitempty" protobuf:"bytes,8,opt,name=creationTimestamp"` DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" protobuf:"bytes,9,opt,name=deletionTimestamp"` DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" protobuf:"varint,10,opt,name=deletionGracePeriodSeconds"` Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"` Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"` OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" patchStrategy:"merge" patchMergeKey:"uid" protobuf:"bytes,13,rep,name=ownerReferences"` Finalizers []string `json:"finalizers,omitempty" patchStrategy:"merge" protobuf:"bytes,14,rep,name=finalizers"` ZZZ_DeprecatedClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"` ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" protobuf:"bytes,17,rep,name=managedFields"` }
2.3.k8s.io/apimachinery 的rumetime/schema包提供 GVRK 各种数据结构
- 在kubernetes中,为了方便描述资源,或描述REST 的URL,提出了5个概念:
- GV:GroupVersion
- GR:GroupResource
- GVR:GroupVersionResource
- GK:GroupKind
- GVK:GroupVersionKind
- 其中,GR、GVR都是用来描述 RESTFUL API 的,GK、GVK都是用来描述资源类型的
- 这 5种数据结构的 struct 定义,都是写在
k8s.io/apimachinery/pkg/runtime/schema/group_version.go
文件中 - 该文件中还提供了这5种数据结构相互转换的方法
-
其中,APIVersion Kind,就是我们平时写yaml看到的apiVersion:
Group/Version
Kind
-
各结构的转换方法如下:
-
源码如下:
-
2.4.k8s.io/apimachinery 提供 scheme 数据结构
2.4.1.资源的internal版本、external版本
- kubernetes的资源,并非一下就确定好的,是有一个发展过程的,因此一个资源Kind,可能在多个 GroupVersion 下同时存在。比如 Deployment,在apps/v1下存在,在apps/v1beta1下也存在。
- 那么,在kubernetes的开发者想要处理Deployment的时候,到底应该按照哪个版本写程序呢?
- 按理说,每一种GVK都要有相应的处理方法。
- 但是这样实在是太繁琐了,维护起来不方便,还会有大量重复代码
- 因此,为每一种GK,维护了一个internal版本,作为中转节点。
apps/v1/Deployment
和apps/v1beta1/Deployment
的相互转换,均是先转成internal的Deployment,再转成 对外的版本- kubernetes的作者们,只需要对
internal版本
的资源编写逻辑,就可以处理所有version的资源
- 这种设计方式,将GVK之间的 拓扑结构,变成了星型结构,非常巧妙。
2.4.2.scheme的作用
- kubernetes的资源版本太多了,没有谁专门有时间去维护,还是让资源自己来注册比较方便。
- scheme就是为资源注册信息设计的一个数据结构,每个GVK,将自己的信息封装成一个scheme对象,并将这个scheme对象交给APIServer统一管理,API Server就能够认识这种 GVK 了
- 在k8s.io/api 项目中,每一个GV下都有一个register.go文件,里面就是将当前GV下的所有Kind,注册到 APIServer 的统一scheme中去。比如
staging/src/k8s.io/api/apps/v1/register.go
文件,package v1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" ) // 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: "v1"} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } var ( // TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api. // localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes. SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) localSchemeBuilder = &SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &Deployment{}, &DeploymentList{}, &StatefulSet{}, &StatefulSetList{}, &DaemonSet{}, &DaemonSetList{}, &ReplicaSet{}, &ReplicaSetList{}, &ControllerRevision{}, &ControllerRevisionList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil }
2.4.3.k8s.io/apimachinery 提供 scheme 数据结构
- 根据前面的描述,我们知道scheme是一个数据结构,它的struct其实就是 k8s.io/apimachinery 提供的
- 在
staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
文件中,有 Scheme 结构type Scheme struct { // map,记录 gvk-->type。其中type是通过反射的方式记录的 gvkToType map[schema.GroupVersionKind]reflect.Type // map,记录 type-->gvk typeToGVK map[reflect.Type][]schema.GroupVersionKind // map,记录 type-->gvk。像pod这种,只有一个version的,就记录在这里。 unversionedTypes map[reflect.Type]schema.GroupVersionKind // map,记录 gvk-->type。像pod这种,只有一个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 // map,记录默认方法。为某一个具体的type,设置默认值 defaulterFuncs map[reflect.Type]func(interface{}) // 转换器 converter *conversion.Converter // 记录version的优先级。当没有选择version的时候,优先使用谁 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结构中,所有的字段首字母都是小写的,即非导出的,外界无法访问。为此,
staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
文件中还提供了一个方法 NewScheme(),用于初始化一个空的Scheme对象func NewScheme() *Scheme { s := &Scheme{ 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{}){}, versionPriority: map[string][]string{}, schemeName: naming.GetNameFromCallsite(internalPackages...), } s.converter = conversion.NewConverter(nil) // Enable couple default conversions by default. utilruntime.Must(RegisterEmbeddedConversions(s)) utilruntime.Must(RegisterStringConversions(s)) return s }
- 此外,
staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
文件还提供了很多方法,用于将GVK注册到Scheme对象中。用的比较多的是AddKnownTypes()方法func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) { s.addObservedVersion(gv) for _, obj := range types { t := reflect.TypeOf(obj) if t.Kind() != reflect.Ptr { panic("All types must be pointers to structs.") } t = t.Elem() s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj) } }
3.参考博客
- Kubernetes学习笔记[第5章]API Machinery