第34节 API Server


❤️💕💕新时代拥抱云原生,云原生具有环境统一、按需付费、即开即用、稳定性强特点。Myblog:http://nsddd.topopen in new window


[TOC]

💡API Server 它很重要

它就像是数据库一样重要,缺了它,我们什么都没办法实现,但是相比较控制器,它又不是我们核心。

但是我们作为第一步,我希望可以从它开始学习~

提示

我们知道 kubernetes 控制层面的核心组件包括 API-Server、 Controller Manager、Scheduler,其中 API-Server 对内与分布式存储系统 etcd 交互实现 kubernetes 资源(例如 pod、namespace、configMap、service 等)的持久化,对外提供通过 RESTFul 的形式提供 kubernetes API 的访问接口,除此之外,它还负责 API 请求的认证(authN)、授权(authZ)以及验证。刚提到的“对外”是相对的概念,因为除了像 kubectl 之类的命令行工具之外,kubernetes 的其他组件也会通过各种客户端库来访问 kubernetes API,关于官方提供的各种客户端库请查看 client-libraries 列表,其中最典型的是 Go 语言的客户端库 client-go。

image-20221203150413748

⚠️ 前面知道:apis 是包含内建 API Groups 和 API Objects 的,而 scheme 相关的代码大部分在这里。

三个重要的 API 名词

API Object

是 Kubernetes 内部管理的基本元素,是 Kubernetes 在 ETCD 中信息存储单元。

例如 Deployment,Pod,Service,都是 API Object。内部代码常用 API 称呼。

API Group

一组 API Object 组成的一个具有共有性质的对象集合。

例如:apps 这个 group ,它由 Deployment,ReplicaSet,StatefulSet。

Legacy API Object

绝大多数的 API Object 都被归在 API Group 下面,特别是新版中引入的一定非遵从这一原则。

但是在 Kubernetes 项目项目初始化阶段所引入的 API Object 没有显示定义在 API Group 下面,例如 Pod,Event,Node等等,在代码中有时也称呼他们为 core 、API Object

GVK与GVR

Kubernetes API 通过 HTTP 协议以 RESTful 的形式提供,API 资源的序列化方式主要是以 JSON 格式进行,但为了内部通信也支持 Protocol Buffer 格式。为了方便扩展与演进,kubernetes API 支持分组与多版本,这体现在不同的 API 访问路径上。有了分组与多版本支持,即使要在新版本中去掉 API 资源的特定字段或者重构 API 资源的展现形式,也可以保证版本之间的兼容性。

  • GVK 就是 group、verison、kind
  • GVR 就是 group、version、resource
为什么会有 Kind resource 两个相似的概念
  • 首先我们要明确几个概念:

    • 在编码过程中,资源数据的存储都是

      以结构体存储

      (称为 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。

API-group

将整个 kubernetes API 资源分成各个组,可以带来很多好处:

  • 各组可以单独打开或者关闭
  • 各组可以有独立的版本,在不影响其他组的情况下单独向前衍化
  • 同一个资源可以同时存在于多个不同组中,这样就可以同时支持某个特定资源稳定版本与实验版本

关于 kubernetes API 资源的分组信息可以在序列化的资源定义中有所体现,例如:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-deploy
spec:
...

其中 apiVersion 字段中 apps 即为 Deployment 资源的分组,实际上,Deployment 不止出现在 apps 分组里,也出现在 extensions 分组中,不同的分组可以实验不同的特性;另外,kubernetes 中的核心资源如 podnamespaceconfigmapnodeservice 等存在于 core 分组中,但是由于历史的原因,core 不出现在 apiVersion 字段中,例如以下定义一个 pod 资源的序列化对象:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
  labels:
    app: exp
...

API 分组也体现在访问资源的 RESTful API 路径上,core 组 中的资源访问路径一般为 /api/$VERSION,其他命名组的资源访问路径则是 /apis/$GROUP_NAME/$VERSION,此外还有一些系统级别的资源,如集群指标信息 /metrics,以上这些就基本构成了 kubernetes API 的树结构:

image-20221203150759615

API-version

为了支持独立的演进,kubernetes API 也支持不同的版本,不同的版本代表不同的成熟度。注意,这里说的是 API 而非资源支持多版本。因为多版本支持是针对 API 级别,而不是特定的资源或者资源的字段。一般来说,我们根据 API 分组、资源类型、namespace 以及 name 来区分不同的资源对象,对于同一个资源对象的不同版本,API-Server 负责不同版本之间的无损切换,这点对于客户端来说是完全透明的。事实上,不同版本的同类型的资源在持久化层的数据可能是相同的。例如,对于同一种资源类型支持 v1v1beta1 两个 API 版本,以 v1beta1 版本创建该资源的对象,后续可以以v1 或者 v1beta1 来更新或者删除该资源对象。

API 多版本支持一般通过将资源分组置于不同的版本中来实现,例如,batch 同时存在 v2alph1v1 版本。一般来说,新的资源分组先出现 v1alpha1 版本,随着稳定性的提高被推进到 v1beta1 ,最后从 v1 版本毕业。

随着新的用户场景出现,kubernetes API 需要不断变化,可能是新增一个字段,也可能是删除旧的字段,甚至是改变资源的展现形式。为了保证兼容性,kubernetes 制定了一系列的策略。总的来说,对于已经 GA 的 API,API,kubernetes 严格维护其兼容性,终端用户可以放心食用,beta 版本的 API 则尽量维护,保证不打破版本跨版本之间的交互,而对于 alpha 版本的 API 则很难保证兼容性,不太推荐生产环境使用。

GVK 与 GVR 映射

在 kubernetes API 宇宙中,我们经常使用属于 GVK 或者 GVR 来区分特定的 kubernetes 资源。其中 GVK 是 Group Version Kind 的简称,而 GVR 则是 Group Version Resource 的简称。

通过上面对于 kubernetes API 分组和多版本的介绍中我们已经了解了 Group 与 Version,那么 Kind 与 Resource 又分别是指什么呢?

Kind 是 API “顶级”资源对象的类型,每个资源对象都需要 Kind 来区分它自身代表的资源类型,例如,对于一个 pod 的例子:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
  labels:
    app: exp
...

其中 kind 字段即代表该资源对象的类型。一般来说,在 kubernetes API 中有三种不同的 Kind:

  1. 单个资源对象的类型,最典型的就是刚才例子中提到的 Pod
  2. 资源对象的列表类型,例如 PodList 以及 NodeList 等
  3. 特殊类型以及非持久化操作的类型,很多这种类型的资源是 subresource, 例如用于绑定资源的 /binding、更新资源状态的 /status 以及读写资源实例数量的 /scale

需要注意的是,同 Kind 不止可以出现在同一分组的不同版本中,如 apps/v1beta1apps/v1,它还可能出现在不同的分组中,例如 Deployment 开始以 alpha 的特性出现在 extensions 分组,GA 之后被推进到 apps 组,所以为了严格区分不同的 Kind,需要组合 API Group、API Version 与 Kind 成为 GVK

Resource 则是通过 HTTP 协议以 JSON 格式发送或者读取的资源展现形式,可以以单个资源对象展现,例如 .../namespaces/default,也可以以列表的形式展现,例如 .../jobs。要正确的请求资源对象,API-Server 必须知道 apiVersion 与请求的资源,这样 API-Server 才能正确地解码请求信息,这些信息正是处于请求的资源路径中。一般来说,把 API Group、API Version 以及 Resource 组合成为 GVR 可以区分特定的资源请求路径,例如 /apis/batch/v1/jobs 就是请求所有的 jobs 信息。

GVR 常用于组合成 RESTful API 请求路径。例如,针对应用程序 v1 部署的 RESTful API 请求如下所示:

GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}

