在k8s中有许多编排工具,目前比较热门的是包管理工具Helm,如果说docker是奠定的单实例的标准化交付,那么Helm则是集群化多实例、多资源的标准化交付,但是helm 只能实现简单的编排能力,一些特定场合的应用编排并不能依靠helm实现,比如当一个被依赖服务挂掉感知到之后,如何做一个特定的操作(重启各个依赖服务,操作数据库,发送特定的请求等等),再比如,自动初始化数据库,自动解决服务依赖 实现一键化部署, 再比如节点自愈。这些功能并不能使用helm去实现。

所以helm只是实现了自动化,以及标准化。但仅仅是这些并不能满足我们现有的业务架构。

operator则在实现自动化和标准化的同时实现了智能化,operator对于资源的管理则不仅是创建和交付。由于其可以通过watch的方式获取相关资源的变化事件,因此可以实现高可用、可扩展、故障恢复等运维操作。因此operator对于生命周期的管理不仅包括创建,故障恢复,高可用,升级,扩容缩容,异常处理,以及最终的清理等等。

其主要的工作流程是根据当前的状态,进行智能分析判断,并最终进行创建、恢复、升级等操作。而位于容器中的脚本,因为缺乏很多全局的信息,仅靠自身是无法无法达实现这些全部的功能的。而处于第三方视角的operator,则可以解决这个问题。他可以通过侧面的观察,获取所有的资源的状态和信息,并且跟预想/声明的状态进行比较。通过预置的分析流程进行判断,从而进行相应的操作,并最终达到声明状态的一个目的。这样所有的运维逻辑就从镜像中抽取出来,集中到operator里去。层次和逻辑也就更加清楚,容易维护,也更容易交付和传承。

operator可以说是另外一种controller。目前的controller manager集合的主要是基础的、通用的资源概念,比如rs/deployment,而对于特定的应用或者服务(如etcdcluster,都可以认为是一种资源),则放权给了第三方,也就是CRD。用户可以通过自定义的资源描述,以及自研的controller/operator进行接入。因此controller和operator的关系有点类似于标准库和第三方库的关系。

一般来说,对于不同的应用一般来说需要不同的operator进行处理。这时我们再来想下,以replicaset controller为例。rs的主要功能是保持副本数。当有pod因某种原因挂掉/删除,对于无状态的应用来说,恢复的方式就是再增加对应的pod数量。那么从这个角度来说,对于无状态的应用来说,rs controller其实就是无状态应用的operator。

 

使用kubebuilde编写自己的operater:

主要结构:

pkg/apis - 包含定义的 API 和自定义资源(CRD)的目录树,这些文件允许 sdk 为 CRD 生成代码并注册对应的类型,以便正确解码自定义资源对象。该目录下存在type文件,通过该文件下 会有一个类似于***Spec的struct 通过这个自定义字段

pkg/controller - 用于编写所有的操作业务逻辑的地方,该目录下的*_controller的文件 ,通过该文件下的Reconcile方法实现主要的operater逻辑

controller把轮训与事件监听都封装在这一个接口里了.你不需要关心怎么事件监听的.所有逻辑控制需要我们去更改的地方也不是很多,核心的就是Reconcile方法,该方法就是去不断的 watch 资源的状态,然后根据状态的不同去实现各种操作逻辑

 

operater要点:

CRD级联删除

在很多场景 当我们删除一个crd资源时,需要删除整个crd所产生的所有资源例如我们在创建deployment资源的时候,刚好这个deployment的功能有可能需要访问kubernetes的api接口 这个时候需要创建serviceaccount role rolebinding等相关资源,如果只是创建的话我们可以直接在Reconcile 编写相关资源直接创建,但删除的时候如果想在删除crd的同时删除所有该cr产生的资源 这个时候需要引入OwnerReferences,默认情况下 只有sts depolyment pod这些资源在使用operater创建的时候才会带有OwnerReferences,并且需要指定OwnerReferences的值为该crd的name

带有OwnerReferences 如图:

如图在编写该mysql资源的时候 我们需要在ObjectMeta里指定:

OwnerReferences: []metav1.OwnerReference{

*metav1.NewControllerRef(m.GetObjectMeta(), m.GroupVersionKind()),

},

订阅删除前的事件

 

如果在etcd中已经删除了资源后operator才watch到该事件,此时由于资源已经不复存在,很多逻辑操作无法得到足够的参数来执行处理

因此,因此我们需要的是在执行最终删除之前就能够watch到该事件,并执行一些销毁资源的操作。好在k8s api-server已经为我们提供了finalizer机制。

如果某个资源的finalizers不为空,当执行删除之前,会被operator watch到操作。此时,其meta.DeletionTimestamp不为null,对应operator应该在该次事件的handler中删除掉其注册上来的finalizer对象;并执行其他业务逻辑handler

finalizer比较典型的例子: 在删除namespace的时候 如果该namesapce下的还存在资源 这个时候执行delete操作的时候 是无法删除该namespace的,通过查看namespace的相关配置 我们也可以看到它是有带finalizer标签的

处理事件风暴

k8s operator 中reconcile方法 的作用就是不断的watch,当资源变化时 就会触发reconcile方法,理论上有多少次的变化就会执行多少次的reconcile方法,如果没有做好基于状态来终结循环的逻辑,那么就形成了死循环,产生事件风暴

例如服务依赖于mysql 在mysql没有启动完成之前 operater需要不断的读取myslq的状态,只有当mysql 启动完成之后 才能启动服务。

在kubebuilder中也为我们提供了解决方案:

在判断状态逻辑的时候 如果状态一直没有走到最终状态 我们在watch到该事件的时候 可以return reconcile.Result{RequeueAfter: SyncBuildStatusInterval} 这样就不会不会执行到下面的状态更新操作,而是直接返回,operator会将该资源变动的event重新放入队列,然后等到RequeueAfter参数指定的时间间隔之后重新取出来再调用reconcile处理。这样的优点是,到达的效果一样,但不会频繁的写etcd,从而保障k8s集群不受影响。

 

 

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