operator开发之api和apimachinery篇
k8s.io/api 项目
k8s.io/api 项目是什么
一开始,kubernetes的 内建资源 还不太多,内建资源的 结构定义,都是放在项目里维护的。后来为了方便资源的管理和扩展,将 所有内建资源 的结构定义文件、scheme注册文件、deepcopy等文件,放入了 staging/src 目录下,作为一个单独的项目维护。该项目的名称就是 k8s.io/api。因此,简单来说,k8s.io/api 项目,维护着 Kubernetes 所有内建资源 的 struct定义。
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 的函数
因此,我们操作内建资源的时候,所有 GVK 内建资源的结构,都是由 k8s.io/api 这个项目提供的。
k8s.io/apimachinery 项目
k8s.io/apimachinery 项目是什么
k8s.io/apimachinery 项目是一个关于Kubernetes API资源的工具集,为 k8s.io/api 项目所有的资源,提供下列能力。
- ObjectMeta与TypeMeta
- Scheme
- RESTMapper
- 编码与解码
- 版本转换
有了 k8s.io/apimachinery,就可以很方便的操作 kubernetes API。
k8s.io/apimachinery 提供 TypeMeta 与 ObjectMeta
TypeMeta 与 ObjectMeta 是特别常用的两个数据结构。kubernetes 的每一个资源,都会包含一个 TypeMeta、一个ObjectMeta。
- TypeMeta是内嵌的,转json的时候不会有嵌套结构
- ObjectMeta,json标签就是 metadata
1
2
3
4
5
6type 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
1 | type TypeMeta struct { |
ObjectMeta:位于 apimachinery/pkg/apis/meta/v1/types.go
1 | type ObjectMeta struct { |
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
k8s.io/apimachinery 提供 scheme 数据结构
1、资源的internal版本、external版本
资源的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、internal版本 和 external版本 相互转换的源码位置
kubernetes/pkg/apis 中,每个目录都是一个group,每个group都有一个 internal 的 资源 types.go 文件
3、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 文件,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44package 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
}
3、k8s.io/apimachinery 提供 scheme 数据结构
根据前面的描述,我们知道scheme是一个数据结构,它的struct其实就是 k8s.io/apimachinery 提供的。在 staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go 文件中,有 Scheme 结构
1 | type Scheme struct { |
Scheme结构中,所有的字段首字母都是小写的,即非导出的,外界无法访问。为此,staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go 文件中还提供了一个方法 NewScheme(),用于初始化一个空的Scheme对象
1 | func NewScheme() *Scheme { |
此外,staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go 文件还提供了很多方法,用于将GVK注册到Scheme对象中。用的比较多的是AddKnownTypes()方法
1 | func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) { |
4、Scheme结构提供的常用方法
AddKnownTypes
- 方法签名:func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types …Object)
- 方法功能:向 scheme 中注册GVK,参数1 gv 表示 GroupVersion,参数2 types 是具体的 Kind 类型
- 举例:staging/src/k8s.io/api/apps/v1/register.go 文件中,使用 AddKnownTypes 方法,将apps/v1下的所有Kind,都注册到scheme中去
1 | func addKnownTypes(scheme *runtime.Scheme) error { |
KnownTypes
- 方法签名:func (s *Scheme) KnownTypes(gv schema.GroupVersion) map[string]reflect.Type
- 方法功能:获取指定GV下所有Kind的Type类型
- 举例
1 | types := Scheme.KnownTypes(schema.GroupVersion{ |
VersionsForGroupKind
- 方法签名:func (s *Scheme) VersionsForGroupKind(gk schema.GroupKind) []schema.GroupVersion
- 方法功能:获取指定GK的所有Versions,并以GV列表的形式返回
1 | groupVersions := Scheme.VersionsForGroupKind( |
ObjectKinds
- 方法签名:func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error)
- 方法功能:获取指定object 的 所有可能的 group、version、kind 值,并以 GVK 列表的形式返回
1 | gvks, notVersioned, err := Scheme.ObjectKinds(&appsv1.Deployment{}) |
New
- 方法签名:func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error)
- 方法功能:根据指定的GVK,创建该资源的一个对象
1 | deployment, err := Scheme.New(schema.GroupVersionKind{ |
- AddConversionFunc
- 方法源码
1
2
3func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error {
return s.converter.RegisterUntypedConversionFunc(a, b, fn)
}
- 方法源码
该方法,用于向scheme中注册 不同资源 的自定义转换器。
k8s.io/apimachinery 提供 RESTMapper 结构
1、理解GVR和GVK的用途
在 上面中提到,k8s.io/apimachinery 提供了 GR/GVR、GK/GVK 等数据结构。GR和GVR 负责对接 RESTful 风格的url路径,GK和GVK 负责确定一个具体的kubernetes资源
- GVR举例:
用户想要获取 apps组下、v1版本的 deployments,如何编写url地址?–> GET /apis/apps/v1/deployments.这个url中,就可以使用 GVR 描述,group为apps,version为v1,Resource为deployments - GVK举例:
当kubernetes的代码中,想要操作一个资源的时候,如何找到资源的struct 结构?通过GVK去找。比如 apps/v1/Deployment,就可以确定 group为apps,version为v1,kind为Deployment,就可以找到这个资源的struct
2、RESTMapper是什么
当用户使用 REST风格 的 url 访问资源时,kubernetes如何确定需要操作哪一个GVK呢?REST风格 的 url,可以从中得到 GVR,只需要完成 GVR 到 GVK 的转换就可以了。因此,apimachinery维护了一个数据结构 RESTMapper,记录 GVR 和 GVK 的映射关系
1 | type RESTMapping struct { |
另外,apimachinery还提供了一个接口 RESTMapper,接口中提供了 将 GVR 转成 GVK 的方法。其中,KindFor 和 KindsFor 就是将 GVR 转成 GVK 的方法
1 | type RESTMapper interface { |
3、DefaultRESTMapper
RESTMapper接口,有一个默认的实现 DefaultRESTMapper.位于 staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go
1 | type DefaultRESTMapper struct { |
staging/src/k8s.io/apimachinery/pkg/api/meta/restmapper.go 中还提供了一个 NewDefaultRESTMapper 方法,用于新建一个DefaultRESTMapper
k8s.io/apimachinery 提供序列化、编解码能力
1、k8s.io/apimachinery 的 runtime.serializer 包
k8s.io/apimachinery 中,关于 序列化 和 编解码 的代码,大都在 staging/src/k8s.io/apimachinery/pkg/runtime/serializer 包下。
json、protobuf、yaml包,分别提供了对应格式的序列化器,共3种序列化器
2、k8s.io/apimachinery 提供了序列化的通用接口
staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go 文件中,提供了序列化的通用接口 Serializer。Serializer接口提供了编解码能力。
1
2
3
4type Serializer interface {
Encoder
Decoder
}Encoder是编码器接口,还是在 staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go 文件中
1
2
3
4type Encoder interface {
Encode(obj Object, w io.Writer) error
Identifier() Identifier
}Decoder是解码器接口,还是在 staging/src/k8s.io/apimachinery/pkg/runtime/interfaces.go 文件中
1
2
3type Decoder interface {
Decode(data []byte, defaults *schema.GroupVersionKind, into Object) (Object, *schema.GroupVersionKind, error)
}3、json 序列化器
staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json/json.go 文件中,提供了json序列化器
1
2
3
4
5
6
7
8type Serializer struct {
meta MetaFactory
options SerializerOptions
creater runtime.ObjectCreater
typer runtime.ObjectTyper
identifier runtime.Identifier
}json序列化器 实现了 runtime.interface.go 中的Serializer接口,实现了 Encode、Decode方法
创建一个json序列化器,有多个方法
NewSerializer、NewSerializerWithOptions
4、yaml 序列化器
- staging/src/k8s.io/apimachinery/pkg/runtime/serializer/yaml/yaml.go 文件中,提供了yaml序列化器yaml序列化器 实现了 runtime.interface.go 中的Serializer接口
1
2
3
4type yamlSerializer struct {
// the nested serializer
runtime.Serializer
}
5、protobuf 序列化器
- staging/src/k8s.io/apimachinery/pkg/runtime/serializer/protobuf/protobuf.go 文件中,提供了protobuf序列化器protobuf序列化器 实现了 runtime.interface.go 中的Serializer接口
1
2
3
4
5type Serializer struct {
prefix []byte
creater runtime.ObjectCreater
typer runtime.ObjectTyper
}
k8s.io/apimachinery 提供 不同资源 相互转换能力
- scheme提供了AddConversionFunc方法,用于向scheme中注册 不同资源 的自定义转换器。
举例:创建了一个Scheme对象,名为scheme。我们就可以通过下面的方法,注册 appsv1.Deployment 与 appsv1beta1.Deployment 的相互转换方法1
2
3
4
5
6
7
8
9scheme.AddConversionFunc(
(*appsv1.Deployment)(nil),
(*appsv1beta1.Deployment)(nil),
func(a, b interface{}, scope conversion.Scope) error{
v1deploy := a.(*appsv1.Deployment)
v1beta1deploy := b.(*appsv1beta1.Deployment)
// make conversion here
return nil
})