2. Kubernetes 核心数据结构

1. Group、Version、Resource 核心数据结构

理解 Kubernetes 核心数据结构,在阅读源码时可以事半功倍并能够深刻理解 Kubernetes 核心设计。在整个 Kubernetes 体系架构中,资源是 Kubernetes 最重要的概念,可以说 Kubernetes 的生态系统都围绕着资源运作。Kubernetes 系统虽然有相当复杂和众多的功能,但它本质上是一个资源控制系统——注册、管理、调度资源并维护资源的状态

在 Kubernetes 庞大而复杂的系统中,只有资源是远远不够的,Kubernetes 将资源再次分组和版本化,形成 Group(资源组)、Version(资源版本)、Resource(资源)。在 Kubernetes API Server 中它们又称为 APIGroup、APIVersion、APIResource。此外还有 Kind(资源种类),描述 Resource 的种类,与 Resource 为同一级别。

Kubernetes 系统支持多个 Group,每个 Group 支持多个 Version,每个 Version 支持多个 Resource,其中部分资源同时会拥有自己的子资源(即 SubResource)。例如,Deployment 资源拥有 Status 子资源。

资源组、资源版本、资源、子资源的完整表现形式为<group>/<version>/<resource>/<subresource>。以常用的 Deployment 为例,其完整表现形式为 apps/v1/deployments/status

另外,资源对象(Resource Object)在 Kubernetes 中也是一个常用概念,由“资源组+资源版本+资源种类”组成,并在实例化后表达一个资源对象,例如 Department 资源实例化后拥有资源组、资源版本以及资源种类,其表现形式为 <group>/<version>Kind=<kind>,例如 apps/v1,Kind=Deployment

每一个资源都拥有一定数量的资源操作方法(即 Verbs),资源操作方法用于 Etcd 集群存储中对资源对象的增、删、改、查操作。目前 Kubernetes 系统支持 8 种资源操作方法,分别是 create、delete、deletecollection、get、list、patch、update、watch 操作方法。

每一个资源都至少有两个版本,分别是外部版本(External Version)和内部资源(Internal Version)。外部版本用于对外暴露给用户请求的接口所使用的资源对象。内部版本不对外暴露,仅在 Kubernetes API Server 内部使用。

Kubernetes 资源也可分为两种,分别是 Kubernetes Resource(Kubernetes 内置资源)和 Custom Resource(自定义资源)。开发者通过 CRD(即 Custom Resource Definitions)可实现自定义资源,它允许用户将自己定义的资源添加到 Kubernetes 系统中,并像使用 Kubernetes 内置资源一样使用它们。

<br>

2. ResourceList

Kubernetes Group、Version、Resource 等核心数据结构存放在 vendor\k8s.io\apimachinery\pkg\apis\meta\v1\ 目录中,数据结构定义在 types.go 中。目录中包含了 Kubernetes 集群中所有组件使用的通用核心数据结构,例如 APIGroup、APIVersion、APIResource 等。

代码路径:vendor\k8s.io\apimachinery\pkg\apis\meta\v1\types.go

// APIResource specifies the name of a resource and whether it is namespaced.
type APIResource struct {
	// name is the plural name of the resource.
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
	// singularName is the singular name of the resource.  This allows clients to handle plural and singular opaquely.
	// The singularName is more correct for reporting status on a single item and both singular and plural are allowed
	// from the kubectl CLI interface.
	SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"`
	// namespaced indicates if a resource is namespaced or not.
	Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
	// group is the preferred group of the resource.  Empty implies the group of the containing resource list.
	// For subresources, this may have a different value, for example: Scale".
	Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"`
	// version is the preferred version of the resource.  Empty implies the version of the containing resource list
	// For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)".
	Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"`
	// kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')
	Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
	// verbs is a list of supported kube verbs (this includes get, list, watch, create,
	// update, patch, delete, deletecollection, and proxy)
	Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
	// shortNames is a list of suggested short names of the resource.
	ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
	// categories is a list of the grouped resources this resource belongs to (e.g. 'all')
	Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
	// The hash value of the storage version, the version this resource is
	// converted to when written to the data store. Value must be treated
	// as opaque by clients. Only equality comparison on the value is valid.
	// This is an alpha feature and may change or be removed in the future.
	// The field is populated by the apiserver only if the
	// StorageVersionHash feature gate is enabled.
	// This field will remain optional even if it graduates.
	// +optional
	StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"`
}

// Verbs masks the value so protobuf can generate
//
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type Verbs []string

func (vs Verbs) String() string {
	return fmt.Sprintf("%v", []string(vs))
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// APIResourceList is a list of APIResource, it is used to expose the name of the
// resources supported in a specific group and version, and if the resource
// is namespaced.
type APIResourceList struct {
	TypeMeta `json:",inline"`
	// groupVersion is the group and version this APIResourceList is for.
	GroupVersion string `json:"groupVersion" protobuf:"bytes,1,opt,name=groupVersion"`
	// resources contains the name of the resources and if they are namespaced.
	APIResources []APIResource `json:"resources" protobuf:"bytes,2,rep,name=resources"`
}

// APIVersions lists the versions that are available, to allow clients to
// discover the API at /api, which is the root path of the legacy v1 API.
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type APIVersions struct {
	TypeMeta `json:",inline"`
	// versions are the api versions that are available.
	Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
	// a map of client CIDR to server address that is serving this group.
	// This is to help clients reach servers in the most network-efficient way possible.
	// Clients can use the appropriate server address as per the CIDR that they match.
	// In case of multiple matches, clients should use the longest matching CIDR.
	// The server returns only those CIDRs that it thinks that the client can match.
	// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
	// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
	ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// APIGroupList is a list of APIGroup, to allow clients to discover the API at
// /apis.
type APIGroupList struct {
	TypeMeta `json:",inline"`
	// groups is a list of APIGroup.
	Groups []APIGroup `json:"groups" protobuf:"bytes,1,rep,name=groups"`
}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// APIGroup contains the name, the supported versions, and the preferred version
// of a group.
type APIGroup struct {
	TypeMeta `json:",inline"`
	// name is the name of the group.
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
	// versions are the versions supported in this group.
	Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"`
	// preferredVersion is the version preferred by the API server, which
	// probably is the storage version.
	// +optional
	PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty" protobuf:"bytes,3,opt,name=preferredVersion"`
	// a map of client CIDR to server address that is serving this group.
	// This is to help clients reach servers in the most network-efficient way possible.
	// Clients can use the appropriate server address as per the CIDR that they match.
	// In case of multiple matches, clients should use the longest matching CIDR.
	// The server returns only those CIDRs that it thinks that the client can match.
	// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
	// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
	// +optional
	ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
}

其中,我们可以通过 APIResourceList 数据结构描述所有 Group、Version、Resource 的结构,以最常用的 Pod、Service、Deployment 资源为例,APIResourceList Example 代码示例如下:

resourceList := []*metav1.APIResourceList{
	{
		GroupVersion: "v1",
		APIResources: []metav1.APIResource{
			{
				Name: "pods",
				Namespaced: true,
				Kind: "Pod",
				Verbs: []string{"get", "list", "delete", "create", "update", "patch", "watch"},
			},
			{
				Name: "services",
				Namespaced: true,
				Kind: "Service",
				Verbs: []string{"get", "list", "delete", "create", "update", "patch", "watch"},
			},
		},

	},	
	{
		GroupVersion: "apps/v1",
		APIResources: []metav1.APIResource{
			{
				Name: "deployments",
				Namespaced: true,
				Kind: "Deployment",
				Verbs: []string{"get", "list", "delete", "create", "update", "patch", "watch"},
			},
		},

	},
}

Kubernetes 的每个资源可使用 metav1.APIResource 结构进行描述,它描述资源的基本信息,例如资源名称(即 Name 字段)、资源所属的命名空间(即 NameSpaced 字段)、资源种类(即 Kind 字段)、资源可操作的方法列表(即 Verbs 字段)。

每一个资源都属于一个或多个资源版本,资源所属的版本通过 metav1.APIResource 结构描述,一个或多个资源版本通过 Versions []string 字符串数组进行存储。

在 APIResourceList Example 代码示例中,通过 GroupVersion 字段来描述资源组和资源版本,它是一个字符串,当资源同时存在资源组和资源版本时,它被设置为 <group>/<version>;当资源不存在资源组(Core Group)时,它被设置为 /<version>。可以看到 Pod、Service 资源属于 v1 版本,而 Deployment 资源属于 apps 资源组下的 v1 版本。

另外,可以通过 Group、Version、Resource 结构来明确标识一个资源的资源组名称、资源版本以及资源名称。Group、Version、Resource 简称 GVR,在 Kubernetes 源码中该数据结构被大量使用,它被定义在 vendor\k8s.io\apimachinery\pkg\runtime\schema\ 目录下。

代码路径: vendor\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
}

以 Deployment 资源为例,资源信息描述如下:

schema.GroupVersionResource {
	Group:    "apps",
	Version:  "v1",
	Resource: "deployments"
}

vendor\k8s.io\apimachinery\pkg\runtime\schema\ 包中定义了常用的资源数据结构,如表所示:

GroupVersionResource

GVR

描述资源组、资源版本、资源

GroupVersion

GV

描述资源组、资源版本

GroupResource

GR

描述资源组、资源

GroupVersionKind

GVK

描述资源组、资源版本、资源类型

GroupKind

GK

描述资源组、资源类型

GroupVersions

GVS

描述资源组内多个资源版本

<br>

3. Group

Group(资源组),在 Kubernetes API Server 中也可以称其为 APIGroup。Kubernetes 系统中定义了许多资源组,这些资源组按照不同功能将资源进行了划分,资源组特点如下:

  • 将众多资源按照功能划分成不同的资源组,并允许单独启用/禁用资源组,当然也可以单独启用/禁用资源组中的资源。
  • 支持不同资源组中拥有不同的资源版本。这方便组内的资源根据版本进行迭代升级。
  • 支持同名的资源种类(即 Kind)存在于不同的资源组内。
  • 资源组与资源版本通过 Kubernetes API Server 对外暴露,允许开发者通过 HTTP 协议进行交互并通过动态客户端(即 DynamicClient)进行资源发现。
  • 支持 CRD 自定义资源扩展。
  • 用户交互简单,例如在使用 kubectl 命令行工具时,可以不填写资源组名称。

代码路径:vendor\k8s.io\apimachinery\pkg\apis\meta\v1\types.go

// APIGroup contains the name, the supported versions, and the preferred version
// of a group.
type APIGroup struct {
	TypeMeta `json:",inline"`
	// name is the name of the group.
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
	// versions are the versions supported in this group.
	Versions []GroupVersionForDiscovery `json:"versions" protobuf:"bytes,2,rep,name=versions"`
	// preferredVersion is the version preferred by the API server, which
	// probably is the storage version.
	// +optional
	PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty" protobuf:"bytes,3,opt,name=preferredVersion"`
	// a map of client CIDR to server address that is serving this group.
	// This is to help clients reach servers in the most network-efficient way possible.
	// Clients can use the appropriate server address as per the CIDR that they match.
	// In case of multiple matches, clients should use the longest matching CIDR.
	// The server returns only those CIDRs that it thinks that the client can match.
	// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
	// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
	// +optional
	ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs,omitempty" protobuf:"bytes,4,rep,name=serverAddressByClientCIDRs"`
}

资源组数据结构字段说明如下:

  • Name:资源组名称。
  • Versions:资源组下所支持的资源版本。
  • PreferredVersion:首选版本。当一个资源组内存在多个资源版本时,Kubernetes API Server 在使用资源时会选择一个首选版本作为当前版本。

在当前的 Kubernetes 系统中,支持两类资源组,分别是拥有组名的资源组和没有组名的资源组。

  • 拥有组名的资源组:其表现形式为 <group>/<version>/<resource>,例如 apps/v1/deployments
  • 没有组名的资源组:被称为 Core Groups(即核心资源组)或 Legacy Groups,也可被称为 GroupLess(即无组)。其表现形式为 /<version>/<resource>,例如 /v1/pods

两类资源组表现形式不同,形成的 HTTP PATH 路径也不同。拥有组名的资源组的 HTTP PATH 以 /apis 为前缀,其表现形式为 /apis/<group>/<version>/<resource>,例如 http://localhost:8080/apis/apps/v1/deployments。没有组名的资源组的 HTTP PATH 以 /api 为前缀,其表现形式为 /api/<version>/<resource>,例如 http://localhost:8080/api/v1/pods

<br>

4. Version

Kubernetes 的资源版本控制可分为 3 种,分别是 Alpha、Beta、Stable,它们之间的迭代顺序为 Alpha -> Beta -> Stable,其通常用来表示软件测试过程的 3 个阶段。Alpha 是第 1 个阶段,一般用于内部测试;Beta 是第 2 个阶段,该版本已经修复了大部分不完善之处,但仍有可能存在缺陷和漏洞,一般由特定的用户群来进行测试;Stable 是第 3 个阶段,此时基本形成了产品并达到了一定的成熟度,可稳定运行。Kubernetes 资源版本控制详情如下。

4.1 Alpha 版本

Alpha 版本为内部测试版本,用于 Kubernetes 开发者内部测试,该版本是不稳定的,可能存在很多缺陷和漏洞,官方随时可能会放弃支持该版本。在默认情况下,处于 Alpha 版本的功能会被禁用。Alpha 版本名称一般为 v1alpha1、v1alpha2、v2alpha1 等。

4.2 Beta 版本

Beta 版本为相对稳定的版本,Beta 版本经过官方和社区很多次测试,当功能迭代时,该版本会有较小的改变,但不会被删除。在默认情况下,处于 Beta 版本的功能时开启状态的。Beta 版本一般命名为 v1beta1、v1beta2、v2beta1。

4.3 Stable 版本

Stable 版本为正式发布的版本,Stable 版本进本形成了产品,该版本不会被删除。在默认的情况下,处于 Stable 版本的功能全部处于开启状态。Stable 版本命名一般为 v1、v2、v3。

下面以 apps 资源组为例,该资源组下的所有资源分别属于 v1、v1beta1、v1beta2 资源版本,如图:

代码路径:vendor\k8s.io\apimachinery\pkg\apis\meta\v1\types.go

// APIVersions lists the versions that are available, to allow clients to
// discover the API at /api, which is the root path of the legacy v1 API.
//
// +protobuf.options.(gogoproto.goproto_stringer)=false
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type APIVersions struct {
	TypeMeta `json:",inline"`
	// versions are the api versions that are available.
	Versions []string `json:"versions" protobuf:"bytes,1,rep,name=versions"`
	// a map of client CIDR to server address that is serving this group.
	// This is to help clients reach servers in the most network-efficient way possible.
	// Clients can use the appropriate server address as per the CIDR that they match.
	// In case of multiple matches, clients should use the longest matching CIDR.
	// The server returns only those CIDRs that it thinks that the client can match.
	// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
	// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
	ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs" protobuf:"bytes,2,rep,name=serverAddressByClientCIDRs"`
}

<br>

5. Resource

在整个 Kubernetes 体系架构中,资源是 Kubernetes 最重要的概念,可以说 Kubernetes 的生态系统都围绕着资源运作。Kubernetes 系统虽然有相当复杂和众多的功能,但它本质上是一个资源控制系统——管理、调度资源并维护资源的状态。

一个资源被实例化后会表达为一个资源对象(即 Resource Object)。在 Kubernetes 系统中定义并运行着各式各样的资源对象。

代码路径:vendor\k8s.io\apimachinery\pkg\apis\meta\v1\types.go

// APIResource specifies the name of a resource and whether it is namespaced.
type APIResource struct {
	// name is the plural name of the resource.
	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
	// singularName is the singular name of the resource.  This allows clients to handle plural and singular opaquely.
	// The singularName is more correct for reporting status on a single item and both singular and plural are allowed
	// from the kubectl CLI interface.
	SingularName string `json:"singularName" protobuf:"bytes,6,opt,name=singularName"`
	// namespaced indicates if a resource is namespaced or not.
	Namespaced bool `json:"namespaced" protobuf:"varint,2,opt,name=namespaced"`
	// group is the preferred group of the resource.  Empty implies the group of the containing resource list.
	// For subresources, this may have a different value, for example: Scale".
	Group string `json:"group,omitempty" protobuf:"bytes,8,opt,name=group"`
	// version is the preferred version of the resource.  Empty implies the version of the containing resource list
	// For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)".
	Version string `json:"version,omitempty" protobuf:"bytes,9,opt,name=version"`
	// kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')
	Kind string `json:"kind" protobuf:"bytes,3,opt,name=kind"`
	// verbs is a list of supported kube verbs (this includes get, list, watch, create,
	// update, patch, delete, deletecollection, and proxy)
	Verbs Verbs `json:"verbs" protobuf:"bytes,4,opt,name=verbs"`
	// shortNames is a list of suggested short names of the resource.
	ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,5,rep,name=shortNames"`
	// categories is a list of the grouped resources this resource belongs to (e.g. 'all')
	Categories []string `json:"categories,omitempty" protobuf:"bytes,7,rep,name=categories"`
	// The hash value of the storage version, the version this resource is
	// converted to when written to the data store. Value must be treated
	// as opaque by clients. Only equality comparison on the value is valid.
	// This is an alpha feature and may change or be removed in the future.
	// The field is populated by the apiserver only if the
	// StorageVersionHash feature gate is enabled.
	// This field will remain optional even if it graduates.
	// +optional
	StorageVersionHash string `json:"storageVersionHash,omitempty" protobuf:"bytes,10,opt,name=storageVersionHash"`
}

5.1 资源外部版本和内部版本

Kubernetes 资源代码定义在 /pkg/apis 目录下,在详解资源代码定义之前,先来了解一下资源的外部版本(External Version)和内部版本(Internal Version)。在 Kubernetes 系统中,同一个资源对应着两个版本,分别是外部版本和内部版本。例如,Deployment 资源,它所属的外部版本表现形式为 apps/v1,内部版本表现形式为 apps/__internal

  • External Object:外部版本资源对象,也称为 Versioned Object(即拥有资源版本的资源对象)。外部版本用于对外暴露给用户请求的接口所使用的资源对象,例如,用户在通过 YAML 或 JSON 格式的描述文件创建资源对象时,所使用的时外部版本的资源对象。外部版本的资源对象通过资源版本(Alpha、Beta、Stable)进行标识。
  • Internal Object:内部版本资源对象。内部版本不对外暴露,仅在 Kubernetes API Server 内部使用。内部版本用于多资源版本的转换,例如将 v1beta1 版本转换为 v1 版本,其过程为 v1beta1 -> internal -> v1,即先将 v1beta1 转换为内部版本,再转换为 v1 版本。内部版本资源对象通过 runtime.APIVersionInternal(即 __internal)进行标识。

资源的外部版本代码定义在 pkg/apis/<group>/<version>/ 目录下,资源的内部版本代码定义在 pkg/apis/<group> 目录下。例如Deployment 资源,它的外部版本定义在 pkg/apis/apps/{v1,v1beta1,v1beta2}/ 目录下,它的内部版本定义在 pkg/apis/apps/ 目录下(内部版本一般与资源组在同一级目录下)。

资源的外部版本和内部版本时需要相互转换的,而用于转换的函数需要事先初始化到资源注册表(Scheme)中。多个外部版本(External Version)之间的资源进行相互转换,都需要通过内部版本(Internal Version)进行中转。这也是 Kubernetes 能实现多资源版本转换的关键。

在 Kubernetes 源码中,外部版本的资源类型定义在 vendor/k8s.io/api 目录下,其完整描述路径为 vendor/k8s.io/api/<group>/<version>/<resource file>

例如,Pod 资源的外部版本,定义在 vendor/k8s.io/api/core/v1 目录下。

资源的外部版本与内部版本的代码定义也不太一样,外部版本的资源需要对外暴露给用户请求的接口,所以资源代码定义了 JSON Tags 和 Proto Tags,用于请求的序列化和反序列化操作。内部版本的资源不对外暴露,所以没有任何的 JSON Tags 和 Proto Tags 定义。以 Pod 资源代码定义为例,代码示例如下:

Pod 资源的外部版本代码定义如下:

代码路径:vendor\k8s.io\api\core\v1\types.go

// Pod is a collection of containers that can run on a host. This resource is created
// by clients and scheduled onto hosts.
type Pod struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`

	// Specification of the desired behavior of the pod.
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
	// +optional
	Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

	// Most recently observed status of the pod.
	// This data may not be up to date.
	// Populated by the system.
	// Read-only.
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
	// +optional
	Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

Pod 资源的内部版本代码定义如下:

代码路径:pkg\apis\core\types.go

// Pod is a collection of containers, used as either input (create, update) or as output (list, get).
type Pod struct {
	metav1.TypeMeta
	// +optional
	metav1.ObjectMeta

	// Spec defines the behavior of a pod.
	// +optional
	Spec PodSpec

	// Status represents the current information about a pod. This data may not be up
	// to date.
	// +optional
	Status PodStatus
}

5.2 资源代码定义

Kubernetes 资源代码定义在 pkg/apis 目录下,同一资源对应着内部版本和外部版本,内部版本和外部版本资源代码结构并不相同。

资源的内部版本定义了所支持的资源类型(types.go)、资源验证方法(validation.go)、资源注册至资源注册表的方法(install/install.go)等。而资源的外部版本定义了资源的转换方法(conversion.go)、资源的默认值(defaults.go)等。

以 Deployment 资源为例,它的内部版本定义在 pkg/apis/apps 目录下,其资源代码结构如下:

内部版本的资源代码结构说明如下:

  • doc.go:GoDoc 文件,定义了当前包的注释信息。在 Kubernetes 资源包中,它还担当了代码生成器的全局 Tags 描述文件。
  • register.go:定义了资源组、资源版本及资源的注册信息。
  • types.go:定义了当前资源组、资源版本下所支持的资源类型。
  • v1、v1beta1、v1beta2:定义了资源组下拥有的资源版本的资源(即外部版本)。
  • install:把当前资源组下的所有资源注册到资源注册表中。
  • validation:定义了资源的验证方法。
  • zz_generated.deepcopy.go:定义了资源的深复制操作,该文件由代码生成器自动生成。

每一个 Kubernetes 资源目录,都通过 register.go 代码文件定义所属的资源组和资源版本,内部版本资源对象通过 runtime.APIVersionInternal(即 __internal)标识,代码示例如下:

// 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}

每一个 Kubernetes 资源目录,都通过 types.go 代码文件定义当前资源组/资源版本下所支持的资源类型,代码示例如下:

...
type Deployment struct {
	metav1.TypeMeta
	// +optional
	metav1.ObjectMeta

	// Specification of the desired behavior of the Deployment.
	// +optional
	Spec DeploymentSpec

	// Most recently observed status of the Deployment.
	// +optional
	Status DeploymentStatus
}
type ControllerRevision struct {
	metav1.TypeMeta
	// Standard object's metadata.
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta

	// Data is the Object representing the state.
	Data runtime.Object

	// Revision indicates the revision of the state represented by Data.
	Revision int64
}
...

例如Deployment 资源,它的外部版本定义在 pkg/apis/apps/{v1,v1beta1,v1beta2}/ 目录下,其资源代码结构如下:

外部版本的资源代码结构说明如下:

  • doc.go:GoDoc 文件,定义了当前包的注释信息。在 Kubernetes 资源包中,它还担当了代码生成器的全局 Tags 描述文件。
  • register.go:定义了资源组、资源版本及资源的注册信息。
  • types.go:定义了当前资源组、资源版本下所支持的资源类型,在 vendor/k8s.io/api/apps/{v1,v1beta1,v1beta2}/ 目录下可以找到。
  • conversion.go:定义了资源的转换函数(默认转换函数),并将默认转换函数注册到资源注册表中。
  • zz_generated.conversion.go:定义了资源的转换函数(自动生成的转换函数),并将自动生成的转换函数注册到资源注册表中。该文件由代码生成器自动生成。
  • default.go:定义了资源的默认值函数,并将默认值函数注册到资源注册表中。
  • zz_generated.default.go:定义了资源的默认值函数(自动生成的默认值函数),并将默认值函数注册到资源注册表中。该文件由代码生成器自动生成。

外部版本与内部版本资源类型相同,都通过 register.go 代码文件定义所属的资源组和资源版本,外部版本资源对象通过资源版本(Alpha、Beta、Stable)标识,代码示例如下:

// 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"}

5.3 将资源注册到注册表中

在每一个 Kubernetes 资源目录,都拥有一个 install/install.go 代码文件,它负责将资源信息注册到资源注册表(Scheme)中。以 core 核心资源组为例,代码示例如下:

代码路径:pkg\apis\core\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(core.AddToScheme(scheme))
	utilruntime.Must(v1.AddToScheme(scheme))
	utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion))
}

legacyscheme.Scheme 是 kube-apiserver 组件的全局资源注册表,Kubernetes 的所有资源信息都交给资源注册表统一管理。core.AddToScheme 函数注册 core 资源组内部版本的资源。v1.AddToScheme 函数注册 core 资源组外部版本的资源。scheme.SetVersionPriority 函数注册资源组的版本顺序,如有多个资源版本,排在最前面的为资源首选版本。

5.4 资源首选版本

首选版本(Preferred Version),也称为优选版本(Priority Version),一个资源组下拥有多个资源版本,例如,apps 资源组拥有 v1, v1beta2, v1beta1 等资源版本,则使用该资源的首选版本。

以 apps 资源组为例,注册资源时会注册多个资源版本,分别是 v1, v1beta2, v1beta1,代码示例如下:

代码路径:pkg\apis\apps\install\install.go

// 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.SetVersionPriority 注册版本顺序很重要,apps 资源组的版本注册顺序为 v1, v1beta2, v1beta1。当通过资源注册表 scheme.PreferredVersionAllGroups 函数获取所有资源组下的首选版本时,将位于最前面的资源版本作为首选版本,代码示例如下:

代码路径:vendor\k8s.io\apimachinery\pkg\runtime\scheme.go

// PreferredVersionAllGroups returns the most preferred version for every group.
// group ordering is random.
func (s *Scheme) PreferredVersionAllGroups() []schema.GroupVersion {
	ret := []schema.GroupVersion{}
	for group, versions := range s.versionPriority {
		for _, version := range versions {
			ret = append(ret, schema.GroupVersion{Group: group, Version: version})
			break
		}
	}
	for _, observedVersion := range s.observedVersions {
		found := false
		for _, existing := range ret {
			if existing.Group == observedVersion.Group {
				found = true
				break
			}
		}
		if !found {
			ret = append(ret, observedVersion)
		}
	}

	return ret
}

5.5 资源操作方法

在 Kubernetes 系统中,针对每一个资源都有一定的操作方法(即 Vebs),例如,对于 Pod 资源对象,可以通过 kubectl 命令行工具对其执行 create、delete、get 等操作。Kubernetes 系统所支持的操作方法目前有 8 种操作,分别是 create、delete、deletecollection、get、list、patch、update、watch。这些操作方法分为四大类,分别属于增、删、改、查,对资源进行创建、删除、更新和查询。资源操作方法可以通过 metav1.Verbs 数据结构进行描述,代码示例如下:

代码路径:vendor\k8s.io\apimachinery\pkg\apis\meta\v1\types.go

// Verbs masks the value so protobuf can generate
//
// +protobuf.nullable=true
// +protobuf.options.(gogoproto.goproto_stringer)=false
type Verbs []string

func (vs Verbs) String() string {
	return fmt.Sprintf("%v", []string(vs))
}

不同资源拥有不同的操作方法,例如,针对 Pod 资源对象与 pod/logs 子资源对象,Pod 资源对象拥有 create、delete、deletecollection、get、list、patch、update、watch 等操作方法,pod/logs 子资源对象只拥有 get 操作方法,因为日志只需要执行查看操作。Pod 资源对象与 pod/logs 子资源对象的操作方法分别通过 metav1.Verbs 数据结构进行描述:

// pod verbs
podVerbs := metav1.Verbs([]string{"create","delete","deletecollection","get","list","patch","update","watch"})

// pod/logs verbs
podLogVerbs := metav1.Verbs([]string{"get"})

资源对象的操作方法与存储(Storage)相关联,增、删、改、查实际上都是针对存储的操作。如何了解一个资源对象拥有哪些可操作的方法呢?需要查看与存储相关的源码包 registry,其定义在 vendor\k8s.io\apiserver\pkg\registry 目录下。每种操作方法对应一个操作方法接口(Interface),资源对象操作方法接口说明如表:

create

rest.Creater

资源对象创建接口

delete

rest.GrecefulDeleter

资源对象删除接口(单个资源对象)

deletecollection

rest.CollectionDeleter

资源对象删除接口(多个资源对象)

update

rest.Updater

资源对象更新接口(完整资源对象更新)

patch

rest.Patcher

资源对象更新接口(局部资源对象更新)

get

rest.Getter

资源对象获取接口(单个资源对象)

list

rest.Lister

资源对象获取接口(多个资源对象)

watch

rest.Watcher

资源对象监控接口

以 get、create 操作方法为例,rest.Getter 接口定义了 Get 方法,rest.Creater 接口定义了 New 和 Create 方法。如果某个资源对象在存储(Storage)上实现了 Get、New 和 Create 方法,就可以认为该资源对象同时拥有了 get 和 create 操作方法。相关接口定义如下:

代码路径:vendor\k8s.io\apiserver\pkg\registry\rest\rest.go

// Getter is an object that can retrieve a named RESTful resource.
type Getter interface {
	// Get finds a resource in the storage by name and returns it.
	// Although it can return an arbitrary error value, IsNotFound(err) is true for the
	// returned error value err when the specified resource is not found.
	Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error)
}

// Creater is an object that can create an instance of a RESTful object.
type Creater interface {
	// New returns an empty object that can be used with Create after request data has been put into it.
	// This object must be a pointer type for use with Codec.DecodeInto([]byte, runtime.Object)
	New() runtime.Object

	// Create creates a new version of a resource.
	Create(ctx context.Context, obj runtime.Object, createValidation ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error)
}

以 Pod 资源对象为例,Pod 资源对象的存储(Storage)实现了以上接口的方法,Pod 资源对象继承了 genericregistry.Store,该对象可以管理存储(Storage)的增、删、改、查操作,代码示例如下:

代码路径:pkg\registry\core\pod\storage\storage.go

// PodStorage includes storage for pods and all sub resources
type PodStorage struct {
	Pod                 *REST
...
}

// REST implements a RESTStorage for pods
type REST struct {
	*genericregistry.Store
	proxyTransport http.RoundTripper
}

代码路径:vendor\k8s.io\apiserver\pkg\registry\generic\registry\store.go

// Create inserts a new item according to the unique key from the object.
// Note that registries may mutate the input object (e.g. in the strategy
// hooks).  Tests which call this might want to call DeepCopy if they expect to
// be able to examine the input and output objects for differences.
func (e *Store) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
	var finishCreate FinishFunc = finishNothing
...

pod/logs 子资源对象为例,该资源对象只实现了 get 操作方法,代码示例如下:

代码路径:pkg\registry\core\pod\storage\storage.go

// PodStorage includes storage for pods and all sub resources
type PodStorage struct {
	Log                 *podrest.LogREST
...
}

// REST implements a RESTStorage for pods
type REST struct {
	*genericregistry.Store
	proxyTransport http.RoundTripper
}

代码路径:pkg\registry\core\pod\rest\log.go

// Get retrieves a runtime.Object that will stream the contents of the pod log
func (r *LogREST) Get(ctx context.Context, name string, opts runtime.Object) (runtime.Object, error) {
...

5.6 资源和命名空间

Kubernetes 系统支持命名空间(Namespace),其用来解决 Kubernetes 集群中资源对象过多导致管理复杂的问题。每个命名空间相当于一个“虚拟集群”,不同命名空间之间可以进行隔离,当然也可以通过某种方式跨命名空间通信。

5.7 自定义资源

Kubernetes 系统拥有强大的高扩展功能,其中自定义资源就是一种常见的扩展方式,即可将自己定义的资源添加到 Kubernetes 系统中。

5.8 资源对象描述文件定义

Kubernetes 资源可分为内置资源(Kubernetes Resources)和自定义资源(Custom Resources),它们都通过资源对象描述文件(Manifest File)进行定义,资源对象描述文件如下:

一个资源对象需要用 5 个字段来描述它,分别是 Group/Version、Kind、MetaData、Spec、Status。这些字段定义在 YAML 或 JSON 文件中。Kubernetes 系统中的所有资源对象都可以采用 YAML 或 JSON 格式的描述文件来定义,下面是某个 Pod 文件的资源对象描述文件。示例如下:

apiVersion: v1
kind: Pod
metadata:
  name: my-first-pod
spec:
  containers:
    - name: my-first-container
      image: nginx:latest
      ports:
        - containerPort: 80

  • apiVersion:指定创建资源对象的资源组和资源版本,其表现形式为 <group>/<version>,若是 core 资源组下的资源对象,其表现形式为 <version>
  • kind:指定创建资源对象的种类。
  • metadata:描述创建资源对象的元数据信息,例如名称、命名空间等。
  • spec:包含有关 Pod 资源对象的核心信息,告诉 Kubernetes 期望的资源状态、副本数量、环境变量、卷等信息。
  • status:包含有关正在运行的 Pod 资源对象的信息。

每一个 Kubernetes 资源对象都包含两个嵌套字段,即 spec 字段和 status 字段。其中 spec 字段是必需的,它描述了资源对象的“期望状态”,而 status 字段用于描述资源对象的“实际状态”,它是 Kubernetes 系统提供和更新的。在任何时刻,Kubernetes 控制器一直努力地管理着对象的实际状态以与期望状态相匹配。

<br>

6. runtime.Object 类型基石

Runtime 被称为 “运行时”,我们在很多其他程序或语言中见过它,它一般指程序或语言核心库的实现。Kubernetes Runtime 在 vendor\k8s.io\apimachinery\pkg\runtime 中实现,它提供了通用资源类型 runtime.Object

runtime.Object 是 Kubernetes 类型系统的基石。Kubernetes 上的所有资源对象(Resource Object)实际上就是一种 Go 语言的 Struct 类型,相当于一种数据结构,它们都有一种共同的结构叫 runtime.Objectruntime.Object 被设计为 Interface 接口类型,作为资源对象的通用资源对象,runtime.Object 类型基石如图:

以资源对象 Pod 为例,该资源对象可以转换成 runtime.Object 通用资源对象,也可以从 runtime.Object 通用资源对象转换成 Pod 资源对象。runtime.Object 结构如下:

代码路径:vendor\k8s.io\apimachinery\pkg\runtime\interfaces.go

// Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are
// expected to be serialized to the wire, the interface an Object must provide to the Scheme allows
// serializers to set the kind, version, and group the object is represented as. An Object may choose
// to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized.
type Object interface {
	GetObjectKind() schema.ObjectKind
	DeepCopyObject() Object
}

代码路径:vendor\k8s.io\apimachinery\pkg\runtime\schema\interfaces.go

// All objects that are serialized from a Scheme encode their type information. This interface is used
// by serialization to set type information from the Scheme onto the serialized version of an object.
// For objects that cannot be serialized or have unique requirements, this interface may be a no-op.
type ObjectKind interface {
	// SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil
	// should clear the current setting.
	SetGroupVersionKind(kind GroupVersionKind)
	// GroupVersionKind returns the stored group, version, and kind of an object, or an empty struct
	// if the object does not expose or provide these fields.
	GroupVersionKind() GroupVersionKind
}

runtime.Object 提供了两个方法,分别是 GetObjectKind 和 DeepCopyObject。

  • GetObjectKind:用于设置并返回 GroupVersionKind。
  • DeepCopyObject:用于深度复制当前资源对象并返回。

深度复制相当于将数据结构克隆一份,因此它不与原始对象共享任何内容。它使代码在不修改原始对象的情况下可以改变克隆对象的任何属性。

那么,如何确认一个资源对象是否可以转换成 runtime.Object 通用资源对象呢?

这时需要确认该资源对象是否拥有 GetObjectKind 和 DeepCopyObject 方法。Kubernetes 的每一个资源对象都嵌入了 metav1.TypeMeta 类型,metav1.TypeMeta 类型实现了 GetObjectKind 方法,所以资源对象拥有该方法。另外,Kubernetes 的每一个资源对象都实现了 DeepCopyObject 方法,该方法一般被定义在 zz_generated.deepcopy.go 文件中。因此,可以认为该资源对象能够转换成 runtime.Object 通用资源对象。

所以,Kubernetes 的任意资源对象都可以通过 runtime.Object 通用资源对象存储它的类型并允许深度复制操作

<br>

7. Unstructured 数据

数据可以分为结构化数据(Structured Data)和非结构化数据(Unstructured Data)。Kubernetes 内部会经常处理这两种数据。

7.1 结构化数据

预先知道数据结构的数据类型是结构化数据。例如,JSON 数据:

{
	"id": 1,
	"name": "Derek"
}

要使用这种数据,需要创建一个 struct 数据结构,其具有 id 和 name 属性:

type Student struct {
	ID int
	Name string
}

s := '{"id":1,"name":"Derek"}'
var student Student
err := json.Unmarshal([]byte(s), &student)

通过 Go 语言的 json 库进行反序列化操作,将 id 和 name 属性映射到 struct 中对应的 ID 和 Name 属性中。

7.2 非结构化数据

无法预知数据结构的数据类型或属性名称不确定的数据类型是非结构化数据,其无法通过构建预定的 struct 数据结构来序列号或反序列化数据。例如:

{
	"id": 1,
	"name": "Derek",
	"description": ...
}

我们无法事先得知 description 的数据类型,它可能是字符串,也可能是数据嵌套等。原因在于 Go 语言是强类型语言,它需要预先知道数据类型,Go 语言在处理 JSON 数据时不如动态语言那样便捷。当无法预知数据结构的数据类型或属性名称不确定时,通过如下结构来解决问题:

var result map[string]interface{}

每个字符串对应一个 JSON 属性,其映射 interface{} 类型对应值,可以是任何类型。使用 interface 字段时,通过 Go 语言断言的方式进行类型转换。

if description, ok := result["description"].(string); ok {
	fmt.Println(description)
}

7.3 Kubernetes 非结构化数据处理

代码路径:vendor\k8s.io\apimachinery\pkg\runtime\interfaces.go

// Unstructured objects store values as map[string]interface{}, with only values that can be serialized
// to JSON allowed.
type Unstructured interface {
	Object
	// NewEmptyInstance returns a new instance of the concrete type containing only kind/apiVersion and no other data.
	// This should be called instead of reflect.New() for unstructured types because the go type alone does not preserve kind/apiVersion info.
	NewEmptyInstance() Unstructured
	// UnstructuredContent returns a non-nil map with this object's contents. Values may be
	// []interface{}, map[string]interface{}, or any primitive type. Contents are typically serialized to
	// and from JSON. SetUnstructuredContent should be used to mutate the contents.
	UnstructuredContent() map[string]interface{}
	// SetUnstructuredContent updates the object content to match the provided map.
	SetUnstructuredContent(map[string]interface{})
	// IsList returns true if this type is a list or matches the list convention - has an array called "items".
	IsList() bool
	// EachListItem should pass a single item out of the list as an Object to the provided function. Any
	// error should terminate the iteration. If IsList() returns false, this method should return an error
	// instead of calling the provided function.
	EachListItem(func(Object) error) error
}

代码路径:vendor\k8s.io\apimachinery\pkg\apis\meta\v1\unstructured\unstructured.go

type Unstructured struct {
	// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
	// map[string]interface{}
	// children.
	Object map[string]interface{}
}

在上述代码中,Kubernetes 非结构化数据通过 map[string]interface{} 表达,并提供接口。在 client-go 编程交互式的 DynamicClient 内部,实现了 Unstructured 类型,用于处理非结构化数据。

<br>

8. Scheme 资源注册表

在使用 Windows 操作系统时都应该听说过,当在操作系统上安装应用程序时,该程序的一些信息会注册到注册表中;当从操作系统上卸载应用程序时,会从注册表中删除该程序的相关信息。而 Kubernetes Scheme 资源注册表类似于 Windows 操作系统上的注册表,只不过注册的是资源类型。

Kubernetes 系统拥有众多资源,每一种资源就是一个资源类型,这些资源类型需要有统一的注册、存储、查询、管理等机制。目前 Kubernetes 系统中的所有资源类型都已注册到 Scheme 资源注册表中,其是一个内存型的资源注册表,拥有如下特点:

  • 支持注册多种资源类型,包括内部版本和外部版本。
  • 支持多种版本转换机制。
  • 支持不同资源的序列号/反序列号机制。

Scheme 资源注册表支持两种资源类型(Type)的注册,分别是 UnversionedType 和 KnownType 资源类型,分别介绍如下:

  • UnversionedType:无版本资源类型,这是一个早期 Kubernetes 系统中的概念,它主要应用于某些没有版本的资源类型,该类型的资源对象并不需要进行转换。在目前的 Kubernetes 发行版本中,无版本类型已被弱化,几乎所有资源对象都拥有版本,但在 metav1 元数据中还有部分类型,它们既属于 meta.k8s.io/v1 又属于 UnversionedType 无版本资源类型,例如 metav1.Statusmetav1.APIVersionmetav1.APIGroupListmetav1.APIGroupmetav1.APIResourceList
  • KnownType:是目前 Kubernetes 系统最常用的资源类型,也可称其为“拥有版本的资源类型”。

在 Scheme 资源注册表中,UnversionedType 资源类型的对象通过 scheme.AddUnversionedTypes 方法进行注册,KnownType 资源类型的对象通过 scheme.AddKnownTypes 方法进行注册。

8.1 Scheme 资源注册表数据结构

Scheme 资源注册表数据结构主要由 4 个 map 结构组成,它们分别是 gvkToType、typeToGVK、unversionedTypes、unversionedKinds,代码示例如下:

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
	// TODO: resolve the status of unversioned types.
	unversionedKinds map[string]reflect.Type
...

Scheme 资源注册表结构字段说明如下:

  • gvkToType:存储 GVK 与 Type 的映射关系。
  • typeToGVK:存储 Type 与 GVK 的映射关系,一个 Type 会对应一个或多个 GVK。
  • unversionedTypes:存储 unversionedType 与 GVK 的映射关系。
  • unversionedKinds:存储 Kind 名称与 unversionedType 的映射关系。

Scheme 资源注册表通过 Go 语言的 map 结构实现映射关系,这些映射关系可以实现高效的正向和反向检索,从 Scheme 资源注册表中检索某个 GVK 的 Type,它的时间复杂度为 O(1)

资源注册表在 Kubernetes 系统中属于非常核心的数据结构,若直接阅读源码会感觉比较晦涩,通过 Scheme Example 代码示例来理解 Scheme 资源注册表,印象会更深刻。Scheme Example 代码示例如下:

func main(){
	// KnowType external
	coreGV := schema.GroupVersion{Group: "", Version: "v1"}
	extensionsGV := schema.GroupVersion{Group: "extensions", Version: "v1beta1"}

	// KnowType internal
	coreInternalGV := schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}

	// unversionedType
	unversioned := schema.GroupVersion{Group: "", Version: "v1"}

	scheme := runtime.NewScheme()
	scheme.AddKnownTypes(coreGV, &corev1.Pod{})
	scheme.AddKnownTypes(extensionsGV, &appsv1.DaemonSet{})
	scheme.AddKnownTypes(coreInternalGV, &corev1.Pod{})
	scheme.AddUnversionedTypes(unversioned, &metav1.Status{})
}

在上述代码中,首先定义了两种类型的 GV(资源组、资源版本),AddKnownType 类型有 coreGV、extensionsGV、coreInternalGV 对象,其中 coreInternalGV 对象属于内部版本(即 runtime.APIVersionInternal),而 unversionedType 类型有 unversioned 对象。

通过 runtime.NewScheme() 实例化一个新的 Scheme 资源注册表。注册资源类型到 Scheme 资源注册表有两种方式,UnversionedType 资源类型的对象通过 scheme.AddUnversionedTypes 方法进行注册,KnownType 资源类型的对象通过 scheme.AddKnownTypes 方法进行注册。

GVK(资源组、资源版本、资源种类)在 Scheme 资源注册表中以 <group>/<version>,Kind=<kind> 的形式存在,其中对于 Kind(资源种类)字段,在注册时如果不指定该字段的名称,那么默认使用类型的名称,例如 corev1.Pod 类型,通过 reflect 机制获取资源类型的名称,那么它的资源种类 Kind=Pod

资源类型在 Scheme 资源注册表中以 Go Type(通过 reflect 机制获取)形式存在。另外,需要注意的是,UnversionedType 资源类型的对象通过 scheme.AddUnversionedTypes 方法进行注册,会同时存在于 4 个 map 结构中,代码结构如下:

代码路径:vendor\k8s.io\apimachinery\pkg\runtime\scheme.go

func (s *Scheme) AddUnversionedTypes(version schema.GroupVersion, types ...Object) {
	s.addObservedVersion(version)
	s.AddKnownTypes(version, types...) //
	for _, obj := range types {
		t := reflect.TypeOf(obj).Elem()
		gvk := version.WithKind(t.Name()) //
		s.unversionedTypes[t] = gvk  //
		if old, ok := s.unversionedKinds[gvk.Kind]; ok && t != old {
			panic(fmt.Sprintf("%v.%v has already been registered as unversioned kind %q - kind name must be unique in scheme %q", old.PkgPath(), old.Name(), gvk, s.schemeName))
		}
		s.unversionedKinds[gvk.Kind] = t //
	}
}

8.2 资源注册表注册方法

在 Scheme 资源注册表中,不同的资源类型使用的注册方法不同,分别介绍如下。

  • scheme.AddUnversionedTypes:注册 UnversionedType 资源类型。
  • scheme.AddKnownTypes:注册 KnownType 资源类型。
  • scheme.AddKnownTypeWithName:注册 KnownType 资源类型,须指定资源的 Kind 资源种类名称。

以 scheme.AddKnownTypes 方法为例,在注册资源类型时,无须指定 Kind 名称,而是通过 reflect 机制获取资源类型的名称作为资源种类名称,代码示例如下:

代码路径:vendor\k8s.io\apimachinery\pkg\runtime\scheme.go

// AddKnownTypes registers all types passed in 'types' as being members of version 'version'.
// All objects passed to types should be pointers to structs. The name that go reports for
// the struct becomes the "kind" field when encoding. Version may not be empty - use the
// APIVersionInternal constant if you have a type that does not have a formal version.
func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
	s.addObservedVersion(gv)
	for _, obj := range types {
		t := reflect.TypeOf(obj)
		if t.Kind() != reflect.Pointer {
			panic("All types must be pointers to structs.")
		}
		t = t.Elem()
		s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
	}
}

8.3 资源注册表查询方法

在运行过程中,kube-apiserver 组件常对 Scheme 资源注册表进行查询,它提供了如下方法:

  • scheme.KnownTypes:查询注册表中指定 GV 下的资源类型。
  • scheme.AllKnownTypes:查询注册表中所有 GVK 下的资源类型。
  • scheme.ObjectKinds:查询资源对象所对应的 GVK,一个资源对象可能存在多个 GVK。
  • scheme.New:查询 GVK 所对应的资源对象。
  • scheme.IsGroupRegistered:判断指定的资源组是否已经注册。
  • scheme.IsVersionRegistered:判断指定的 GV 是否已经注册。
  • scheme.IsUnversioned:判断指定的资源对象是否属于 UnversionedType 类型。

<br>

9. Converter 资源版本转换器

在 Kubernetes 系统中,同一资源拥有多个资源版本,Kubernetes 系统允许同一资源的不同版本进行转换。例如 Deployment 资源对象,当前运行的是 v1beta1 资源版本,但 v1beta1 资源版本的某些功能或字段不如 v1 资源版本完善,则可以将 Deployment 资源对象的 v1beta1 资源版本转换为 v1 版本。可通过 kubectl convert 命令进行资源版本转换,执行命令如下:

$ cat v1beta1Deployment.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: my-first-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-first-deploy
  template:
    metadata:
      labels:
        app: my-first-deploy
    spec:
      containers:
      - name: my-first-container
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

$ kubectl convert -f v1beta1Deployment.yaml --output-version=apps/v1
...

首先,定义一个 YAML Manifest File 资源描述文件,该文件定义 Deployment 资源版本为 v1beta1。通过执行 kubectl convert 命令,--output-version 将资源版本转换为指定的资源版本 v1。如果指定的资源版本不在 Scheme 资源注册表中,则会报错。如果不指定资源版本,则默认转换为资源的首选版本。

Converter 资源版本转换器主要用于解决多资源版本转换问题,Kubernetes 系统中的一个资源支持多个资源版本,如果要在每个资源版本之间转换,最之间的方法是,每个资源版本都支持其他资源版本的转换,但这样处理起来非常麻烦。例如,某个资源对象支持 3 个资源版本,那么就需要提前定义一个资源版本转换到其他两个版本(v1->v1alpha1,v1->v1beta1)、(v1alpha1->v1,v1alpha1->v1beta1)、(v1beta1->v1,v1beta1->v1alpha1),随着资源版本的增加,资源版本转换的定义会越来越多。

为了解决这个问题,Kubernetes 通过内部版本(Internal Version)机制实现资源版本转换,Converter 资源版本转换过程如下:

v1alpha1 -> __internal -> v1beta1 或 v1

当需要在两个资源版本之间转换时,先v1alpha1 转 __internal 版本,再由 __internal 转为 v1beta1 或 v1 版本。

9.1 Converter 转换器数据结构

Converter 转换器数据结构主要存放转换函数(即 Conversion Funcs)。Converter 转换器数据结构代码示例如下:

代码路径:vendor\k8s.io\apimachinery\pkg\conversion\converter.go

// Converter knows how to convert one type to another.
type Converter struct {
	// Map from the conversion pair to a function which can
	// do the conversion.
	conversionFuncs          ConversionFuncs
	generatedConversionFuncs ConversionFuncs

	// Set of conversions that should be treated as a no-op
	ignoredUntypedConversions map[typePair]struct{}
}

Converter 转换器数据结构字段说明如下。

  • conversionFuncs:默认转换函数。这些转换函数一般定义在资源目录下的 converter.go 代码文件中。
  • generatedConversionFuncs:自动生成的转换函数。这些转换函数一般定义在资源目录下的 zz_gernerated.comversion.go 代码文件中,是由代码生成器自动生成的转换函数。
  • ignoredConversions:若资源对象注册到此字段,则忽略此资源对象的转换操作。
  • nameFunc:在转换过程中其用于获取资源种类的名称,该函数被定义在 vendor\k8s.io\apimachinery\pkg\runtime\scheme.go 代码文件中。

Converter 转换器数据结构中存放的转换函数(Conversion Funcs)可以分为两类,分别为默认的转换函数(即 conversionFuncs 字段)和自动生成的转换函数(generatedConversionFuncs 字段)。它们都通过 ConversionFuncs 来管理转换函数,代码示例如下:

type typePair struct {
	source reflect.Type
	dest   reflect.Type
}
type ConversionFunc func(a, b interface{}, scope Scope) error
type ConversionFuncs struct {
	untyped map[typePair]ConversionFunc
}

ConversionFunc 类型函数(即 Type Function)定义了转换函数实现的结构,将资源对象 a 转换为资源对象 b。a 参数定义了转换源(即 source)的资源类型,b 参数定义了转换目标(即 dest)的资源类型。scope 定义了多次转换机制(即递归调用转换函数)。

9.2 Converter 注册转换函数

Converter 转换函数需要通过注册才能在 Kubernetes 内部使用,目前 Kubernetes 支持 5 个注册转换函数,分别介绍如下:

  • scheme.AddIgnoredConversionType:注册忽略的资源类型,不会执行转换操作,忽略资源对象的转换操作。
  • scheme.AddConversionFuncs:注册多个 Conversion Func 转换函数。
  • scheme.AddConversionFunc:注册单个 Conversion Func 转换函数。
  • scheme.AddGeneratedConversionFunc:注册自动生成的转换函数。
  • scheme.AddFieldLabelConversionFunc:注册字段标签(FieldLabel)的转换函数。

apps/v1 资源组、资源版本为例,通过 scheme.AddConversionFuncs 函数注册所有资源的转换函数,代码示例如下:

代码路径:pkg\apis\apps\v1\conversion.go

// Convert_apps_DeploymentSpec_To_v1_DeploymentSpec is defined here, because public
// conversion is not auto-generated due to existing warnings.
func Convert_apps_DeploymentSpec_To_v1_DeploymentSpec(in *apps.DeploymentSpec, out *appsv1.DeploymentSpec, s conversion.Scope) error {
	if err := autoConvert_apps_DeploymentSpec_To_v1_DeploymentSpec(in, out, s); err != nil {
		return err
	}
	return nil
}

func Convert_v1_Deployment_To_apps_Deployment(in *appsv1.Deployment, out *apps.Deployment, s conversion.Scope) error {
	if err := autoConvert_v1_Deployment_To_apps_Deployment(in, out, s); err != nil {
		return err
	}

	// Copy annotation to deprecated rollbackTo field for roundtrip
	// TODO: remove this conversion after we delete extensions/v1beta1 and apps/v1beta1 Deployment
	if revision := in.Annotations[appsv1.DeprecatedRollbackTo]; revision != "" {
		if revision64, err := strconv.ParseInt(revision, 10, 64); err != nil {
			return fmt.Errorf("failed to parse annotation[%s]=%s as int64: %v", appsv1.DeprecatedRollbackTo, revision, err)
		} else {
			out.Spec.RollbackTo = new(apps.RollbackConfig)
			out.Spec.RollbackTo.Revision = revision64
		}
		out.Annotations = deepCopyStringMap(out.Annotations)
		delete(out.Annotations, appsv1.DeprecatedRollbackTo)
	} else {
		out.Spec.RollbackTo = nil
	}

	return nil
}

func Convert_apps_Deployment_To_v1_Deployment(in *apps.Deployment, out *appsv1.Deployment, s conversion.Scope) error {
	if err := autoConvert_apps_Deployment_To_v1_Deployment(in, out, s); err != nil {
		return err
	}

	out.Annotations = deepCopyStringMap(out.Annotations) // deep copy because we modify it below

	// Copy deprecated rollbackTo field to annotation for roundtrip
	// TODO: remove this conversion after we delete extensions/v1beta1 and apps/v1beta1 Deployment
	if in.Spec.RollbackTo != nil {
		if out.Annotations == nil {
			out.Annotations = make(map[string]string)
		}
		out.Annotations[appsv1.DeprecatedRollbackTo] = strconv.FormatInt(in.Spec.RollbackTo.Revision, 10)
	} else {
		delete(out.Annotations, appsv1.DeprecatedRollbackTo)
	}

	return nil
}

func Convert_apps_DaemonSet_To_v1_DaemonSet(in *apps.DaemonSet, out *appsv1.DaemonSet, s conversion.Scope) error {
	if err := autoConvert_apps_DaemonSet_To_v1_DaemonSet(in, out, s); err != nil {
		return err
	}

	out.Annotations = deepCopyStringMap(out.Annotations) // deep copy annotations because we change them below
	out.Annotations[appsv1.DeprecatedTemplateGeneration] = strconv.FormatInt(in.Spec.TemplateGeneration, 10)
	return nil
}

// Convert_apps_DaemonSetSpec_To_v1_DaemonSetSpec is defined here, because public
// conversion is not auto-generated due to existing warnings.
func Convert_apps_DaemonSetSpec_To_v1_DaemonSetSpec(in *apps.DaemonSetSpec, out *appsv1.DaemonSetSpec, s conversion.Scope) error {
	if err := autoConvert_apps_DaemonSetSpec_To_v1_DaemonSetSpec(in, out, s); err != nil {
		return err
	}
	return nil
}

func Convert_v1_DaemonSet_To_apps_DaemonSet(in *appsv1.DaemonSet, out *apps.DaemonSet, s conversion.Scope) error {
	if err := autoConvert_v1_DaemonSet_To_apps_DaemonSet(in, out, s); err != nil {
		return err
	}
	if value, ok := in.Annotations[appsv1.DeprecatedTemplateGeneration]; ok {
		if value64, err := strconv.ParseInt(value, 10, 64); err != nil {
			return err
		} else {
			out.Spec.TemplateGeneration = value64
			out.Annotations = deepCopyStringMap(out.Annotations)
			delete(out.Annotations, appsv1.DeprecatedTemplateGeneration)
		}
	}
	return nil
}

func deepCopyStringMap(m map[string]string) map[string]string {
	ret := make(map[string]string, len(m))
	for k, v := range m {
		ret[k] = v
	}
	return ret
}

// Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec augments auto-conversion to preserve < 1.17 behavior
// setting apiVersion/kind in nested persistent volume claim objects.
func Convert_v1_StatefulSetSpec_To_apps_StatefulSetSpec(in *appsv1.StatefulSetSpec, out *apps.StatefulSetSpec, s conversion.Scope) error {
	if err := autoConvert_v1_StatefulSetSpec_To_apps_StatefulSetSpec(in, out, s); err != nil {
		return err
	}
	// set APIVersion/Kind to behave the same as reflective conversion < 1.17.
	// see http://issue.k8s.io/87583
	if out.VolumeClaimTemplates != nil {
		// copy so we don't modify the input
		templatesCopy := make([]core.PersistentVolumeClaim, len(out.VolumeClaimTemplates))
		copy(templatesCopy, out.VolumeClaimTemplates)
		out.VolumeClaimTemplates = templatesCopy
		for i := range out.VolumeClaimTemplates {
			out.VolumeClaimTemplates[i].APIVersion = ""
			out.VolumeClaimTemplates[i].Kind = ""
		}
	}
	return nil
}

// Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec augments auto-conversion to preserve < 1.17 behavior
// setting apiVersion/kind in nested persistent volume claim objects.
func Convert_apps_StatefulSetSpec_To_v1_StatefulSetSpec(in *apps.StatefulSetSpec, out *appsv1.StatefulSetSpec, s conversion.Scope) error {
	if err := autoConvert_apps_StatefulSetSpec_To_v1_StatefulSetSpec(in, out, s); err != nil {
		return err
	}
	// set APIVersion/Kind to behave the same as reflective conversion < 1.17.
	// see http://issue.k8s.io/87583
	if out.VolumeClaimTemplates != nil {
		// copy so we don't modify the input
		templatesCopy := make([]corev1.PersistentVolumeClaim, len(out.VolumeClaimTemplates))
		copy(templatesCopy, out.VolumeClaimTemplates)
		out.VolumeClaimTemplates = templatesCopy
		for i := range out.VolumeClaimTemplates {
			out.VolumeClaimTemplates[i].APIVersion = "v1"
			out.VolumeClaimTemplates[i].Kind = "PersistentVolumeClaim"
		}
	}
	return nil
}

Kubernetes源码阅读 文章被收录于专栏

Kubernetes源码阅读

全部评论

相关推荐

不愿透露姓名的神秘牛友
11-26 18:54
说等下个版本吧的发呆爱好者很贪睡:佬最后去了哪家呀
点赞 评论 收藏
分享
10-15 10:57
已编辑
武昌理工学院 FPGA工程师
狠赚笔第一人:老哥学院本没实习还想拿13k学Java狠赚笔呢
点赞 评论 收藏
分享
评论
点赞
2
分享
牛客网
牛客企业服务