CRD(custom resource definition)
简介
在 Kubernetes 中一切都可视为资源,Kubernetes 1.7 之后增加了对 CRD 自定义资源二次开发能力来扩
展 Kubernetes API,通过 CRD 我们可以向 Kubernetes API 中增加新资源类型,而不需要修改
Kubernetes 源码来创建自定义的 API server,该功能大大提高了 Kubernetes 的扩展能力。
当你创建一个新的CRD时,Kubernetes API服务器将为你指定的每个版本创建一个新的RESTful资源路径,我
们可以根据该api路径来创建一些我们自己定义的类型资源。CRD可以是命名空间的,也可以是集群范围的,由
CRD的作用域(scpoe)字段中所指定的,与现有的内置对象一样,删除名称空间将删除该名称空间中的所有自定义
对象。CRDC本身没有名称空间,所有名称空间都可以使用
简单栗子
student.yaml:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
# metadata.name的内容是由"复数名.分组名"构成,如下,students是复数名,bolingcavalry.k8s.io是分组名
name: students.bolingcavalry.k8s.io
spec:
# 分组名,在REST API中也会用到的,格式是: /apis/分组名/CRD版本
group: bolingcavalry.k8s.io
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
# 是否有效的开关.
served: true
# 只有一个版本能被标注为storage
storage: true
# 范围是属于namespace的
scope: Namespaced
names:
# 复数名
plural: students
# 单数名
singular: student
# 类型名
kind: Student
# 简称,就像service的简称是svc
shortNames:
- stu
启动kubectl apply -f student.yaml
, 可以通过kubectl get crd
查看到新创建的students.bolingcavalry.k8s.io资源, 如果想查看对应更详细的信息, 使用命令kubectl get crd stu
stu 为student.yaml中定义的简称;
如果配置了etcd可以查看到新创建的CRD保存到了etcd上.
controller
但如果仅仅是在etcd保存Student对象是没有什么意义的,试想通过deployment创建pod时,如果只在etcd创建pod对象,而不去node节点创建容器,那这个pod对象只是一条数据而已,没有什么实质性作用,其他对象如service、pv也是如此;
作用
监听指定对象的新增、删除、修改等变化,针对这些变化做出相应的响应
如上图,API对象的变化会通过Informer存入队列(WorkQueue),在Controller中消费队列的数据做出响应,响应相关的具体代码就是我们要做的真正业务逻辑;
相关代码
可自动生成的代码
上图中的informer, client, workqueud代码可以通过k8s中的工具自动生成, 我们只需要编写controller中的逻辑即可(也就是接收到有变化后需要做处的反应)
client目录下的内容都是客户端相关代码,在开发controller时会用到; client目录下的clientset、informers、listers的身份和作用可以和前面的图结合来理解;
真正处理的逻辑代码
如何与k8s通信
首先,控制器需要与 kubernetes apiserver 进行通讯,则需要上面所说的 client, 这个 client 需要有以下的信息:
- apiserver 的地址以及连接 apiserver 的认证信息,如用户名密码或者 token。
- kubernetes 的 API resource 的 group 和 version,以及结构体的定义。
- 一个 serializer 来控制序列化与反序列化 apiserver 的结果。
然后你就可以用这个 client 去 apiserver list/watch 特定的类型的资源。 一般是建议使用 client-go 中提供的 informer 来 watch 资源的变更而不是轮询 apiserver,因为 informer 的方式性能更好。
infomer
基本功能
Informer 是 Client-go 中的一个核心工具包。在 Kubernetes 源码中,如果 Kubernetes 的某个组件,需要 List/Get Kubernetes 中的 Object,在绝大多 数情况下,会直接使用 Informer 实例中的 Lister()方法(该方法包含 了 Get 和 List 方法),而很少直接请求 Kubernetes API。Informer 最基本 的功能就是 List/Get Kubernetes 中的 Object。
高级功能
Informer 还可以监听事件并触发回调函数等,以实现更加复杂的业务逻辑。
设计思路
为了让 Client-go 更快地返回 List/Get 请求的结果、减少对 Kubenetes API 的直接调用,Informer 被设计实现为一个依赖 Kubernetes List/Watch API 、可监听事件并触发回调函数的二级缓存工具包。
更快地返回 List/Get 请求,减少对 Kubenetes API 的直接调用
使用 Informer 实例的 Lister() 方法, List/Get Kubernetes 中的 Object 时,Informer 不会去请求 Kubernetes API,而是直接查找缓存在本地内存中的数据(这份数据由 Informer 自己维护)。通过这种方式,Informer 既可以更快地返回结果,又能减少对 Kubernetes API 的直接调用。
依赖 Kubernetes List/Watch API
Informer 只会调用 Kubernetes List 和 Watch 两种类型的 API。Informer 在初始化的时,先调用 Kubernetes List API 获得某种 resource 的全部 Object,缓存在内存中; 然后,调用 Watch API 去 watch 这种 resource,去维护这份缓存; 最后,Informer 就不再调用 Kubernetes 的任何 API。
用 List/Watch 去维护缓存、保持一致性是非常典型的做法,但令人费解的是,Informer 只在初始化时调用一次 List API,之后完全依赖 Watch API 去维护缓存,没有任何 resync 机制。
可监听事件并触发回调函数
Informer 通过 Kubernetes Watch API 监听某种 resource 下的所有事件。而且,Informer 可以添加自定义的回调函数,这个回调函数实例(即 ResourceEventHandler 实例)只需实现 OnAdd(obj interface{}) OnUpdate(oldObj, newObj interface{}) 和 OnDelete(obj interface{}) 三个方法,这三个方法分别对应 informer 监听到创建、更新和删除这三种事件类型。
二级缓存
二级缓存属于 Informer 的底层缓存机制,这两级缓存分别是DeltaFIFO 和 LocalStore
。
这两级缓存的用途各不相同。DeltaFIFO 用来存储 Watch API 返回的各种事件 ,LocalStore 只会被 Lister 的 List/Get 方法访问 。
虽然 Informer 和 Kubernetes 之间没有 resync 机制,但 Informer 内部的这两级缓存之间存在 resync 机制。
详细解析
我们以 Pod 为例,详细说明一下 Informer 的关键逻辑:
-
Informer 在初始化时,Reflector 会先 List API 获得所有的 Pod
-
Reflect 拿到全部 Pod 后,会将全部 Pod 放到 Store 中
-
如果有人调用 Lister 的 List/Get 方法获取 Pod, 那么 Lister 会直接从 Store 中拿数据
5. Informer 初始化完成之后,Reflector 开始 Watch Pod,监听 Pod 相关 的所有事件;如果此时 pod_1 被删除,那么 Reflector 会监听到这个事件
-
Reflector 将 pod_1 被删除 的这个事件发送到 DeltaFIFO
-
DeltaFIFO 首先会将这个事件存储在自己的数据结构中(实际上是一个 queue),然后会直接操作 Store 中的数据,删除 Store 中的 pod_1
-
DeltaFIFO 再 Pop 这个事件到 Controller 中
10. Controller 收到这个事件,会触发 Processor 的回调函数
LocalStore 会周期性地把所有的 Pod 信息重新放到 DeltaFIFO 中
参考&引用资料: