2

给 Kubernetes 提 PR

 1 year ago
source link: https://gobomb.github.io/post/k8s-pr/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

给 Kubernetes 提 PR

12 Oct 2020

之前在解决 weave 的问题时,顺便也发现了 K8s client-go 代码中存在的问题 ,并提交了 PR ,九月份的时候终于被 merge 进 1.20 的 release,并 cherry pick 回 1.17~1.19(不过公司使用的环境是 1.16,社区不再提供官方的补丁……)

weave 因为依赖的这个库的 bug,掩盖了本应该很快发现的 bug,从而导致很多诡异的现象,我觉得这个库应用这么广泛,应该有不少开发者也受影响了才对。而且我和同事在其他项目的使用过程中,也发现过类似的现象(Process 被阻塞,不能正确处理新的事件)。但一开始在 k8s 的 issues 翻了很久,都找到没有类似的问题,实在是违反直觉。

后来仔细看了下 client-go 源码,也找到了合理的解释。informer 有两种用法,一种是使用 client-go/tools/cache/controller.go 中提供的 controller(可以说是低级别的 informer),一种是 client-go/tools/cache/shared_informer.go 中的 SharedInformer 或者 SharedIndexInformer,也就是 SharedInformerFactory 返回的 informer。 SharedIndexInformer 封装的层次更高,大部分项目都是用的后者,刚好避开了普通 informer 存在的 bug。而我们的部分项目,以及 weave-npc 都是用的 controller/低级别informer。

具体而言,bug 就是刚好发生在 controller/低级别informer 和 SharedIndexInformer 对 Contorller 接口方法 Run() 的不同实现上面。它们对 ResourceEventHandler 接口的处理方式或者说 Process 的实现是不同的,而 ResourceEventHandler 的实现就是用户业务代码发生 panic 的地方。

controller/低级别informer 中的 processLoop 和 SharedIndexInformer 中的 sharedProcessor 都是 Process: 1. 负责消费 DeltaFIFO 队列产生的事件 2. 将事件存入 cache 里 3. 根据事件调用用户实现的 ResourceEventHandler。

controller/低级别informer 只有一个 ResourceEventHandler,Run() 是直接调用 processLoop 来使用 ResourceEventHandler 的,Run() 是 processLoop 的直接调用者,panic 的传递就被错误的 defer 给拦截了 。而 SharedIndexInformer 为了支持多个 ResourceEventHandler 共用一个 Informer,用了一个 sharedProcessor 来做事件的分发(而非简单的 processLoop)。SharedIndexInformer 的 Run() 虽然会调用 controller 的 Run() 方法,但是最终调用的是 sharedProcessor 提供的 Process。 所以只要 sharedProcessor 正确处理了 panic,就不会发生和 controller 一样的问题。

一般情况下,SharedIndexInformer 确实是更推荐使用,SharedInformerFactory 已经封装好了各种资源的 informer interface,直接调用就可以,同一种资源可以添加多个 ResourceEventHandler,且可以添加各种索引加速查找,满足更复杂的需求。当然,controller/低级别informer 也有它的场景,比如说只需要一个 ResourceEventHandler、不需要复杂 index 的情况,并且 controller 的实现很简单。

在命名上,其实是有点容易混淆的:对于低级别的 informer 而言,它既是 controller ,也是 informer。而高级别的 informer,实现了 Controller 接口,还保留了旧的 controller 对象,因为它只是重写了 Process 的方式,同时复用原来 controller 的 Reflector、DeltaFIFO 实现。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK