《深入剖析Kubernetes》总结六:声明式API

声明式API与编程范式

想要使用Kubernetes 的 API 对象,需要编写一个对应的 YAML 文件交给 Kubernetes,而声明式API,则为kubectl apply 命令,先 kubectl create,再 replace 的操作,称为命令式配置文件操作,并不是声明式API

kubectl replace 的执行过程,是使用新的 YAML 文件中的 API 对象,替换原有的 API 对象;
kubectl apply执行了一个对原有 API 对象的 PATCH 操作,类似地,kubectl set image 和 kubectl edit 也是对已有 API 对象的修改

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

体现其意义的例子:Istio
在这里插入图片描述
Envoy 是一个高性能 C++ 网络代理,Istio把这个代理服务以 sidecar 容器的方式,运行在了每一个被治理的应用 Pod 中;
因为Pod 里的所有容器都共享同一个 Network Namespace,所以,Envoy 容器就能够通过配置 Pod 里的 iptables 规则,把整个 Pod 的进出流量接管下来;
这时候,Istio 的控制层(Control Plane)里的 Pilot 组件,就能够通过调用每个 Envoy 容器的 API,对这个 Envoy 代理进行配置,从而实现微服务治理

虽然Istio 需要在每个 Pod 里安装一个 Envoy 容器,但是在微服务治理的过程中对用户和应用是无感知的,这使用的是k8s的一个功能:Dynamic Admission Control

在Kubernetes 中,当一个 Pod 或者任何一个 API 对象被提交给 APIServer 之后,有一些“初始化”性质的工作需要在它们被 Kubernetes 项目正式处理之前进行,比如,自动为所有 Pod 加上某些标签(Labels)
而这个“初始化”操作的实现,借助的是一个叫作 Admission 的功能;
它其实是 Kubernetes 里一组被称为 Admission Controller 的代码,可以选择性地被编译进 APIServer 中,在 API 对象创建之后会被立刻调用到;
但这就意味着如果想要添加一些自己的规则到 Admission Controller,就会比较困难,因为要重新编译并重启 APIServer;
显然。这种使用方法对 Istio 来说,影响太大了,所以,Kubernetes额外提供了一种“热插拔”式的 Admission 机制,它就是 Dynamic Admission Control,也叫作:Initializer

首先Istio 会将这个 Envoy 容器本身的定义,以 ConfigMap 的方式保存在 Kubernetes 当中;
这个 ConfigMap 的 data 部分是一个 Pod 对象的一部分定义,其中有Envoy 容器对应的 containers 字段,以及一个用来声明 Envoy 配置文件的 volumes 字段;
Initializer 要做的工作,就是把这部分 Envoy 相关的字段,自动添加到用户提交的 Pod 的 API 对象里,而用户提交的 Pod 里本来就有 containers 字段和 volumes 字段,所 以 Kubernetes 在处理这样的更新请求时,就必须使用类似于 git merge 这样的操作,才能将 这两部分内容合并在一起;
所以说,在 Initializer 更新用户的 Pod 对象的时候,必须使用 PATCH API 来完成。而这种 PATCH API,正是声明式 API 最主要的能力,也就体现了上述所说的意义

在使用 Initializer 的流程中,最核心的步骤就是Initializer“自定义控制器”的编写过程;
它遵循的,正是标准的“Kubernetes 编程范式”,即:如何使用控制器模式,同 Kubernetes 里 API 对象的“增、删、改、查”进行协 作,进而完成用户业务逻辑的编写过程。

  • 总结

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

首先,“声明式”指的就是只需要提交一个定义好的 API 对象来“声明”,所期望的状态是什么样子;

其次,“声明式 API”允许有多个 API 写端,以 PATCH 的方式对 API 对象进行修改,而无需关心本地原始 YAML 文件的内容;

最后,也是最重要的,有了上述两个能力,Kubernetes 项目才可以基于对 API 对象的增、 删、改、查,在完全无需外界干预的情况下,完成对“实际状态”和“期望状态”的调谐 (Reconcile)过程。

深入解析声明式API(一)

声明式API工作原理

首先知道一下一个 API 对象在 Etcd 里的完整资源路径,是由:Group(API 组)、 Version(API 版本)和 Resource(API 资源类型)三个部分组成的,可以用如下图的树形结构表示出来:
在这里插入图片描述
API对象的组织方式是层层递进的,Kubernetes会对Group、Version和Resource进行解析,也就是层层匹配,得到相应的对象定义,如Cronjob(Pod、Node 等核心API对象不需要Group,直接匹配Version)

