头阵子基于kubebuilder实现了一套用于大数据交换平台底层调度的operator。在过程中遇到一些坑,平时学习的时候不太容易注意到的点,简单记录一下。
要点
多个CRD级联删除
我自定义了一个CRD PipelineRun,基于PipelineRun的逻辑会自动创建其子资源deployment。我希望在删除PipelineRun的时候,能够自动删除其对应的所有deployment实例。
这里只需要在PipelineRun创建deployment的时候,指定deployment的ObjectMet.OwnerReferences
的值为PipelineRun自己即可。具体见:
1 | func MakeDeploysAndServices(pipeline *v1.Pipeline, run *v1.PipelineRun) ([]appsv1.Deployment, []corev1.Service) { |
订阅删除前的事件
如果在etcd中已经删除了资源后operator才watch到该事件,此时由于资源已经不复存在,很多逻辑操作无法得到足够的参数来执行处理。因此,因此我们需要的是在执行最终删除之前就能够watch到该事件,并执行一些销毁资源的操作。好在k8s api-server已经为我们提供了finalizer机制。
如果某个资源的finalizers不为空,当执行删除之前,会被operator watch到操作。此时,其meta.DeletionTimestamp
不为null,对应operator应该在该次事件的handler中删除掉其注册上来的finalizer对象;并执行其他业务逻辑handler。
finalizer定义方式如下:1
2
3
4
5
6
7
8
9apiVersion: controller.xxx.cn/v1
kind: Pipeline
metadata:
name: pipeline-test-1
finalizers: # 可以指定多个finalizers数组
- finalizer.xxx.cn
spec:
batchJob:
......
以下是reconcile的一个示例:
1 | func (r *PipelineReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { |
处理事件风暴
因为k8s operator本来就是一个循环,即: 当资源变化时,operator的reconcile会被调用。如果此时在代码中又update了资源,那么对资源的update操作又会触发下一轮reconcile。如果在reconcile中没做好基于状态来终结循环的逻辑,循环就会无休止的进行,产生事件风暴。
假设你实现的CRD A的状态依赖于其子资源B的状态,在未走到最终状态之前,operator需要不断读取B的状态来同步给A。想想这个要如何实现?
既然k8s operator的机制本身就是一个循环,因此,我们可以利用这种循环来不断读取子资源的状态,并同步给A。这个循环的控制逻辑需要满足:
- 在B的状态未到最终状态时,循环必须一直执行下去;
- 在B进入不可迁移状态时,A的operator需要同步B的状态,并终结循环。
但是,这里依然存在一个问题!在B处于中间状态的时候,A的operator就一直循环,处于时间风暴中浪费资源吗?其实kubebuilder为我们提供了解决方案。
1 | func (r *PipelineRunReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { |
上面的reconcile.Result{RequeueAfter: SyncBuildStatusInterval}
不会执行到下面的状态更新操作,而是直接返回。operator会将该资源变动的event重新放入队列,然后等到RequeueAfter
参数指定的时间间隔之后重新取出来再调用reconcile处理。这样的优点是,到达的效果一样,但不会频繁的写etcd,从而保障k8s集群不受影响。
利弊
另外,kubebuilder自动生成了operator的代码框架,同时生成CRD的yaml文件,这减少了不少工作量,但是它不生成client侧的代码。因此,这也比较坑,需要重新基于CRD类型文件来生成代码。
项目介绍
项目名称: pipeline-operator
项目代码: https://github.com/chenleji/pipeline-operator