通过获取资源的 JSON 或 YAML 格式的序列化对象,进而从资源的类型信息中可以获得该资源的 GVK;相反,通过 GVK 信息则可以获取要读取的资源对象的 GVR,进而构建 RESTful API 请求获取对应的资源。这种 GVK 与 GVR 的映射叫做 RESTMapper。Kubernetes 定义了 RESTMapper 接口[9]并带默认带有实现 DefaultRESTMapper。

API Server 在 cobra 的实现

API Server

func main() {
	command := app.NewAPIServerCommand()	
	code := cli.Run(command)	//跑 command
	os.Exit(code)	//一直跑,除非调用 code 
}

很简洁的主程序~

server chain

构建过程 从右向左; 请求流向 从左向右:

Master 中转载 API

API Server 的内容 是 API Object

通过 Restful 服务对外提供操作 API Object 的能力。

详解 Scheme 机制

定义

Scheme 是一个接口体,内含处理外部 Version 之间的转换, GVK 和 Go Type之间的转换之用的数据和方法。

Scheme 内部有两张 map,分别对应到 gvk 到 type ( gvkToTypeype )和 type 到 gvk( typeToGVK );

使得两者可以相互找到。

  • 内部和外部版本转换。

或许你可以立即为 Windows 上的注册表,存储当前 API Server 所知道的所有 api 服务。

如果说RESTMapper管理的是GVRGVK的关系,那么Scheme管理的就是GVKType的关系。系统中所有的Type都要注册到Scheme中,当然目前系统只有一个Scheme,即 api.Scheme

定义在/pkg/api/register.go中:

Scheme 定义了资源序列化和反序列化的方法以及资源类型和版本的对应关系;这里我们可以理解成一张纪录表。定义在 k8s.io/apimachinery/pkg/runtime/scheme.goopen in new window 中。需要关注的 gvkToTypeypetypeToGVK 字段

// Schemes are not expected to change at runtime and are only threadsafe after
// registration is complete.
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

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

和 API Server 通信的时候能够处理新的 types 类型就需要知道有新的 types 类型,AddToScheme 会利用到反射,新定义的 types 类型的结构体的命名必须和自定义的 Kind 的命名一致,否则找不到对应的kind

converter 和 defaulter 代码生成

目录:pkg/apis/

  • 注册:register.go

为什么需要代码生成?

  1. Go语言没有继承机制,所以代码的冗余必然发生。
  2. External Version 和 Internal Version 的转换

提示

version 会越来越多,每一个 version 出现都需要进行转换,如果都需要手写太累了。

我们也没办法通过继承,通过父类写方法,然后子类继承它。

Version

每一个 API Group 都会有很多 version,每一个 version 包含很多个 kind (一个 KInd会出现在多个 version 下)

这些 Version 又称为 External Version ,它们面向 API Server 外部,Internal Version 是 API Server 在内部程序中处理数据时 API Object 的实际类型。

GVK

Group,Version,Kind 。这三元组确定了一个 Kind,当然并不是缺一不可。GVK 理解为一个字符串,是三者拼接的结果。

Type

代码中常见 "TYPE" 。

参考文章

END 链接