把YAML 文件提交给 Kubernetes 之后,创建出 API 对象的流程:以创建 CronJob为例

1 发起创建 CronJob 的 POST 请求后,编写的 YAML 的信息就被提交给 了 APIServer

2 APIServe过滤这个请求,并完成一些前置性的工作,比如授权、超时 处理、审计等

3 请求进入 MUX 和 Routes 流程;
MUX 和 Routes 是 APIServer 完成 URL 和 Handler 绑定的场所;
APIServer 的 Handler 要做的事 情,就是按照层层匹配的过程,找到对应的 CronJob 类型定义

4 APIServer根据CronJob 类型定义,使用用户提交的 YAML 文件里的字段,创建一个 CronJob 对象;
APIServer 会进行一个 Convert 工作,即把用户提交的 YAML 文件,转换成一个叫作 Super Version 的对象,它正是该 API 资源类型所有版本的字段全集,这样用户提交的不同版本的 YAML 文件,就都可以用这个 Super Version 对象来进行处理了

5 先后进行 Admission() 和 Validation() 操作;
Admission Controller 和 Initializer都属于 Admission 的内容;
Validation负责验证这个对象里的各个字段是否合法,这个被验证过的 API 对象,都保存在了 APIServer 里一个叫作 Registry 的数据结构中,也就是说,只要一个 API 对象的定义能 在 Registry 里查到,它就是一个有效的 Kubernetes API 对象

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

自定义API对象

如果想要添加自定义API资源类型,建议使用CRD( Custom Resource Definition),它允许用户在 Kubernetes 中添加一个跟 Pod、Node 类似的、新的 API 资源类型,即:自定义 API 资源

使用CRD创建出自定义API对象后,就是为这个 API 对象编写一个自定义控制器(Custom Controller),这样, Kubernetes 才能根据 自定义 API 对象的“增、删、改”操作,在真实环境中做出相应的响应

编写自定义控制器分为三个过程:编写 main 函数、编写自定义控制器的定义,以编写控制器里的业务逻辑

自定义控制器的工作流程:
在这里插入图片描述
1 首先从 Kubernetes 的 APIServer 里获取它所关心的对象,也就是自定义的控制器对象

这个操作,依靠的是一个叫作 Informer(通知器)的代码库完成的;
Informer 与 API 对象是一一对应的,所以需要传递给自定义控制器一个Informer;
Informer是一个带有本地缓存和索引机制的、可以注册 EventHandler 的 client。它是自定义控制器跟 APIServer 进行数据同步的重要组件。

创建Informer的时候需要传一个Client,Informer 正是使用Client,跟 APIServer 建立了连接;
真正负责维护这个连接的是 Informer 所使用的 Reflector 包

Reflector 使用的是一种叫作ListAndWatch的方法,来“获取”并“监听”这些API对象实例的变化(Informer通过 ListAndWatch,把 APIServer 中的 API 对象缓存在了本地,并负责更新和维护这个缓存。)

在 ListAndWatch 机制下,一旦 APIServer 端有新的API对象实例被创建、删除或者更新, Reflector 都会收到“事件通知”;
这时,该事件及它对应的 API 对象这个组合,就被称为增量 (Delta),它会被放进一个 Delta FIFO Queue(即:增量先进先出队列)中;

Informe 会不断地从 Delta FIFO Queue 里读取(Pop)增量。每拿到一个增量,Informer 就会判断这个增量里的事件类型,然后创建或者更新本地对象的缓存;
这个缓存,在 Kubernetes 里一般被叫作 Store

Informer 的第二个职责,则是根据这些事件的类型,触发事先注册好的 ResourceEventHandler;
这些 Handler,需要在创建控制器的时候注册给它对应的 Informer

2 Informer 与要编写的控制循环之间,则使用了一个工作队列来进行协同,防止控制循环执行过慢把Informer拖死

3 接下来就是熟悉的控制循环的逻辑了

总结起来就是根据Informer来拉取API对象,通过WorkQueue与控制讯护进行交互

猜你喜欢

转载自blog.csdn.net/qq_41594698/article/details/107182630
今日推荐