Kubernetes Operator #
Kubernetes 是一个高度可扩展的系统,它的扩展点包括 kubectl、APIServer、Kubernetes 资源、Controller 控制器、Schedule 调度器、CNI 网络插件、CSI 存储插件、CRI 容器运行时,虽然它的扩展点这么多,但是一般来说我们接触的比较多的还是 自定义资源,控制器,准入控制,有些还会对 kubectl 和 调度器做一些扩展,其他的大部分使用成熟的开源组件就可以了。而 Operator 就会涉及到 自定义资源,控制器和准入控制。
常见范围: #
- 按需部署应用
- 获取 / 还原应用状态的备份
- 处理应用代码的升级以及相关改动。例如 数据库的 schema 或额外的配置设置
- 发布一个 service,要求不支持 kubernetes API 的应用也能发现它
- 模拟整个活部份集群中的故障来测试其稳定性
- 在没有内部成员选举程序的情况下, 为分布式应用选取首领角色
开发工具: #
- CoreOS 开源的 operator-sdk
- k8s sig 小组维护的 kubebuilder
- 可以在 https://operatorhub.io/ 上找到别人开发的现成的 Operator 进行使用
基础定义 #
Kubernetes API #
在 kubernetes 集群中,所有需要数据存取的组件都需要和 kube-apiserver 组件通信,而集群数据都是保存在 etcd 中。同时,kubernetes 也大量使用了声明式 api 来提高用户开发和使用效率,而其 api 分别由 **Group(API 组)、Version(API 版本)和 Resource(API 资源类型)**组成。如下图所示:
Resource、ResourceType、Controller #
Kubernetes 有以下几个特点:
- Kubernetes 可以类比成一个 “数据库”(数据实际持久存储在etcd中)
- 其 API 就是 “sql语句”
- API 设计采用基于 resource 的 Restful 风格,resource type 是 API 的端点(endpoint)
- 每一类 resource(即 Resource Type)是一张 “表”,Resource Type 的 spec 对应 “表结构” 信息(schema)
- 每张 “表” 里的一行记录就是一个 resource,即该表对应的 Resource Type 的一个实例(instance)
- Kubernetes 这个 “数据库” 内置了很多 “表”,比如 Pod、Deployment、DaemonSet、ReplicaSet 等
resource type 有两类,一类的 namespace 相关的(namespace-scoped),另外一类则是 namespace 无关,即 cluster 范围(cluster-scoped)的
Kubernetes 是服务编排和容器调度的平台标准,它的基本调度单元是Pod(也是一个resource type),即一组容器的集合。那么 Pod 又是如何被创建、更新和删除的呢?这就离不开控制器(controller)了。每一类 resource type 都有自己对应的控制器(controller)。以 pod 这个 resource type 为例,它的 controller 为 ReplicasSet 的实例。控制器的运行逻辑如下图所示:
控制器一旦启动,将尝试获得 resource 的当前状态(current state),并与存储在 k8s 中的 resource 的期望状态(desired state,即 spec)做比对,如果不一致,controller 就会调用相应 API 进行调整,尽力使得 current state 与期望状态达成一致。这个达成一致的过程被称为协调(reconciliation)
根据前面我们对 resource type 理解,定义 CRD 相当于建立新 “表”(resource type),一旦 CRD 建立,k8s 会为我们自动生成对应 CRD 的 API endpoint,我们就可以通过 yaml 或 API 来操作这个 “表”。我们可以向 “表” 中 “插入” 数据,即基于 CRD 创建 Custom Resource(CR),这就好比我们创建 Deployment 实例,向 Deployment “表” 中插入数据一样
和原生内置的 resource type 一样,光有存储对象状态的 CR 还不够,原生 resource type 有对应 controller 负责协调(reconciliation)实例的创建、伸缩与删除,CR 也需要这样的 “协调者”,即我们也需要定义一个 controller 来负责监听 CR 状态并管理 CR 创建、伸缩、删除以及保持期望状态(spec)与当前状态(current state)的一致。这个 controller 不再是面向原生 Resource type 的实例,而是面向 CRD 的实例 CR 的 controller
有了自定义的操作对象类型(CRD),有了面向操作对象类型实例的 controller,我们将其打包为一个概念:“Operator模式”,operator 模式中的 controller 也被称为 operator,它是在集群中对 CR 进行维护操作的主体
GV GVK GVR #
下面是一个标准的 k8s deployment 的 yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
...
上述指定了 apiVersion: apps/v1,其中包括了 Group(apps)、Version(v1),即 GV。
kind 字段标识了资源类型 Deployment(Kind),和上述集合称为 GVK
spec 下定义了众多字段资源(即 Resource,是 Kind 的对象标识,存储的是 Kind 的 API 对象的一个集合)即 GVR
Client-go #
Operator 使用 client-go。 如果我们需要对 kubernetes 中的资源进行增删查改等,则需要通过操作 api 接口进行操作,我们不需要自己去调用各种 api 接口来实现,官方有开源的 SDK 来供我们使用,即 client-go。
client-go 提供四种客户端对象来和 apiserver 进行交互:
- RESTClient:这是最基础的客户端对象,仅对 HTTPRequest 进行了封装,实现 RESTFul 风格 API,这个对象的使用并不方便,因为很多参数都要使用者来设置,于是 client-go 基于 RESTClient 又实现了三种新的客户端对象;
- ClientSet:把 Resource 和 Version 也封装成方法了,一个资源是一个客户端,多个资源就对应了多个客户端,所以 ClientSet 就是多个客户端的集合了,不过 ClientSet 只能访问内置资源,访问不了自定义资源
- DynamicClient:是一种动态客户端,它可以动态的指定资源的组,版本和资源。因此它可以对任意 K8S 资源进行 RESTful 操作,包括自定义资源。它封装了 RESTClient。所以同样提供 RESTClient 的各种方法。该类型的官方例子。
- DiscoveryClient:用于发现 kubernetes 的 API Server 支持的 Group、Version、Resources 等信息;
Informer #
我们去获取集群中的资源对象以及当集群中存在大量资源数据时,每次从 apiServer 获取都会占用大量内存资源,client-go 使用 informer 机制来解决。 Informer 在初始化的时先通过 List 去从 Kubernetes API 中取出资源的全部 object 对象,并同时缓存,然后通过 Watch 的机制去监控资源。
Reflector(反射器) 定义在 /tools/cache 包内。监视(Watch) Kubernetes API 以获取指定的资源类型 (Kind),当监控的资源发生变化时,触发相应的变更事件,例如Add 事件、Update 事件、Delete 事件,并将其资源对象存放到本地缓存 DeltaFIFO 中
DeltaFIFO: DeltaFIFO 是一个生产者-消费者的队列,生产者是 Reflector,消费者是 Pop 函数,FIFO 是一个先进先出的队列,而 Delta 是一个资源对象存储,它可以保存资源对象的操作类型,例如 Add 操作类型、Update 操作类型、Delete 操作类型、Sync 操作类型等
Indexer: Indexer 是 client-go 用来存储资源对象并自带索引功能的本地存储,Informer 从 DeltaFIFO 中将消费出来的资源对象存储至 Indexer。以此,我们便可从 Indexer 中读取数据,而无需从 apiserver 读取
WorkQueue: DeltaIFIFO 收到时间后会先将时间存储在自己的数据结构中,然后直接操作 Store(本地缓存) 中存储的数据,更新完 store 后 DeltaIFIFO 会将该事件 pop 到 WorkQueue 中,Controller 收到 WorkQueue 中的事件会根据对应的类型触发对应的回调函数(这是在控制器代码中创建的队列,用于将对象的分发与处理解耦)
流程示例:
如果删除一个 pod,Informer 执行流程:
- 首先初始化 Informer,Reflector 通过 List 接口获取所有的 Pod 对象
- Reflector 拿到所有 Pod 后,将全部 Pod 放到 Store(本地缓存)中
- 如果有人调用 Lister 的 List/Get 方法获取 Pod,那么 Lister 直接从 Store 中去拿数据
- Informer 初始化完成后,Reflector 开始 Watch Pod 相关的事件
- 此时如果我们删除 Pod1,那么 Reflector 会监听到这个事件,然后将这个事件发送到 DeltaFIFO 中
- DeltaFIFO 首先先将这个事件存储在一个队列中,然后去操作 Store 中的数据,删除其中的 Pod
- DeltaFIFO 然后 Pop 这个事件到事件处理器(资源事件处理器)中进行处理
- LocalStore 会周期性地把所有的 Pod 信息重新放回 DeltaFIFO 中
Scheme #
每种资源的都需要有对应的 Scheme,Scheme 结构体包含 gvkToType 和 typeToGVK 的字段映射关系,APIServer 根据 Scheme 来进行资源的序列化和反序列化。
Schema 定义了自定义资源的字段、验证规则、默认值等,确保数据的一致性和合法性。
Schema和CRD的联系:
- 功能:Scheme 主要用于类型管理和序列化/反序列化,而 CRD 用于定义和扩展 Kubernetes API。
- 使用场景:Scheme 是 Kubebuilder 项目内部使用的,用于确保控制器和 API 服务器之间的类型一致性;CRD 是 Kubernetes 集群中的资源,用于定义自定义资源的结构和行为。
- 实现方式:Scheme 是通过 Go 代码定义和管理的,而 CRD 是通过 YAML 文件定义并应用到 Kubernetes 集群中的。在 Kubebuilder 项目中:
- Scheme 通常在
pkg/apis/
目录下定义,包含了所有自定义资源(CR)的 Go 结构体和相应的类型信息。 - CRD 是通过代码生成工具自动生成的。用户在定义自定义资源的 Go 结构体后,Kubebuilder 会生成相应的 CRD YAML 文件,用户可以将这些文件应用到 Kubernetes 集群中。
- Scheme 通常在
schema 定义了资源的参数模型,并通过序列化方式转化为 CRD 等配置,存储至 etcd 中,以下为示例:
// 此处为定义instance的schema
type Instance struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec InstanceSpec `json:"spec"`
Status InstanceStatus `json:"status,omitempty"`
}
type InstanceSpec struct {
RegionID string `json:"regionID"`
ZoneID string `json:"zoneID"`
VpcID string `json:"vpcID"`
SubnetID string `json:"subnetID"`
}
生成的 CRD 为:
此处为crd内容
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
...
spec:
names:
...
versions:
name: v1alpha1
schema:
openAPIV3Schema:
description: Instance is a specification for a Instance Instance resource
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: Spec defines the desired state of Instance
properties:
regionID:
description: RegionID is the location that the Instance lives in.
type: string
subnetID:
description: SubnetID is the id of VPC subnet
type: string
vpcID:
description: VpcID is the id of user VPC
type: string
zoneID:
description: ZoneID is the id of target available-zone
type: string
required:
- regionID
- subnetID
- vpcID
- zoneID
type: object
status:
...
required:
- metadata
- spec
type: object
served: true
storage: true
subresources: {}
status:
...
Operator 工作方式 #
Operator 通过扩展 Kubernetes 控制平面和 API 进行工作。Operator 将一个 endpoint(称为自定义资源 CR)添加到 Kubernetes API 中,该 endpoint 还包含一个监控和维护新类型资源的控制平面组件。整个操作原理如下图所示:
当 Operator 接收任何信息时,它将采取行动将 Kubernetes 集群或外部系统调整到所需的状态,作为其在自定义 controller 中的和解循环(reconciliation loop)的一部分
Kubebuilder #
Kubebuilder 是一个基于 CRD 来构建 Kubernetes API 的框架,可以使用 CRD 来构建 API、Controller 和 Admission Webhook。参考文档:The Kubebuilder Book