优化Kubernetes集群负载的技术方案探讨

 

摘要:Kubernetes的资源编排调度使用的是静态调度,将Pod Request Resource与Node Allocatable Resource进行比较来决定Node是否有足够资源容纳该Pod。静态调度带来的问题是,集群资源很快被业务容器分配完,但是集群的整体负载非常低,各个节点的负载也不均衡。本文将介绍优化Kubernetes集群负载的多种技术方案。

Kubernetes为什么使用静态调度

静态调度,是指根据容器请求的资源进行装箱调度,而不考虑节点的实际负载。静态调度最大的优点就是调度简单高效、集群资源管理方便,最大的缺点也很明显,就是不管节点实际负载,极容易导致集群负载不高。

Kubernetes为什么会使用静态调度呢?因为要做好通用的动态调度几乎是不可能的,对,是通用的动态调度很难都满足不同企业不同业务的诉求,结果可能适得其反。那是不是我们就没必要去往动态调度做技术尝试呢?未必!平台根据托管的业务属性,可以适当的通过scheduler extender的方式扩展Kubernetes Scheduler来做一定权重的动态调度决策。

集群资源构成

以cpu资源为例,一个大规模Kubernetes集群的资源组成结构大致如下:

优化Kubernetes集群负载的技术方案探讨_第1张图片

由以下几部分组成:

  • 每个节点的预留资源,对应kubelet的system-reserved, kube-reserved, eviction-hard配置的资源之和,Kubernetes计算Node的Allocatable资源时会减去这部分预留资源。
  • 目前我们集群的平均资源碎片大概在5%~10%左右,根据不同规格的CVM机型略有不同。这些资源碎片分散在集群中的各个节点,以1c1g, 2c2g, 3cxg为主,平台提供用户选择的容器规格都很难match到这些碎片,经常存在这这种情况:要调度某个Pod时,发现某个节点上的cpu足够,但是mem不足,或者相反。
  • 剩下的就是可以被业务Pod真正分配使用的资源了,业务在选择容器规格时带有一定的主观性和盲目性,导致业务容器的负载很低,这样的业务占比一大就容易导致集群低负载的情况,但是集群按照Kubernetes静态调度策略又无法再容纳更多的业务容器了。如上图中所示的,集群分配cpu水位线很高,但是实际cpu利用率不高的情况。

提升集群负载的方案

除了借助强大的容器监控数据做一定权重的动态调度决策之外,是否还有其他方案可以用于解决静态调度带来的集群低负载问题呢?下面我将给出一整套技术方案,从多个技术维度来尝试提升Kubernetes集群负载。

优化Kubernetes集群负载的技术方案探讨_第2张图片

Pod分配资源压缩

前面提到,研发同学部署业务选择容器资源规格时,带有一定的盲目性,而且Kubernetes原生也不支持实时无感知的修改容器规格(虽然这可以通过Static VPA方案解决),导致业务容器负载低。为了解决这个问题,我们可以给Pod Request Resource做一定比例的压缩(Pod Limit Resource不压缩)。注意压缩Pod Request Resource只发生在Pod创建或者重建的时候,比如业务做变更发布之时,对于正常运行中的Pod不能做这一动作,否则可能导致对应Workload Controller重建Pod(取决于Workload的UpdateStrategy)对业务带来影响。

需要注意的是:

  • 每个Workload负载变动规律不同,因此Pod分配资源压缩比例也对应不一样,需要支持每个Workload自定义配置,而且这是对用户无感知的。这个压缩比,我们设置到Workload的Annotation中,比如cpu资源压缩对应Annotation stke.platform/cpu-requests-ratio

  • 压缩比,谁去设置?自研组件(Pod-Resource-Compress-Ratio-Reconciler)基于Workload的历史监控数据,动态的/周期性去调整压缩比。比如某Workload连续7d/1M的负载持续很低,那么可以把压缩比设置的更大,以此让集群剩余可分配资源更大,容纳更多的业务容器。当然实际上压缩比的调整策略并不会这么简单,需要更多的监控数据来辅助。

  • Pod分配压缩特性一定要是可以关闭的和恢复的,通过Workload Annotation stke.platform/enable-resource-compress: "n"针对Workload级别disable,通过设置压缩比为1进行压缩恢复。

  • 何时通过压缩比去调整Pod Spec中的Request Resource?Kubernetes发展到现阶段,直接改Kubernetes代码是最愚蠢的方式,我们要充分利用Kubernetes的扩展方式。这里,我们通过kube-apiserver的Mutating Admission Webhook对Pod的Create事件进行拦截,自研webhook(pod-resource-compress-webhook)检查Pod Annotations中是否enable了压缩特性,并且配置了压缩比,如果配置了,则根据压缩比重新计算该Pod的Request Resource,Patch到APIServer。

优化Kubernetes集群负载的技术方案探讨_第3张图片

Node资源超卖

Pod资源压缩方案,是针对每个Workload级别的资源动态调整方案,优点是细化到每个Workload,能做到有的放矢,缺点是业务不做变更发布,就没有效果,见效慢。

Node资源超卖方案是针对Node级别的资源动态调整方案,根据每个节点的真实历史负载数据,进行不同比例的资源超卖。

  • 每个节点的资源超卖比例,我们设置到Node的Annotation中,比如cpu超卖对应Annotation stke.platform/cpu-oversale-ratio

  • 每个节点的超卖比例,谁去设置?自研组件(Node-Resource-Oversale-Ratio-Reconciler)基于节点历史监控数据,动态的/周期性的去调整超卖比例。比如某个Node连续7d/1M持续低负载并且节点已分配资源水位线很高了,那么可以把超卖比例适当调高,以此使Node能容纳更多的业务Pod。

  • Node超卖特性一定要是可以关闭和还原的,通过Node Annotation stke.platform/mutate: "false"关闭Node超卖,Node在下一个心跳会完成资源复原。

  • 何时通过压缩比去调整Node Status中的Allocatable&Capacity Resource?同样的,我们通过kube-apiserver的Mutating Admission Webhook对Node的Create和Status Update事件进行拦截,自研webhook(node-resource-oversale-webhook)检查Node Annotations中是否enable了超卖并且配置了超卖比,如果配置了,则根据安超卖比重新计算该Node的Allocatable&Capacity Resource,Patch到APIServer。

Node资源超卖,表面上看起来很简单,但实际上要考虑的细节还很多:

  • Kubelet Register Node To ApiServer的详细原理是什么,通过webhook直接Patch Node Status是否可行?

  • 当节点资源超卖后,Kubernetes对应的Cgroup动态调整机制是否能继续正常工作?

  • Node status更新太频繁,每次status update都会触发webhook,大规模集群容易对apiserver造成性能问题,怎么解决?

  • 节点资源超卖对Kubelet Eviction的配置是否也有超配效果,还是仍然按照实际Node配置和负载进行evict? 如果对Evict有影响,又该如解决?

  • 超卖比例从大往小调低时,存在节点上 Sum(pods' request resource) > node's allocatable情况出现,这里是否有风险,该如何处理?

  • 监控系统对Node的监控与Node Allocatable&Capacity Resource有关,超卖后,意味着监控系统对Node的监控不再正确,需要做一定程度的修正,如何让监控系统也能动态的感知超卖比例进行数据和视图的修正?

  • Node Allocatable和Capacity分别该如何超卖?超卖对节点预留资源的影响是如何的?

这里涉及的Kubernetes技术细节比较多,我将在下一篇文章中详细介绍。

优化Kubernetes集群负载的技术方案探讨_第4张图片

优化AutoScale能力

提起Kubernetes的弹性伸缩,大家比较熟悉的是HPA和HNA,一个对Workload的Pod进行横向伸缩,一个对集群中Node进行横向伸缩。社区中还有一个VPA项目,用来对Pod的资源进行调整,但是需要重建Pod才能生效,VPA存在的意义就是要快速扩容,如果像HPA一样,需要重建Pod启动应用来扩容,其实已经失去了价值。

Kube-controller-manager内置的HPA-Controller存在以下问题:

  • 性能问题:一个goroutine中循环遍历集群中所有的HPA对象,针对每个HPA对象获取对应的Pod监控数据、计算新Replicas,这对于大业务是比较耗时的。

  • 核心配置不支持Workload自定义:HPA伸缩响应时间是每个业务都可能不一样的,有些业务期望能5s进行响应,有些业务觉得60s就够了。而内置HPA Controller在响应时间控制上只能配置全局的启动参数horizontal-pod-autoscaler-sync-period。还有每个业务对负载的抖动容忍是不一样的,在内置的HPA Controller中只能通过horizontal-pod-autoscaler-tolerance做全局配置,无法提供业务级的自定义。

  • Kubernetes目前对custom metrics的支持,只能注册一个后端监控服务,如果集群中有些业务通过prometheus来expose应用自定义指标,也有一些业务通过Monitor来监控应用自定义指标,这个时候就做不到All in了,这在for自研上云的场景中,是一定存在的场景。

我们自研了HPAPlus-Controller组件:

  • 每个HPA对象会启动一个goroutine协程专门负责该HPA对象的管理和计算工作,各个协程并行执行,极大的优化了性能。HPAPlus-Controller独立部署,其资源需求可以是集群规模和HPA数量进行合理调整,相比于原内置HPA-Controller有更大的灵活性。

  • HPAPlus-Controller支持各个HPA对象自定义伸缩响应时间,支持自动感应业务是否在变更发布并决定是否要禁用HPA(某些业务有这样的需求:升级时禁止触发弹性伸缩),支持基于pod resource limit为基数进行Pod资源利用率计算,从而推导出扩缩容后的期望replicas,这一点对于节点超卖和Pod资源压缩后的集群非常重要。

  • 支持业务级别对负载的抖动容忍度的个性化配置。

  • 支持基于更多维度的监控数据进行Scale决策,比如Pod历史7d/1M的cpu负载。

  • 支持CronHPA,满足规律性扩缩容的业务诉求。

  • 通过Extension APIServer的方式对接公司Monitor监控,保留Prometheus-Adaptor的方式来支持基于Prometheus的应用监控,满足基于多种应用监控系统的custom metrics进行HPA。

注意:HPAPlus-Controller与Kubernetes buit-in HPA-Controller存在功能冲突,上线前需要disable kube-controller-manager的HPA-Controller控制器。

优化Kubernetes集群负载的技术方案探讨_第5张图片

除了HPA的优化和增强之外,我们也在进行Dynamic VPA技术研发,后续再单独文章介绍。

其他技术方案

另外,通过scheduler extender的方式开发动态调度器、基于业务级的配额动态管理组件、基于业务优先级和配额管理的在线离线业务混部能力、主动探测节点资源碎片信息并上报到控制器进行Pod的再漂移进行资源碎片管理等方案,也是我们正在进行实践的方向,对应方案及实现复杂度更高,后续再单独文章介绍。

总结

本文介绍了Kubernetes静态调度带来的集群资源分配水位线高但集群实际负载低的问题进行了技术方案上的探讨,详细介绍了Pod资源动态压缩、节点资源动态超卖、优化AutoScale的能力的技术方案,后面会再对动态调度、动态业务配额管理、在线离线业务混部方案进行介绍。所有这些集群负载提升方案,要做到动态,都强依赖于强大的容器监控系统。我们正与腾讯云监控产品团队深入合作,更好的服务于腾讯自研业务上云

你可能感兴趣的:(互联网,编程,Java)