我们发现调度器端到端的延迟达到7ms,相当于140pod/秒的吞吐量,比我们之前测试看到的20pod/秒要高出很多。这说明瓶颈在调度器自身以外。
我们继续通过查看性能指标和日志来缩小问题的范围。我们发现瓶颈在replication controller里。看这里:
我们打印了日志,发现等到500 CreatePods() 用了25秒。这正好是20pod/秒,但 CreatePods() 的延迟只有1ms。
不久之后,我们在客户端用户里发现了一个速率限制。速率限制是用来保护API服务器不被过度使用。在我们的测试中,我们并不需要它。 我们提高了限制,想看看调度速度的边界。它没有完全解决问题。
结果,我们又发现了一个速率限制,并且修复了一个程序的非有效路径。在那些变化之后,我们得以看到了最终的提高。在100个节点的建立中,平均建立速度从20pod/秒提高到超过300pod/秒,在50秒中完成,平均pod吞吐量在60pod/秒。
目光转向调度器
然后我们尝试在1千个节点的集群上进行提高。然而,我们这次没这么幸运。看看下图:
在一千个节点的集群上,花了5243秒来跑3万个pod,平均吞吐量在5.72pod/秒。Creation rate持续增加,但running rate一直比较低。
我们然后又进行了测试,查看调度器的性能指标。这次,调度延迟变成了开始时的60ms,到结束时(即3万个pod)增加到了200ms。结合我们在日志和图表中所见,我们意识到以下两点:
60ms的延迟很高。调度器很可能变成了瓶颈。如果我们能把它降低,这样就能增加调度速度,这样pod的running rate就很有可能变高。 调度延迟随着被调度pod总体数量的增加而上升,这导致了集群pod的running rate的下降。这在调度器里面是个扩容问题。
调度器的代码库是很复杂的,我们需要很细致的归档才能理解调度器在哪块花费了时间。但是,在Kubernetes上面重复同样的过程是很耗时的,我们的测试用了超过两个小时才完成。我们想要一个更加轻便一些的方法来做调度器组建测试,从而集中我们的精力和时间。这样,我们就能写一个像Go unit test那样的调度器的benchmark做基准工具。这个工具测试调度器作为一个整体,而不用启动不必要的部件。更多细节可以从我们的幻灯片(https://docs.google.com/presentation/d/1HYGDFTWyKjJveAk_t10L6uxoZOWTiRVLLCZj5Zxw5ok/edit?usp=sharing)中找到关于调度器性能测试以及在Kubernetes pull request中看到。
通过使用benchmarking做基准的工具,我们高效地工作,打破调度器在1千个节点集群的3万个pod建立上的瓶颈。
例如,在Kubernetes的issue#18126(https://github.com/kubernetes/kubernetes/issues/18126)中,我们有如下pprof (performance profiling)结果:
总共的79秒钟内,Round()方法只用了18秒。我们觉得这对于取整而言是失效的。我们用更加有效的实现方式调整了这个问题。结果,我们把对1千个节点上调度1千个pod的平均调度延迟时间从53秒降低到23秒。
这样,我们可以挖掘出更多没有效率且成为瓶颈的代码。在以下的upstream issue中我们进行了汇报并做了提升:
https://github.com/kubernetes/kubernetes/pull/18170 https://github.com/kubernetes/kubernetes/issues/18255 https://github.com/kubernetes/kubernetes/pull/18413 https://github.com/kubernetes/kubernetes/issues/18831