实现CRD控制逻辑
前言
上一篇已经设定了Unit所要实现的目标,完成了Unit结构体各子字段、ownResource字段的填充,为控制逻辑的实现做了基础铺垫。
本篇主要解决和实现的控制逻辑:
- 如何管理Unit下属的own Resources
- 如何使Unit和own Resources生命周期绑定
- 删除Unit资源前能否做一些自定义操作
逐一来实现。
管理own Resources
如前文所说,一共设计了5种ownResource分别对应StatefulSet/Deployment/Ingress/Service/Ingress这5种资源,每一种的管理无一例外需要进行增删改查,管理方式大同小异,因此,提炼了一个通用的接口:
controllers/unit_controller.go:34
1 | type OwnResource interface { |
对应有4个接口方法,分别应用于:
- MakeOwnResource() 根据Unit的指定,生成相应的各类own build-in资源对象,用作创建或更新
- OwnResourceExist() 判断此ownResource资源是否已存在
- UpdateOwnResourceStatus() 获取对应的own build-in资源的状态,用来填充Mycrd的status字段
- ApplyOwnResource() 创建/更新 Mycrd对应的own build-in资源
也即是说,每种ownResource结构体都要实现这4个方法,同时要注意,这4种方法被用作CUR(不包括D),要求是幂等性的,多次执行结果一致。
篇幅有限,这里只列举ownStatefulSet的实现方法,其他的几种资源的实现可直接去github库里查看。
ownStatefulSet 的接口方法实现
MakeOwnResource
api/v1/own_statefulSet.go:22
1 | type OwnStatefulSet struct { |
OwnResourceExist
1 | // Check if the StatefulSet already exists |
ApplyOwnResource
1 | // apply this own resource, create or update |
UpdateOwnResourceStatus
1 | func (ownStatefulSet *OwnStatefulSet) UpdateOwnResourceStatus(instance *Unit, client client.Client, |
接口方法实现后,就要在Reconcile内调用了。
Apply OwnResources
getOwnResources
apply之前首先要生成所有的OwnResources对象,这个方法用来生成所有的ownResource对象放到一个array里。详情看注释
controllers/unit_controller.go:220
1 | // 根据Unit.Spec生成其所有的own resource |
apply
1 | // 3. 创建或更新操作 |
这里就用到了ApplyOwnResource接口方法了。Apply完成后,下一步就需要更新状态了
Update OwnResourceStatus
controllers/unit_controller.go:152
1 | // 4. update Unit.status |
由于Status Update可能比较频繁,因此有两点值得一提:
- 读请求走的是informer local storage的cache,而写请求是发起APIServer的直连,为了减轻APIServer的压力,要尽可能的减少写请求,所以这里设计为所有的OwnResouces更新完毕后才发起一次Update请求。
- 为了与Spec的写操作区分开,这里使用的是
r.Status().Update()
的status局部更新方法,而不是r.Update()
整体更新的方法,这样可以尽量避免写操作的并发冲突。 - 如果连续多次Update,每次Update后Resource Object的Revision会更新,因此每次Update完成后,需要重新Get后才能再次Update,否则会报错。
生命周期绑定
上面描述了创建/更新Unit时如何同步Apply更新到own resources,那如何保证Unit生命周期结束时(删除),own resources跟随一起结束呢?
实现的方式非常简单,K8s已经替我们实现了,只需要给own resource加上一组特殊的标记即可。
还是以StatefulSet举例,回顾上面的OwnStatefulSet.MakeOwnResource()
方法,你会看到这么几行代码:
1 | func (ownStatefulSet *OwnStatefulSet) MakeOwnResource(instance *Unit, logger logr.Logger, |
controllerutil.SetControllerReference(OwnerObj, OwnObj, scheme)
方法,可以为被管理的own resource实例加上控制来源描述,这样,它便可与所属对象实现生命周期的绑定了。
这个方法在K8s build-in资源中也多处使用,例如StatefulSet用来管理Pod。找一个被Sts管理的pod实例来看看:
加上这个ownerReferences描述之后,owner 删除前,也会同步删除own resources.
当然,如果在删除owner时希望非级联删除,可以在kubectl命令末尾追加--cascade=false
参数。
PreDelete操作
在上面添加了SetControllerReference的步骤后,默认的PreDelete策略是: 在删除Owner之前,先确保删除所有的Own resources,如果放任删除干净了,即使是local cache也找不到资源的信息。如果需要在此之前实现自定义的删除前操作,添加额外的自定义PreDelete钩子,可以按下面的方式实现。
###PreDelete钩子
使用controller GC的Finalizer终结器机制,可以在Delete之前加入PreDelete钩子:
翻译一下,如果资源对象被直接删除,就无法再读取任何被删除对象的信息,这就会导致后续的清理工作因为信息不足无法进行,Finalizer字段设计来处理这种情况:
- Finalizer本身只是一串随机字符串标识,controller负责添加它和删除它。
- 添加了Finalizer之后,delete操作就会变成update操作,即为对象加上deletionTimestamp时间戳
- Finalizer已主动清空(视为清理后续的任务已处理完成)之后,当前时间大于deletionTimestamp,就会开始执行gc
显而易见,PreDelete钩子要放在主动清除Finalizer之前。来看代码:
controllers/unit_controller.go:98
1 | // 2. 删除操作 |
如此,在PreDelete()方法内部,就可以实现自定义的PreDelete钩子的逻辑了。
总结
控制器逻辑的核心,还是围绕着owner和own resources之间来进行的,在这个过程中,尽量减少写请求的频率,同时可以充分利用k8s现有的各种生命周期管理机制。