K8s informer DeletedFinalStateUnknown 对象的来源与处理

之前生产环境 volcano 使用遇到一个问题,大致现象是:偶发出现一个 job 被删除了,但相关操作并未执行。最终通过 diff 旧版本与 volcano 最新版本,发现旧版本中未针对 DeletedFinalStateUnknown 场景做判断和处理,仿照新版本加上这段处理逻辑后问题修复。那么 DeletedFinalStateUnknown 对象是如何产生的? controller 应该如何对其处理?作本文以记之。

来源:watch 断连丢失数据的补偿机制

在整个 controller 运行中,reflector 组件通过 ListWatch 机制直接与 apiserver 交互,将数据顺序写入 DetlaFIFO。

ListWatch 机制简介

reflector 源码约 600 行,其中最主要的函数就是 ListAndWatch 和 watchHandler,这两个函数加起来足以阐述 ListWatch 机制。其大致分为两步:

  1. 通过 List 方法从 apiserver 请求到全量的资源对象,并通过 Replace 方法将对象写入 DeltaFIFO 队列
  2. 第一步中 List 同时拿到了最新的资源版本,然后调用 Watch 方法异步接收该版本之后的资源对象事件,根据事件类型调用不同方法将对象写入 DeltaFIFO 队列

除非遇到特别错误或收到停止信号,watch 过程中断连会重新执行 List 步骤,然后继续建立长连接 watch。但在重新 watch 的过程中就会遇到一个问题:如果资源对象在中断过程中被删除了,那么如何让 controller 知道对象已被删除呢?DeletedFinalStateUnknown 就是该问题的一个补偿方案。

Reflector 只负责与 apiserver 通信并将数据存入 DetlaFIFO 内,其中 List 得到的全量最新数据通过 Replace 方法写入,Watch 得到的数据通过 Add/Update/Delete 方法写入。可以说,List 执行结束后拿到的数据是那个时间点最新的数据,如果缓存中有数据不在其中,则表示数据已经被删除。

DetlaFIFO 会将缓存数据(队列内的数据或 indexer 数据,根据是否有 knownObjects 传入决定)与 Replace 方法传入的一组最新数据做对比,如果不在最新数据集合中,则向 DetlaFIFO 中插入 DeletedFinalStateUnknown(此时 Delta 事件类型为 Deleted),以表示该资源已被删除,但最终态未知。DeletedFinalStateUnknown 有两个字段 Key 和 Obj,如果确认数据已被删除,则 Obj 被赋值为 indexer 内 key 对应的数据,或 DetlaFIFO 内 key 对应的最新数据。

type DeletedFinalStateUnknown struct {
        Key string
        Obj interface{}
}

处理:DeleteFunc 要做二次断言

在 controller 注册的资源处理函数中,DeleteFunc 如果针对指定资源类型断言失败,则需要再判断对象是否是 DeletedFinalStateUnknown 类型,以 K8s job-controller 为例:

func (jm *Controller) deleteJob(logger klog.Logger, obj interface{}) {
        jm.enqueueSyncJobImmediately(logger, obj)
        jobObj, ok := obj.(*batch.Job)
        if !ok {
                tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
                if !ok {
                        utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %+v", obj))
                        return
                }
                jobObj, ok = tombstone.Obj.(*batch.Job)
                if !ok {
                        utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a job %+v", obj))
                        return
                }
        }
        jm.cleanupPodFinalizers(jobObj)
}

在这段代码中,controller 先判断 obj 是否为 Job 类型,如果不是则再次判断是否为 DeletedFinalStateUnknown 类型,然后对该 job 做处理。虽然最终 tombstone.Obj 不一定是最终态的 Job,但至少 controller 知道该对应已被删除,从而做相应的处理逻辑。

© 2022 - 2024 · Stay foolish · Theme Simpleness Powered by Hugo ·