Kubernetes编程范式
发表于:2025-11-23 | 分类: 学习
声明式 API

在Kubernetes中kubectl apply是一个声明式 API,可以用它来代替 kubectl create 命令,修改YAML文件的kubectl replace是一个命令式API,kubectl replace 的执行过程,是使用新的 YAML 文件中的 API 对象,替换原有的 API 对象;同样也可以使用kubectl apply代替,kubectl apply相比于kubectl replace,则是执行了一个对原有 API 对象的 PATCH 操作

kube-apiserver 在响应命令式请求(比如,kubectl replace)的时候,一次只能处理一个写请求,否则会有产生冲突的可能。而对于声明式请求(比如,kubectl apply),一次能处理多个写操作,并且具备 Merge 能力

Kubernetes“声明式 API”的独特之处:

  • 首先,所谓“声明式”,指的就是我只需要提交一个定义好的 API 对象来“声明”,我所期望的状态是什么样子。
  • 其次,“声明式 API”允许有多个 API 写端,以 PATCH 的方式对 API 对象进行修改,而无需关心本地原始 YAML 文件的内容。
  • 最后,也是最重要的,有了上述两个能力,Kubernetes 项目才可以基于对 API 对象的增、删、改、查,在完全无需外界干预的情况下,完成对“实际状态”和“期望状态”的调谐(Reconcile)过程。

声明式 API,是 Kubernetes 项目编排能力“赖以生存”的核心所在

声明式 API 的设计

Kubernetes 项目中,一个 API 对象在 Etcd 里的完整资源路径,是由:Group(API 组)、Version(API 版本)和 Resource(API 资源类型)三个部分组成的。

下图展示了Kubernetes 里 API 对象的组织方式

Kubernetes对 Resource、Group 和 Version的解析分以下步骤:

  1. Kubernetes 会匹配 API 对象的组。:需要明确的是,对于 Kubernetes 里的核心 API 对象,比如:Pod、Node 等,是不需要 Group 的(即:它们 Group 是“”)。所以,对于这些 API 对象来说,Kubernetes 会直接在 /api 这个层级进行下一步的匹配过程。

    而对于 CronJob 等非核心 API 对象来说,Kubernetes 就必须在 /apis 这个层级里查找它对应的 Group,进而根据“batch”这个 Group 的名字,找到 /apis/batch。

    不难发现,这些 API Group 的分类是以对象功能为依据的,比如 Job 和 CronJob 就都属于“batch” (离线业务)这个 Group。

  2. 然后,Kubernetes 会进一步匹配到 API 对象的版本号。:在 Kubernetes 中,同一种 API 对象可以有多个版本,这正是 Kubernetes 进行 API 版本化管理的重要手段。这样,比如在 CronJob 的开发过程中,对于会影响到用户的变更就可以通过升级新版本来处理,从而保证了向后兼容。

  3. 最后,Kubernetes 会匹配 API 对象的资源类型。

下图展示了APIServer创建CronJob 对象的过程:

首先,当我们发起了创建 CronJob 的 POST 请求之后,我们编写的 YAML 的信息就被提交给了 APIServer。

而 APIServer 的第一个功能,就是过滤这个请求,并完成一些前置性的工作,比如授权、超时处理、审计等。

然后,请求会进入 MUX 和 Routes 流程。如果你编写过 Web Server 的话就会知道,MUX 和 Routes 是 APIServer 完成 URL 和 Handler 绑定的场所。而 APIServer 的 Handler 要做的事情,就是按照我刚刚介绍的匹配过程,找到对应的 CronJob 类型定义。

接着,APIServer 最重要的职责就来了:根据这个 CronJob 类型定义,使用用户提交的 YAML 文件里的字段,创建一个 CronJob 对象。

在这个过程中,APIServer 会进行一个 Convert 工作,即:把用户提交的 YAML 文件,转换成一个叫作 Super Version 的对象,它正是该 API 资源类型所有版本的字段全集。这样用户提交的不同版本的 YAML 文件,就都可以用这个 Super Version 对象来进行处理了。

接下来,APIServer 会先后进行 Admission() 和 Validation() 操作。 Admission Controller 和 Initializer,就都属于 Admission 的内容。

