API 概述
Kubernetes API 通过 HTTP 协议以 RESTful 的形式提供,API 资源的序列化方式主要是以 JSON 格式进行,但为了内部通信也支持 Protocol Buffer 格式。为了方便扩展与演进,kubernetes API 支持分组与多版本,这体现在不同的 API 访问路径上。有了分组与多版本支持,即使要在新版本中去掉 API 资源的特定字段或者重构 API 资源的展现形式,也可以保证版本之间的兼容性。
kubernetes API 的详细规范请参考 API Conventions。
API-Group
Kubernetes 的 API 资源被分为各个组,这样做带来的好处是:
- 各组可以单独打开或者关闭。
- 各组可以有独立的版本,在不影响其他组的情况下单独向前衍化。
- 同一个资源可以同时存在于多个不同组中,这样就可以同时支持某个特定资源稳定版本与实验版本。
资源定义中的体现
比如 Deployment 资源的组名即为 apps,定义在 apiVersion 字段(当然后面还有版本号)。实际上,Deployment 不止出现在 apps
分组里,也出现在 extensions
分组中,不同的分组可以实验不同的特性。
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deploy
spec:
...
另外,kubernetes 中的核心资源如 pod、namespace、configmap、node、service 等存在于 core
分组中,但是由于历史的原因,core
不出现在 apiVersion
字段中(只显示版本号),例如以下定义一个 pod 资源的序列化对象
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app: exp
...
RESTful API 上的体现
- core 组的资源访问路径一般是
/api/$VERSION
- 其他命名组的资源访问路径则是
/apis/$GROUP_NAME/$VERSION
此外除了 API 资源之外,还有一些系统级别的资源,比如指标信息 /metrics
。整体如下所示:
API-Version
Version 表示版本,为了支持 API 的独立演进,Kubernetes API 支持不同的版本,不同的版本代表不同的成熟度。
- 一般来说,新的资源分组先出现
v1alpha1
版本,随着稳定性的提高被推进到v1beta1
,最后从v1
版本毕业。总的来说,对于已经 GA 的 API,Kubernetes 严格维护其兼容性,终端用户可以放心使用,beta 版本的 API 则尽量维护,保证不打破版本跨版本之间的交互,而对于 alpha 版本的 API 则很难保证兼容性,不太推荐生产环境使用。 - Kubernetes 承诺向前兼容 3 个版本,所以在升级的时候需要考虑这个,不能跳 3 个版本以上的。怎么理解呢?kubernetes 每发一个版本,它都会有它支持的 API 版本情况,这个支持的 API 版本只会跨 3 个版本。支持 API 兼容,就可以保证如果我们在老版本的 kubernetes 中用了旧版本的 API,那么升级到新的 kubernetes 的话,也是可以被识别和处理的。对于保证兼容性,kubernetes 制定了相应的策略。
- API 会发生变化的主要原因是,新的用户场景出现之后,可能会新增一个字段或者删除某个资源,甚至是改变资源的展现形式。
需要注意的是,
-
上述所说的 api version 准确来说是 external version,也就是对外的,用户直接使用的。一般提 api version 也都是指 external version。
-
其实还有 internal version,这个 version 是对内的,是存储时候使用的。用户向 API Server 提交的资源对象,会被转换为 internal version,并存储到 etcd cluster 中。
设立 internal version 的好处是:external version 都只需要转化为 internal version 即可,不再需要考虑不同 external version 的转化,减少了复杂度。比如,external version v1 转换为 internal version,之后用于想用 extension version v1beta1 读取的时候,只需要将 internal version 转换为 v1beta1 即可。
API REST 方法
- GET
- /{资源类型名复数格式}/{资源对象名字}:获取单个资源对象
- /{资源类型名复数格式}:LIST 请求,获取某一类型的资源对象列表,比如 GET /pods 返回一个 Pod 资源列表。
- /{资源类型名复数格式}?watch=true:Watch 请求,比如 GET /deployments?watch=true。
- POST:/{资源类型名复数格式},创建一个资源对象,资源对象的配置来自客户端提供的 JSON/YAML。
- DELETE:/{资源类型名复数格式}/{资源对象名字},删除某一个资源对象。
- PUT:/{资源类型名复数格式}/{资源对象名字},更新或创建一个资源对象,资源对象的配置来自客户端提供的 JSON/YAML。
- PATCH:/{资源类型名复数格式}/{资源对象名字},修改某个资源对象详细指定的域。支持三种方式:JSON Patch、Merge Patch、Strategic Merge Patch(定制化的 merge patch)。不同方式通过 HTTP 首部的 Content-Type 来标识。
API 相关内容
GVK && GVR
GVK(Group Version Kind)
- Group:资源所属的 API Group。
- Version:资源所属的 API Group 的 Version。
- Kind:API 资源对象的类型,如 Pod、Deployment、Service 等。一般来说,kubernetes API 中有三种类型的 kind:
- 单个资源对象的类型,比如 Pod 或者 Node,用于表示该资源是一个 Pod 还是 Node。
- 资源对象的列表类型,比如 PodList 或者 NodeList。
- 特殊类型以及非持久化操作的类型,这种类型的资源具有代表性的,比如 subresource,绑定资源的 /binding、更新资源状态的 /status,以及读写资源实例数量的 /scale。
需要注意的是,同 kind 名不止可以出现在同一分组的不同版本号,比如 apps/v1beta1 与 apps/v1。还可以出现在不同的分组中,比如 deployment 一开始是以 alpha 的特性出现在 extensions 分组中,GA 之后被推到 apps 组中。因此,要做到严格的区分,还需要组合 group 和 version。
GVK 通常用于编程接口中识别和操作具体的 API 类型和对象,特别是序列化和反序列化时。例如,在 Go 客户端库中,需要指定 GVK 来创建、获取或修改一个 Kubernetes 对象。
GVR(Group Version Resource)
- Group:与 GVK 的 Group 相同,指 API Group。
- Version:与 GVK 的 Version 相同,指 API Group 的 Version。
- Resource:资源在 API 服务器中的名称。它与 Kind 有所不同,Resource 是以复数形式出现的,如 pods、deployments、services 等。
GVR 在与 Kubernetes API 服务器的 RESTful 操作中被使用,主要用于 HTTP 请求中构建正确的 URL 路径。
两者的区别
虽然 GVK 和 GVR 包含相似的信息(API Group 和 Version),但它们的 "Kind" 和 "Resource" 分别指向不同的概念:
- Kind 指的是对象的类型,通常对应于一个 Go 语言的结构体类型,在 YAML 文件或者序列化对象时用到它。
- Resource 指的是 API 服务器的端点,通常用于构造 API 调用的 URL,如 GET 请求获取资源列表。
例如,对于一个 Deployment 对象来说:
- GVK 是
apps/v1, Kind=Deployment
(表示在代码里处理的对象) - GVR 是
apps/v1, Resource=deployments
(表示在 API 中访问的资源)
在 Kubernetes 客户端编程中,通常需要了解并正确使用这两种标识符来确保能够正确地引用和操作感兴趣的资源。
RESTMapper
GVK 和 GVR 之间经常会涉及转换,比如在 ListWatcher 的时候,根据 Schema 定义的 GVK 解析出 GVR,向 API Server 发起 http 请求获取资源,然后 watch。
Kubernetes 使用 RESTMapper 来实现 GVK 与 GVR 之间的映射(定义了 RESTMapper 接口 并带默认实现 DefaultRESTMapper)。当你传入 GVK 之后就可以得到对应的 GVR,同理传入 GVR 之后也可以得到对应的 GVK。同时,RESTMapper 支持在只传入 GVR 中若干信息的情况下,返回完整的 GVK(详见 RESTMapper 接口,staging/src/k8s.io/apimachinery/pkg/api/meta/interfaces.go)。
不考虑两者具体概念上的区别,仅考虑在代码层面的话,可以发现 GVR 就是 GVK 的复数形式,那么为什么还需要有这么一个 RESTMapper 用来专门做映射呢?其实从我理解来看,虽然映射方式简单,但是正是 RESTMapper,让这些转换有了统一的模块,也更好维护,而不是采用直接加 s 的方式。同时,在这个模块中也可以加一些更强大的功能,比如传入部分的 GVR 信息就可以返回完整的 GVK。
Scheme
scheme 主要负责维护 GVK 和资源 model(Go struct) 的对应关系,资源 model 的默认值函数,以及资源不同版本相互转换的函数等。scheme 是组织 kubernetes 资源的核心。
scheme 的数据结构如下所示,主要有以下内容:
- gvkToType 维护 GVK 与资源 model 对象类型(可以理解成资源对应的 Go struct,比如 Pod struct)的关系。
- typeToGVK 维护 model 对象类型与 GVK 的关系。
- defaulterFuncs 维护 model 对象类型与默认值函数的关系。
- converter 实现资源不同版本间的转换。
- fieldLabelConversionFuncs 维护 GVK label 标签转换函数的关系。
//staging/src/k8s.io/apimachinery/pkg/runtime/scheme.go
// Scheme defines methods for serializing and deserializing API objects, a type
// registry for converting group, version, and kind information to and from Go
// schemas, and mappings between Go schemas of different versions. A scheme is the
// foundation for a versioned API and versioned configuration over time.
//
// In a Scheme, a Type is a particular Go struct, a Version is a point-in-time
// identifier for a particular representation of that Type (typically backwards
// compatible), a Kind is the unique name for that Type within the Version, and a
// Group identifies a set of Versions, Kinds, and Types that evolve over time. An
// Unversioned Type is one that is not yet formally bound to a type and is promised
// to be backwards compatible (effectively a "v1" of a Type that does not expect
// to break in the future).
//
// Schemes are not expected to change at runtime and are only threadsafe after
// registration is complete.
type Scheme struct {
// versionMap allows one to figure out the go type of an object with
// the given version and name.
gvkToType map[schema.GroupVersionKind]reflect.Type
// typeToGroupVersion 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
// TODO: resolve the status of unversioned types.
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 an array of interfaces 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 实现了一些接口,使得
- scheme 可以根据 GVK 创建资源对象
- 给资源对象赋默认值
- 识别资源对象类型
- 完成资源对象版本之间的转换
- 完成资源 label 标签的转换
// ObjectCreater interface
// ObjectCreater contains methods for instantiating an object by kind and version.
// 根据传入的 GVK 信息完成资源的创建
func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) {}
// ObjectDefaulter interface
// Default takes an object (must be a pointer) and applies any default values.
// 赋予资源默认值
func (s *Scheme) Default(src Object) {}
// ObjectTyper interface
// ObjectTyper contains methods for extracting the APIVersion and Kind of objects.
// 完成资源的类型识别
func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) {}
func (s *Scheme) Recognizes(gvk schema.GroupVersionKind) bool {}
// ObjectConvertor interface
// ObjectConvertor converts an object to a different version.
// 主要用于资源不同版本之间的相互转换
func (s *Scheme) Convert(in, out interface{}, context interface{}) error {}
func (s *Scheme) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {}
func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {}
scheme 资源注册
不同版本的资源是如何注册到 scheme 中的?其实主要依赖于 SchemeBuilder,SchemeBuilder 本质上一个函数数组。SchemeBuilder 的 Register 方法,用于把函数添加到 SchemeBuilder 的函数数组中。AddToScheme 方法,用于把 scheme 对象传入函数数组中的每一个函数,然后分别执行,也就说每个函数的作用其实是往 scheme 对象中注册点东西。
// staging/src/k8s.io/apimachinery/pkg/runtime/scheme_builder.go
type SchemeBuilder []func(*Scheme) error
func (sb *SchemeBuilder) AddToScheme(s *Scheme) error {
for _, f := range *sb {
if err := f(s); err != nil {
return err
}
}
return nil
}
func (sb *SchemeBuilder) Register(funcs ...func(*Scheme) error) {
for _, f := range funcs {
*sb = append(*sb, f)
}
}
func NewSchemeBuilder(funcs ...func(*Scheme) error) SchemeBuilder {
var sb SchemeBuilder
sb.Register(funcs...)
return sb
}
有了 SchemeBuilder 后,下面以 apps 为例,介绍该组下的资源是如何注册到 scheme 中的。
1. 添加 external 版本的资源注册函数
首先介绍 external 版本的资源是如何注册到 scheme 中的。以 v1 版本为例,介绍 v1 版本中的资源、资源初始化函数(默认值函数)、和内部版本的转换函数是如何注册到 scheme 中的。
-
资源 model 类型的注册
首先会创建相应的 SchemeBuilder 对象,并传入 addKnownTypes 函数了。通过,上述的代码可知,addKnownTypes 函数会最终被执行,而这个函数的主要作用就是把 v1 版本中常见的资源注册到 schema 中。
// staging/src/k8s.io/api/apps/v1/register.go // 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 }
-
资源默认值函数的注册
之后是默认值函数的注册,这个注册其实是在 internal 版本代码处进行注册的。其中 appsv1.SchemeBuilder 就是上述生成的 SchemeBuilder,也就是使用的是 v1 版本的 SchemeBuilder。之后,调用 Register 方法将 addDefaultingFuncs 函数添加进去,而这个函数的主要作用就是往 scheme 注册相应资源 model 的默认值函数。
// pkg/apis/apps/v1/register.go // 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 ( localSchemeBuilder = &appsv1.SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. localSchemeBuilder.Register(addDefaultingFuncs) } // pkg/apis/apps/v1/defaults.go func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } // pkg/apis/apps/v1/zz_generated.defaults.go func RegisterDefaults(scheme *runtime.Scheme) error { scheme.AddTypeDefaultingFunc(&v1.DaemonSet{}, func(obj interface{}) { SetObjectDefaults_DaemonSet(obj.(*v1.DaemonSet)) }) scheme.AddTypeDefaultingFunc(&v1.DaemonSetList{}, func(obj interface{}) { SetObjectDefaults_DaemonSetList(obj.(*v1.DaemonSetList)) }) ... return nil }
-
资源外部版本和资源内部版本相互转换函数的注册
同样引用了上述生成的 SchemeBuilder 对象,通过 Register 方法,将 RegisterConversions 函数添加进去,该函数的主要作用是往 scheme 中注册相应的资源外部版本和资源内部版本的转换函数(通过函数名也可以看出来)。
// pkg/apis/apps/v1/zz_generated.conversion.go func init() { localSchemeBuilder.Register(RegisterConversions) } // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { if err := s.AddGeneratedConversionFunc((*v1.ControllerRevision)(nil), (*apps.ControllerRevision)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1_ControllerRevision_To_apps_ControllerRevision(a.(*v1.ControllerRevision), b.(*apps.ControllerRevision), scope) }); err != nil { return err } if err := s.AddGeneratedConversionFunc((*apps.ControllerRevision)(nil), (*v1.ControllerRevision)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_apps_ControllerRevision_To_v1_ControllerRevision(a.(*apps.ControllerRevision), b.(*v1.ControllerRevision), scope) }); err != nil { return err } ... return nil }
2. 添加 internal 版本的资源注册函数
接下去介绍,internal 版本的资源是如何注册到 scheme 中的。internal 版本的资源,只注册资源 model 类型。通过源码,可以看到 internal 版本的版本号是 __internal
。
// pkg/apis/apps/register.go
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
}
3. 遍历执行添加的资源注册函数
上述更多的是生成 SchemeBuilder 对象,并往这个对象中添加相应的函数,用于往 scheme 中注册。但是,并没有开始真正地往 scheme 中注册。真正开始注册的代码,如下所示,首先获取 legacyscheme 中的 Scheme 对象,然后依次调用 AddToScheme 方法开始真正地往 legacyscheme.Scheme 中注册,也就是调用执行上述被添加到 SchemeBuilder 中的函数。
// pkg/apis/apps/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(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))
}
核心 API 资源类型
Kubernetes 官方提供了多种多样的 API 资源类型,它们都在 kubernetes/api 仓库中实现。实际上,最开始该仓库只是 kubernetes 核心仓库的一部分,后来 kubernetes API 被越来越多的其他仓库使用,例如 kubernetes/client-go、kubernetes/apimachinery、kubernetes/apiserver 等,为了避免交叉依赖,将 api 拿出来作为单独的仓库。kubernetes/api 仓库是只读仓库,所有代码都同步自 https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api 核心仓库。
-
external api 定义
从代码角度看,external api 都会带版本信息(external api 也就是我们通俗意义上所述的 Kubernetes API),位于
staging/src/k8s.io/api/
目录下,以 apps group 为例,对于 external api 来说,它 resource 定义如下。
-
internal api 定义
internal api 不会带版本信息,位于
pkg/apis/
目录中,同样以 apps group 为例,其 internal api resource 的定义在pkg/apis/apps/types.go
中,而pkg/apis/apps/
中几个带版本号的目录主要用于定义 external api 和 internal api 的相互转换。
资源类型定义
metav1 是包 k8s.io/apimachinery/pkg/apis/meta/v1 的别名,本文其他部分都将用 metav1 指代。
Kubernetes API 单资源类型基本都包含 4 个属性,
- metav1.TypeMeta:定义资源类型的公共属性,即不同资源类型都会有的属性。
- metav1.ObjectMeta:定义资源对象的公共属性,即资源对象实例可能都会有的属性。
- Spec:定义 API 资源类型的私有属性,也是不同 API 资源类型之间的区别所在。
- Status:用于描述每个资源对象的状态,这和每个资源类型紧密相关的。为什么要分离 Status 为 subresource?主要是因为 Status 的更新会比较频繁。subresource 可以进行 list&watch,也可以做权限控制,也有自己的 url。
type Pod struct {
// 从 TypeMeta 字段名可以看出该字段定义 Pod 类型的元信息,类似于面向对象编程里面
// Class 本身的元信息,类似于 Pod 类型的 API 分组、API 版本等。
metav1.TypeMeta `json:",inline"`
// ObjectMeta 字段定义单个 Pod 对象的元信息。每个 kubernetes 资源对象都有自己的元信息,
// 例如名字、命名空间、标签、注释等等,kuberentes 把这些公共的属性提取出来就是
// metav1.ObjectMeta,成为了API对象类型的父类
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// PodSpec 表示 Pod 类型的对象定义规范,最为代表性的就是 CPU、内存的资源使用。
// 这个字段和 YAML 中 spec 字段对应
Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// PodStatus 表示 Pod 的状态,比如是运行还是挂起、Pod 的 IP 等等。Kubernetes 会根据 pod 在
// 集群中的实际状态来更新 PodStatus 字段
Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
Kubernetes API 资源列表类型,描述了一组相同资源类型的对象列表,基本都包含 3 个属性,
- metav1.TypeMeta:同上,定义资源类型的公共属性。
- metav1.ListMeta:定义资源对象列表类型实例的公共属性。
- Items:存资源对象的列表。
// source code from https://github.com/kubernetes/api/blob/master/core/v1/types.go
type PodList struct {
// PodList 也需要继承 metav1.TypeMeta,毕竟对象列表也好、单体对象也好都需要类型属性。
// PodList 比 []Pod 类型在 yaml 或者 json 表达上多了类型描述,当需要根据 YAML 构建对象列表的时候,
// 就可以根据类型描述反序列成为 PodList。而 []Pod 则不可以,必须确保 YAML 就是 []Pod 序列化的
// 结果,否则就会报错。这就无法实现一个通用的对象序列化/反序列化。
metav1.TypeMeta `json:",inline"`
// 与 Pod 不同,PodList 继承了 metav1.ListMeta,metav1.ListMeta 是所有资源对象列表类型的父类,
// ListMeta 定义了所有对象列表类型实例的公共属性。
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Items 字段则是 PodList 定义的本质,表示 Pod 资源对象的列表,所以说 PodList 就是 []Pod 基础上加了一些
// 跟类型和对象列表相关的元信息
Items []Pod `json:"items" protobuf:"bytes,2,rep,name=items"`
}
metav1.TypeMeta
metav1.TypeMeta 用来描述 Kubernetes API 资源类型的公共属性,也就是不同资源类型都会有的元信息。相同资源类型的资源对象的 TypeMeta 是一致的,不同资源类型的资源对象是不一致的。主要包含以下信息,
- 资源分组及版本信息。
- kind 名字。
该对象实现了 schema.ObjectKind 接口,该接口定义了如何解码与编码这些元信息。同时 metav1.TypeMeta 实现了 GetObjectKind() 用于返回 schema.ObjectKind 类型的对象。
type ObjectKind interface {
SetGroupVersionKind(kind GroupVersionKind)
GroupVersionKind() GroupVersionKind
}
type TypeMeta struct {
Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`
APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}
func (obj *TypeMeta) SetGroupVersionKind(gvk schema.GroupVersionKind) {
obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}
func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind {
return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}
func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj }
metav1.ObjectMeta
metav1.ObjectMeta 用来定义资源对象的公共属性。相同资源类型的不同资源对象实例的 metav1.ObjectMeta 是不同的。主要包含以下信息,
- Namespace/Name。
- ResourceVersion。
- UID。
metav1.ObjectMeta 实现了 metav1.Object 和 metav1.ObjectMetaAccessor 两个接口。
- 前者定义了获取单个资源对象元信息的 Get、Set 方法;
- 后者则会返回 metav1.Object 类型的对象。
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"`
......
}
type Object interface {
GetNamespace() string
SetNamespace(namespace string)
GetName() string
SetName(name string)
GetGenerateName() string
SetGenerateName(name string)
GetUID() types.UID
SetUID(uid types.UID)
GetResourceVersion() string
SetResourceVersion(version string)
......
}
type ObjectMetaAccessor interface {
GetObjectMeta() Object
}
对于所有 Kubernetes 单体资源对象来说,由于它们都继承了 metav1.TypeMeta 和 metav1.ObjectMeta,因此所有的 API 资源对象也都默认实现了 schema.ObjectKind 和 metav1.Object 两种接口。如此一来,Kubernetes 中很多访问 API 资源对象的元信息的地方,就不需要区分对象类型,只要是 metav1.Object 接口类型的对象都可以访问。
Group 定义
Group 定义的相关代码,在讲解 schema 注册的时候有贴出来过,这里再贴一下。
- 首先对 Group 的 Name 和 Version 进行定义。
- 再将相应的注册函数添加到 SchemeBuilder 中。注册函数会往 schema 中注册 GroupVersion 和该 Group 下的资源类型。
// staging/src/k8s.io/api/core/v1/register.go
// 定义 GroupName
const GroupName = ""
// 定义 group version
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
// 定义 SchemeBuilder
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
// addKnownTypes 是把 kind 添加到 group 中,同时将该 group 下定义的资源类型注册到 schema 中。
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&Pod{},
&PodList{},
&PodStatusResult{},
...
)
// Add common types
scheme.AddKnownTypes(SchemeGroupVersion, &metav1.Status{})
// Add the watch version that applies
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
相关链接
初识 Kubernetes API 的组织结构:https://morven.life/posts/k8s-api-1/
深入 kubernetes API 的源码实现:https://morven.life/posts/k8s-api-2/
API Conventions:https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md