容数据服务集结号 2019-09-08
作者
阿里云容器平台技术专家 王程
阿里云容器平台技术专家 张晓宇(衷源)
不知道大家有没有过这样的经历,当我们拥有了一套 Kubernetes 集群,然后开始部署应用的时候,我们应该给容器分配多少资源呢?很难说。由于 Kubernetes 自己的机制,我们可以理解容器的资源实质上是一个静态的配置。如果我发发现资源不足,为了分配给容器更多资源,我们需要重建 Pod。如果分配冗余的资源,那么我们的 worker node 节点似乎又部署不了多少容器。试问,我们能做到容器资源的按需分配吗?这个问题的答案,我们可以在本次分享中和大家一起进行探讨。
首先允许我们根据我们的实际情况抛出我们实际生产环境的挑战。或许大家还记的,2018 的天猫双 11,一天的总成交额达到了 2135 亿。由此一斑可窥全豹,能够支撑如此庞大规模的交易量背后的系统,其应用种类和数量应该是怎样的一种规模。在这种规模下,我们常常听到的容器调度,如:容器编排,负载均衡,集群扩缩容,集群升级,应用发布,应用灰度等等这些词,在被超大规模集群这个词修饰后,都不再是件容易处理的事情。规模本身也就是我们最大的挑战。如何运营和管理好这么一个庞大的系统,并遵循业界 dev-ops 宣传的那样效果,犹如让大象去跳舞。但是马老师说过,大象就该干大象该干的事情,为什么要去跳舞呢。
大象是否可以跳舞,带着这个问题,我们需要从淘宝天猫等 APP 背后系统说起。这套互联网系统应用部署大致可分为三个阶段,传统部署,虚拟机部署和容器部署。相比于传统部署,虚拟机部署有了更好的隔离性和安全性,但是在性能少不可避免的产生了大量损耗。而容器部署又在虚拟机部署实现隔离和安全的背景下,提出了更轻量化的解决方案。我们的系统也是沿着这么一条主航道上运行的。假设底层系统好比一艘巨轮,面对巨量的集装箱---容器,我们需要一个优秀的船长,对它们进行调度编排,让系统这艘大船可以避开层层险阻,操作难度降低,且具备更多灵活性,最终达成航行的目的。
在开始之初,想到容器化和 Kubernetes 的各种美好场景,我们理想中的容器编排效果应该是这样的:
然而理想很丰满,现实很骨感。迎接我们的是杂乱和形态各异的窘迫。杂乱是因为:作为一个异军突起的新型技术栈,很多配套工具和工作流的建设处于初级阶段。Demo 版本中运行良好的工具,在真实场景下大规模铺开,各种隐藏的问题就会暴露无遗,层出不穷。从开发到运维,所有的工作人员都在各种被动地疲于奔命。另外,“大规模铺开”还意味着,要直接面对形态各异的生产环境:异构配置的机器,复杂的需求,甚至是适配用户的既往的使用习惯等等。
除了让人心力交瘁的混乱,系统还面临着应用容器的各种崩溃问题:内存不足导致的 OOM, CPU quota 分配太少导致的,进程被 throttle,还有带宽不足,响应时延大幅上升...甚至是交易量在面对访问高峰时候由于系统不给力导致的断崖式下跌等等。这些都使我们在大规模商用 Kubernetes 场景中积累非常多的经验。
问题总要进行面对的。正如某位高人说过:如果感觉哪里不太对,那么肯定有些地方出问题了。于是我们就要剖析,问题究竟出在哪里。针对于内存的 OOM,CPU 资源被 throttle,我们可以推断我们给与容器分配的初始资源不足。
资源不足就势必造成整个应用服务稳定性下降的问题。例如上图的场景:虽然是同一种应用的副本,或许是由于负载均衡不够强大,或者是由于应用自身的原因,甚至是由于机器本身是异构的,相同数值的资源,可能对于同一种应用的不同副本并具有相等的价值和意义。在数值上他们看似分配了相同的资源,然而在实际负载工作时,极有可能出现的现象是肥瘦不均的。
而在资源 overcommit 的场景下,应用在整个节点资源不足,或是在所在的 CPU share pool 资源不足时,也会出现严重的资源竞争关系。资源竞争是对应用稳定性最大的威胁之一。所以我们要尽力在生产环境中清除所有的威胁。
我们都知道稳定性是件很重要的事情,尤其对于掌控上百万容器生杀大权的一线研发人员。或许不经心的一个操作就有可能造成影响面巨大的生产事故。因此,我们也按照一般流程也做了系统预防和兜底工作。在预防维度,我们可以进行全链路的压力测试,并且提前通过科学的手段预判应用需要的副本数和资源量。如果没法准确预算资源,那就只采用冗余分配资源的方式了。在兜底维度,我们可以在大规模访问流量抵达后,对不紧要的业务做服务降级并同时对主要应用进行临时扩容。但是对于陡然增加几分钟的突增流量,这么多组合拳的花费不菲,似乎有些不划算。或许我们可以提出一些解决方案,达到我们的预期。
回顾一下我们的应用部署情况:节点上的容器一般分属多种应用,这些应用本身不一定,也一般不会同时处于访问的高峰。对于混合部署应用的宿主机,如果能都错峰分配上面运行容器的资源或许更科学。
应用的资源需求可能就像月亮一样有阴晴圆缺,有周期变化。例如在线业务,尤其是交易业务,它们在资源使用上呈现一定的周期性,例如:在凌晨、上午时,它的使用量并不是很高而在午间、下午时会比较高。打个比方:对于 A 应用的重要时刻,对于 B 应用可能不那么重要,适当打压B应用,腾挪出资源给A应用,这是个不错的选择。这听起来有点像是分时复用的感觉。但是如果我们按照流量峰值时的需求配置资源就会产生大量的浪费。
除了对于实时性要求很高的在线应用外,我们还有离线应用和实时计算应用等:离线计算对于 CPU 、Memory 或网络资源的使用以及时间不那么敏感,所以在任何时间段它都可以运行。实时计算,可能对于时间敏感性就会很高。早期,我们业务是在不同的节点按照应用的类型独立进行部署。从上面这张图来看,如果它们进行分时复用资源,针对实时性这个需求层面,我们会发现它实际的最大使用量不是 2+2+1=5,而是某一时刻重要紧急应用需求量的最大值,也就是 3 。如果我们能够数据监测到每个应用的真实使用量,给它分配合理值,那么就能产生资源利用率提升的实际效果。
对于电商应用,对于采用了重量级 java 框架和相关技术栈的 web 应用,短时间内 HPA 或者 VPA 都不是件容易的事情。先说 HPA,我们或许可以秒级拉起了 Pod,创建新的容器,然而拉起的容器是否真的可用呢。从创建到可用,可能需要比较久的时间,对于大促和抢购秒杀-这种访问量“洪峰”可能仅维持几分钟或者十几分钟的实际场景,如果我们等到 HPA 的副本全部可用,可能市场活动早已经结束了。至于社区目前的 VPA 场景,删掉旧 Pod,创建新 Pod,这样的逻辑更难接受。所以综合考虑,我们需要一个更实际的解决方案弥补 HPA 和 VPA 的在这一单机资源调度的空缺。
我们首先要对解决方案设定一个可以交付的标准:那就是 “既要稳定性,也要利用率,还要自动化实施,当然如果能够智能化那就更好”。
然后再交付标准进行细化:
上图是我们最初的工具流程设计:当一个应用面临很高的业务访问需求时,体现在 CPU、Memory 或其他资源类型需求量变大,我们根据 Data Collector 采集的实时基础数据,利用 Data Aggregator 生成某个容器或整个应用的画像,再将画像反馈给 Policy engine。 Policy engine 会瞬时快速修改 容器 Cgroup 文件目录下的的参数。我们最早的架构和我们的想法一样朴实,在 kubelet 进行了侵入式的修改。虽然我们只是加了几个接口,但是这种方式确实不够优雅。每次 kubenrnetes 升级,对于 Policy engine 相关组件升级也有一定的挑战。
为了做到快速迭代并和 Kubelet 解耦,我们对于实现方式进行了新的演进。那就是将关键应用容器化。这样可以达到以下功效:
当然在后续演进中,我们也在尝试和 HPA,VPA 进行打通,毕竟这些和 Policy engine 是存在着互补的关系。因此我们架构进一步演进成如下情形。当 Policy engine 在处理一些更多复杂场景搞到无力时,上报事件让中心端做出更全局的决策。水平扩容或是垂直增加资源。
下面我们具体讨论一下 Policy engine 的设计。Policy engine 是单机节点上进行智能调度并执行 Pod 资源调整的核心组件。它主要包括 api server,指挥中心 command center 和执行层 executor。其中 api server 用于服务外界对于 policy engine 运行状态的查询和设置的请求;command center 根据实时的容器画像和物理机本身的负载以及资源使用情况,作出 Pod 资源调整的决策。Executor 再根据 command center 的决策,对容器的资源限制进行调整。同时,executor 也把每次调整的 revision info 持久化,以便发生故障时可以回滚。
指挥中心定期从 data aggregator 获取容器的实时画像,包括聚合的统计数据和预测数据,首先判断节点状态,例如节点磁盘异常,或者网络不通,表示该节点已经发生异常,需要保护现场,不再对Pod进行资源调整,以免造成系统震荡,影响运维和调试。如果节点状态正常,指挥中心会策略规则,对容器数据进行再次过滤。比如容器 cpu 率飙高,或者容器的响应时间超过安全阈值。如果条件满足,则对满足条件的容器集合给出资源调整建议,传递给executor。
在架构设计上,我们遵循了以下原则:
稳定,这包括以下几个方面:
在资源调整方面,Cgroup 支持我们对各个容器的 CPU、内存、网络和磁盘 IO 带宽资源进行,目前我们主要对容器的 CPU 资源进行调整,同时在测试中探索在时分复用的场景下动态调整 memory limit 和 swap usage 而避免 OOM 的可行性;在未来我们将支持对容器的网络和磁盘 IO 的动态调整。
上图展示了我们在测试集群得到的一些实验结果。我们把高优先级的在线应用和低优先级的离线应用混合部署在测试集群里。SLO 是 250ms,我们希望在线应用的 latency 的 95 百分位值低于阈值 250ms。在实验结果中可以看到,在大约90s前,在线应用的负载很低;latency 的均值和百分位都在 250ms 以下。到了 90s后,我们给在线应用加压,流量增加,负载也升高,导致在线应用 latency 的 95 百分位值超过了 SLO。在大约 150s 左右,我们的小步快跑控制策略被触发,渐进式地 throttle 与在线应用发生资源竞争的离线应用。到了大约 200s 左右,在线应用的性能恢复正常,latency 的 95 百分位回落到 SLO 以下。这说明了我们的控制策略的有效性。
下面我们总结一下在整个项目的进行过程中,我们收获的一些经验和教训,希望这些经验教训能够对遇到类似问题和场景的人有所帮助。
总结起来,我们的工作主要实现了以下几方面的收益:
展望未来,我们希望在以下几个方面加强和扩展我们的工作:
如果大家对于我们的项目代码感兴趣的话,预计 2019 年 9 月份,我们的工作也将出现在阿里巴巴开源项目OpenKruise (https://github.com/openkruise)中,敬请期待!
搜索「阿里巴巴云原生公众号」获取更多K8s容器技术内容