而 Validation,则负责验证这个对象里的各个字段是否合法。这个被验证过的 API 对象,都保存在了 APIServer 里一个叫作 Registry 的数据结构中。也就是说,只要一个 API 对象的定义能在 Registry 里查到,它就是一个有效的 Kubernetes API 对象。

最后,APIServer 会把验证过的 API 对象转换成用户最初提交的版本,进行序列化操作,并调用 Etcd 的 API 把它保存起来。

由此可见,声明式 API 对于 Kubernetes 来说非常重要。所以,APIServer 这样一个在其他项目里“平淡无奇”的组件,却成了 Kubernetes 项目的重中之重。它不仅是 Google Borg 设计思想的集中体现,也是 Kubernetes 项目里唯一一个被 Google 公司和 RedHat 公司双重控制、其他势力根本无法参与其中的组件。

在APIServer 中,添加一个 Kubernetes 风格的 API 资源类型可以使用Kubernetes的API 插件机制:CRD

CRD 的全称是 Custom Resource Definition。顾名思义,它指的就是,允许用户在 Kubernetes 中添加一个跟 Pod、Node 类似的、新的 API 资源类型,即:自定义 API 资源。

编写自定义控制器(Custom Controller)

基于声明式 API 的业务功能实现,往往需要通过控制器模式来“监视”API 对象的变化(比如,创建或者删除 Network),然后以此来决定实际要执行的具体工作。

下图展示了一个自定义控制器的工作原理:

从这幅示意图的最左边看起,这个控制器要做的第一件事,是从 Kubernetes 的 APIServer 里获取它所关心的对象,也就是我定义的 Network 对象

这个操作,依靠的是一个叫作 Informer(可以翻译为:通知器)的代码库完成的。Informer 与 API 对象是一一对应的,所以我传递给自定义控制器的,正是一个 Network 对象的 Informer(Network Informer)。

在创建这个 Informer 工厂的时候,需要给它传递一个 networkClient。事实上,Network Informer 正是使用这个 networkClient,跟 APIServer 建立了连接。不过,真正负责维护这个连接的,则是 Informer 所使用的 Reflector 包。更具体地说,Reflector 使用的是一种叫作ListAndWatch的方法,来“获取”并“监听”这些 Network 对象实例的变化。在 ListAndWatch 机制下,一旦 APIServer 端有新的 Network 实例被创建、删除或者更新,Reflector 都会收到“事件通知”。这时,该事件及它对应的 API 对象这个组合,就被称为增量(Delta),它会被放进一个 Delta FIFO Queue(即:增量先进先出队列)中。而另一方面,Informe 会不断地从这个 Delta FIFO Queue 里读取(Pop)增量。每拿到一个增量,Informer 就会判断这个增量里的事件类型,然后创建或者更新本地对象的缓存。这个缓存,在 Kubernetes 里一般被叫作 Store。

这个同步本地缓存的工作,是 Informer 的第一个职责,也是它最重要的职责。Informer 的第二个职责,则是根据这些事件的类型,触发事先注册好的 ResourceEventHandler。这些 Handler,需要在创建控制器的时候注册给它对应的 Informer

Informer,其实就是一个带有本地缓存和索引机制的、可以注册 EventHandler 的 client。它是自定义控制器跟 APIServer 进行数据同步的重要组件。更具体地说,Informer 通过一种叫作 ListAndWatch 的方法,把 APIServer 中的 API 对象缓存在了本地,并负责更新和维护这个缓存。其中,ListAndWatch 方法的含义是:首先,通过 APIServer 的 LIST API“获取”所有最新版本的 API 对象;然后,再通过 WATCH API 来“监听”所有这些 API 对象的变化。而通过监听到的事件变化,Informer 就可以实时地更新本地缓存,并且调用这些事件对应的 EventHandler 了。

在这个过程中,每经过 resyncPeriod 指定的时间,Informer 维护的本地缓存,都会使用最近一次 LIST 返回的结果强制更新一次,从而保证缓存的有效性。在 Kubernetes 中,这个缓存强制更新的操作就叫作:resync。这个定时 resync 操作,也会触发 Informer 注册的“更新”事件。但此时,这个“更新”事件对应的 Network 对象实际上并没有发生变化,即:新、旧两个 Network 对象的 ResourceVersion 是一样的。在这种情况下,Informer 就不需要对这个更新事件再做进一步的处理了。

上一篇:
RBAC
下一篇:
离线业务