随手记一下在厂里遇到的一些k8s使用的问题,不包括解决方案。

k8s的设计问题

面向声明的编程方式,也只是一种方式,并不比面向过程式要来的好。

用户声明的东西,如果在运行期间无法达到声明的状态了,那这期间的报错过程,该如何透露给用户?厂里比较典型的是SLB挂载,pod置换的时候有可能挂载不上,openapi一直报错,需要提工单。如果是用户自己发起的扩容倒也还好,如果是底层宿主机置换或HPA等场景,就很蛋碎了,这也是日常人肉工作占比非常大的一环。

一些很简单的过程式设计,非常抽象出一个状态对象,典型的例如调度预览,在用户扩容前判断下有没有资源、资源管理员判断集群里水位、HPA场景在跨workload调度时的决策,都需要这东西,非得抽象出一个预览对象,创建后还要及时watch变化,然后回收,如果忘了回收就是资源泄露。这比传统的直接在调度器上开个http接口call一下要麻烦太多而且没什么价值。

声明式也很难定义SLA,因为对象本身的创建,可能依赖太多东西,例如最常见的要求对Pod扩缩容提供SLA,那如果用户写了个不可能满足的affinity,成功率岂不是要跌零,又如何判断这是个不可能满足的?也许过一会又能满足了呢(Cluster Autoscaling场景)。缩容就更玄学了,加了个没人处理的finalizer就可以阻断掉pod删除了。

上一篇文章里提到的PodList对象的问题,几乎所有对象都有这个问题,items用的是 []Obj 而不是 []*Obj ,导致反序列化期间每一次slice内部扩容都要复制一遍存量对象,性能巨差,内存占用巨高。

同样是上一篇文章的问题,List时不支持流式处理(不论apiserver还是client-go),rv=0场景(informer第一次list),要把全量资源憋内存里,导致apiserver巨怕大list非常容易oom,client-go这侧也一样哪怕有了SetTransform做裁剪但第一把全量list的内存占用不可避免。社区有个KEP但落地看来要等很久很久。

volume

kcm的pvc protection controller性能太差,串行工作,而且由于设计缺陷,无法精准判断某个pvc到底有没有被使用,所以加了一大堆频繁访问apiserver pod对象的逻辑。根因都是volume挂载是kubelet管的,并没有一个机制阻止某个pvc被引用,所以无论怎么折腾都是降低概率而不是根治。

kcm的attachdetach controller在pod被重复调度到同一个node且引用了同一个pvc时,有bug,依赖全局resync,否则会因为volumeattachment对象被删除没有及时创建新的volumeattachment导致卡住,厂里这个attach-detach-reconcile-sync-period一开始设了一个小时,结果遇到好几次最差情况接近2个小时才恢复正常,然后按社区的设成了10秒,结果超大集群直接cpu打满,现在是设成了10分钟,业务略微能接受一点了。其实这里应该用volumeattachment的delete事件来重新驱动,我们在自己的csi插件里做了这个定向优化,社区估计是觉得这种定向优化通用性并不高所以没做吧。其实pv controller也是同样的问题,都要全局计算,其实有很多旁路加速的法子。

pv和pvc的定向绑定,也是个大坑,pv对象一旦创建,就会被参与到pv controller的调度中,无法豁免,于是就会被不写selector的pvc给bind走(同storageClass同size),于是唯一的办法是先创建pvc,然后获取到pvc的uid,在创建pv时直接写到claimRef里,这无疑是非常反直觉和繁琐的操作,厂里不同的业务方都在这个地方踩了坑。