当前位置: 首页 > 科技观察

基于K8S,云原生架构成本优化指南

时间:2023-03-18 11:35:06 科技观察

货拉拉技术中心核心基础设施部架构师韩正,全程带领货拉拉从0到1实现Kubernetes,致力于探索特性Huolala的云原生方式。今天的分享主要包括以下五个方面:1.货拉拉基本情况介绍2.基于K8S的成本优化方法3.符合货拉拉特点的成本优化路线4.竞价实例成本优化实践5.时序扩缩容成本优化实践一、货拉拉基本情况介绍在讨论成本优化之前,我们先来了解一下货拉拉的基本情况,让大家对货拉拉有一个初步的了解。霍拉拉的生产环境100%跑在公有云上,所以我们的成本优化也是针对公有云的。霍拉拉是一家全球性的互联网公司。我们的业务遍布世界各地。我们在新加坡、印度、拉丁美洲和中国都有集群。因此,我们必须有多个集群运行在不同的云厂商上。这就决定了我们所有的解决方案都必须是通用的,不绑定任何云厂商。霍拉拉的车流比较规律,峰谷也比较明显。一般情况下,货运行业不会出现突发情况导致车流量骤增。不像微博这样的服务,明星突然抛出一个瓜,引来一大波吃瓜群众,导致流量骤增。同时,同城货运属于日出而作,日落而息的行业,因此白天的高峰期相对稳定,夜间的低峰期客流量也会降到很低的水平。等级。这样的流量特性使我们能够通过简单的预测算法获得相对较高的性能。良好的预测结果,以及淡季的成本优化也将是我们的优化重点。霍拉拉还会有大量的大数据线下任务。大数据离线任务占我们公司计算资源的一半左右,高峰期刚好是业务的低峰期,所以如何通过离线混合部署来提高整体资源利用率也是我们的优化方向之一.2、基于K8S的成本优化方法说完了货拉拉的基本情况,我们来看看云原生时代通用的成本优化方法。我把主流的成本优化方法分为四类:私有云服务器成本优化公有云服务器成本优化服务器利用率优化服务性能优化由于货拉拉100%跑在公有云上,所以这次不讨论私有云优化。服务性能优化是一个非常个性化的优化,所以不在我们这次的讨论范围之内。本次主要讨论公有云的服务器成本优化和服务器利用率优化。1、公有云服务器成本优化所谓公有云服务器成本优化,其实就是研究如何在保证满足企业实际需求的前提下,以尽可能低的价格购买服务器实例。公有云服务器一般有三种优惠模式:订阅:按固定周期购买服务器。这种模式高度稳定且价格实惠,但不够灵活,尤其是当你想在晚上收缩时。Savingsplan:这种模式实际上是承诺每小时最低消费,以换取折扣。稳定性有保障,服务器也可以随意伸缩和变更,但每小时的总消耗量不能低于承诺消耗量。一般来说,公司会按照最低需求采购。竞价实例:竞价实例是指云厂商通过固定折扣或竞价方式出售闲置实例。折扣通常可以是10%或20%。稳定,随时有被回收的风险。使用此类实例对公司的技术能力和服务质量要求很高。2.服务器利用率优化让我们来看看一些常见的提高服务器利用率的优化方法。1)合理的request/limit如何科学合理的配置request/limit是每个公司在使用k8s时都会遇到的问题。请求调整太大节点利用率低,调整太小服务容易OOM或被驱逐。一般来说,优化方法分为两步。第一步是根据应用配置文件和过去的负载设置一个初始值。第二步是建立定期检查机制,根据实际负载和策略动态调整request/limit配置。2)HPA级别的POD自动扩缩容,预先配置好指标和阈值,当指定指标超过阈值时自动扩容,当指定指标低于阈值时自动缩容,比如CPU高于则扩容35%。HPA非常擅长处理突发流量,但是HPA的扩容有一定的滞后性。如果负载增加太快,可能来不及扩展,导致短期故障或雪崩。3)cronHPA定时扩容和缩容,这个很容易理解。设置时间点和副本数,在扩容时间点扩容,在缩容时间点缩容,适用于可预见或有计划的场景。4)智能调度智能调度是指根据公司的实际需求增强调度器。k8s默认的调度器比较简单,但一般不能完全满足公司的需求,还有很大的优化空间,比如实际负载感知,增加更多维度的权重计算,diskGPU,优化的stackingstrategy,等,通过优化,更符合公司实际情况的调度算法,有效提高节点资源利用率。5)离线混合部署离线混合部署是利用离线任务可中断、高峰期与在线服务相反的特点,充分利用服务器计算能力的一种方式。由于离线任务一般都在晚上运行,如果将离线任务和在线服务整合到一个k8s集群中,正好可以弥补在线服务非高峰期的资源浪费。同时,如果离线任务可以支持随时中断,那么通过自动避让以及资源隔离等手段,也可以利用闲置资源在高峰时段运行离线任务,挤出最后一滴服务器性能。但是,这种能力在大幅提升服务器利用率的同时,也带来了巨大的技术挑战。3.符合货拉拉特点的成本优化路线通过结合上述行业最佳实践和货拉拉的实际情况,我们为货拉拉探索出如下成本优化演进路线。首先,我们通过储蓄计划以相对优惠的价格保证了货拉拉的基础算力和稳定性,然后通过低成本、灵活的竞价实例提供弹性可扩展的算力。当然,我们也做好了面对竞价实例带来的稳定性挑战的准备,基本上解决了服务器价格优化的问题。接下来我们需要解决服务器利用率的问题。首先,我们需要解决在非高峰期浪费资源的问题。刚才说了,货拉拉的流量是非常有规律的,所以我们使用自研的CronHPA来实现可预测、有计划的弹性伸缩,同时在流量突然变大的时候使用HPA来实现应急扩容。这是我们所做的。我们目前做的是智能请求,通过应用画像和过往指标计算出合理的请求和限额,希望通过这个功能提高高峰期的节点资源利用率。当我们完成前面的项目后,相信我们已经大大提高了节点的资源利用率,剩下的就是走最后一公里,通过智能调度和离线混合部署来榨干服务器性能的最后一滴。我们计划在2023年完成这些功能,然后整体继续优化。接下来重点说一下我们做的两个优化,实例竞价和定时扩容。四、竞价实例1、什么是竞价实例?我们都知道,云厂商提供的服务器实例,都是在他们的物理机房里虚拟出来的。由于是物理机房,服务器相对固定,能够提供的计算能力也是固定的,但是我们购买的实例数量是浮动的,所以云厂商总是有一些闲置算力,闲置服务器产生的经济价值为0,所以云厂商将这些闲置算力用于打折或拍卖,这些打折或拍卖-出售的实例是现货实例。所有云工厂的竞价实例来源相同,但各云厂商的竞价实例价格计算方式存在差异。主要有两种方式:一种是固定折扣,基本就是20%的折扣,这个很好理解;另一种是叫价,比较难理解。下面我来介绍一下投标的基本流程。2.竞价方式介绍我们在购买竞价实例的时候,会填写一个可接受的最高价格,然后云厂商会根据大家的报价和库存情况计算出一个价格,高于或等于这个价格的可以出价使用这个如果价格低于价格,则购买失败。上图中有一个细节。不知道你有没有注意到。整个图片只是定价。我这里用的是黑色。那是因为价格的计算方式完全是一个黑匣子。只有云供应商自己知道算法。只能知道他终于开出了一个价格,最低可以低至10折,最高可以等于不打折。3.竞价实例的使用原则下面说一下使用竞价实例的基本原则。首先,我们向云供应商提交购买申请,并附上机器型号和可接受的最高价格。如果云厂商判断指定机型有货且价格低于我们的报价,就会分配一个实例给我们,购买完成。这时候我们就可以正常使用了。在我们正常使用竞价实例的过程中,云厂商会一直监控库存和价格变化。当发现实时价格高于我们的报价或库存不足时,会向我们发出中断信号。收到信号后,我们需要做一些措施来保证在不影响业务的情况下回收实例。实例会在我们收到中断信号几分钟后被回收。不同的云厂商有一些不同的回收机制。AWS有一个预测算法,当预测到库存不足时会提前通知你,让你有更多的反应时间;阿里云有小时保护机制,竞价实例运行的第一个小时可以保证不会被回收。但是没有一个是等我们做完自己想做的事情再回收的,无一例外。比如我们想再买一台机器把服务迁移到那里,但是在迁移之前可能会被回收,所以我们处理突然中断的手段一定要足够快,避免对业务造成影响。4.竞价实例的特点介绍完竞价实例的基本原理,我们来总结一下竞价实例的特点:性价比高。与按需实例相比,Spot实例通常只有其价格的10-20%。与预留实例相比,Spot实例通常只有其价格的30-60%。竞价实例是将闲置资源折价出售。没有闲置资源的时候是买不到的。并不是说你想买的时候云供应商总是有的。随时中断。云提供商将动态检测当前的市场价格和库存。一旦库存不足或您的出价低于市场价格,云提供商可以随时回收这些实例。高性价比是我们使用竞价型实例的目标,库存不安全和随时中断是我们需要解决的问题。5.竞价实例结合K8S的实现接下来介绍竞价实例如何结合K8S实现,以及如何解决竞价实例带来的稳定性问题。首先我们从图中可以看出,我将k8s节点划分为多个节点组,然后通过clusterautoscaler进行弹性伸缩。节点组包括按需实例节点组和竞价实例节点组。这是因为并非所有服务都适合部署在现场实例上。我们需要一些更稳定的节点部署,不适合运行在现场实例上。同时,我们还需要拥有按需实例的节点组,在现货实例库存不足的情况下,提供足够的算力来支持业务的正常运行。然后再来看中断恢复部分。当竞价实例需要中断时,云厂商会发出中断信号。如果是托管集群,云厂商会自己处理中断信号,帮我们建立一个新节点,回收旧节点,但是如果是自建集群,我们就需要自己实现一个中断信号自己处理服务,在这个服务中处理中断问题。虽然我们是在货拉拉托管集群,但是我们开发了一个notifyhandler。该服务主要用于收集中断信号和监控中断频率,以便后续用于报警和触发应急预案。整体结构比较简单,但是我们在实际使用过程中还是发现有很多需要优化的地方。下面我将介绍主要的优化点。六、竞价实例优化要点1)增加竞价实例模型。上面我们提到了竞价实例的存量是没有保证的,所以我们可以做的就是扩大池。池越多,库存不足的概率越低,所以我们创建的节点组需要覆盖更多的可用区和更多的实例模型。但需要注意的是,由于集群autoscaler的限制,同一节点组中节点的CPU和内存必须保持一致。2)设置节点组优先级目前我们集群中有按需实例节点组和竞价实例节点组。我们希望在资源不足的时候,优先弹出竞价实例。只有当竞价实例无法弹出时,按需实例才会弹出。这样可以保证最大限度地利用竞价型实例,同时保证在竞价型实例库存不足时及时弹出按需实例,保证业务的稳定运行。这里使用集群自动缩放器的优先级配置。我们在clusterautoscaler的一个configmap中配置节点组的优先级,设置bidding实例的优先级为20,其他节点组的优先级设置为10,这样ca会先弹出bidding。实例,无法弹出现货实例时弹出点播实例。3)设置podaffinity由于biddinginstance随时可能被打断,一旦被打断,很可能同型号的节点同时被打断,所以要避免把鸡蛋放在一个篮子里,尽量把鸡蛋放在一个篮子里同一个服务的pod分布在不同的实例、不同的模型,甚至不同的可用区,避免某个服务的所有pod同时被驱逐,导致服务不可用。这里用到的是pod的affinity配置。从配置中可以看出,我们给可用区赋予了最大的权重,其次是实例模型,最后是实例名,这样k8s会尝试将同一个app的pod分散到不同的可用区。如果没有合适的节点,则分发到不同的实例类型,仍然没有合适的方式重新分发到不同的实例,这样可以最大程度分散同一个服务的pod,以及因为spotinstance中断导致的服务不可用可以避免。4)配置PDB虽然我们之前采取了很多措施来防止同一个服务的pod同时因为实例中断而被逐出,但是没有一个能100%避免这种情况,所以我们需要另外一层保险,这一层保险的是PDB,通过它我们可以设置同一个服务的pod副本必须同时可用多少个。下图的配置保证了70%的服务副本是正常的,这样即使同时需要逐出,k8s也会在强制保证至少70%的副本下进行逐出副本可用,确保整个流程服务处于可用状态。但是需要注意的是,这个配置会导致本可以并发的驱逐变成串行,影响清空节点的时间,所以这就需要服务启动足够快,能够在节点被清空之前完成整个迁移实际上回收过程。5)使用低优先级的pausepod为集群预留空间。上面我们说了,一个bidding实例从收到中断信号到真正被回收只需要两三分钟的时间。到时候无论什么情况,实例都会被回收,所以我们必须保证在实例被回收之前完成整个迁移动作,而这两三分钟中的每一秒都是非常宝贵的。我们必须想办法提高迁移效率,但是创建一个新节点可能需要几十秒甚至一两分钟,等待新节点就绪已经浪费了很多时间,所以我们需要找到一个方法是在收到中断信号时直接对老节点进行drain,但是要对node进行drain,我们需要随时保证集群有足够的资源。运行被逐出的pod的空间。保证空间足够的方法就是我们现在讲的使用低优先级的pausepod为集群预留空间。k8s中的Pod有一个优先级的概念。当资源不足时,高优先级的Pod可以抢占低优先级的Pod。从下图可以看出,我们在Node1和Node2上都放置了一些低优先级的pod。当Node1被中断回收时,高优先级的Pod会直接抢占低优先级的Pod,实现快速启动。无需等待新节点就绪,所有低优先级的pod都会变为pending状态触发扩容或者等待新节点就绪再启动,从而重新创建预留空间。下面是具体的yaml,这里的优先级可以不为-1,只要低于普通pod的优先级即可。低优先级的pod主要需要正确填写PriorityClassName,然后根据实际情况设置副本数和请求数。能。7、不宜在竞价实例上部署服务票据复制服务。这个不用解释大家都懂。启动时间过长的服务。因为要保证在实例被回收之前能够启动服务,所以服务启动时间不宜过长。任何不能正常停止的服务都不会被容忍。我们需要意识到,无论我们采取多少措施,都无法完全避免实例在被耗尽之前被回收。因此,不能优雅停止、不能容忍的服务不适合部署在现货实例上。有状态的服务。有状态服务的迁移远不如无状态服务灵活。为避免出现各种问题,不建议在Spot实例上部署有状态服务。五、定时扩缩容1、背景弹性伸缩的本质是提高服务器的利用率。我们来看看在货拉拉没有做任何优化的集群的CPU使用率。从这张图我们可以看出这个集群的CPU使用率在白天的高峰期是35%,但是在深夜的低峰期只有2.5%,并且在2点有一个小高峰点了,因为我们在这个时候有大量的定时任务在执行,暂时可以忽略。目前这个利用率如果放在虚拟机时代,还是一个比较可以接受的利用率,但是在云原生时代,我们还有很多方法可以提高这个利用率。我们的优化目标是将节点的平均CPU利用率在高峰期提高到50%,在低峰期提高到30%。2、默认HPA的不足按照正常的做法,我们应该可以通过安装HPA来解决这些问题。它会随着流量的增加而自动扩展,并在流量下降后自动收缩。事实上,我们以前也这样做过,但是我们很快就发现了它的问题。第一个问题是扩张存在一定的滞后性。HPA需要等待负载触发阈值后才开始扩容。如果负载上升太快,扩容可能不及时。我们每天上午9:00和下午2:00有一波流量快速上升。此时,HPA将大量扩能。但大规模扩容需要先扩容大量节点。导致这两个时间点的扩容总是太慢,每天这两个时间点都会报一些错误,然后触发告警。第二个问题是扩展门槛有限。为了在流量上升的白天尽量保持扩容稳定,我们不能把最小副本数设置的太低,这样会导致我们晚上的CPU利用率上不去。为了及时扩容,我们不能将CPU阈值设置得太高,因为阈值越高,扩容时响应越慢,高峰期CPU使用率越低。比如我们在HPA中设置CPU的阈值阈值设置为35%,那么我们的pod的CPU使用率基本不能超过35%,因为如果超过35%,就会扩容,那么CPU使用率就会被拉下。第三个问题是伸缩的时机无法控制。HPA扩容完全基于配置的指标和阈值,不能根据企业特性定制策略。比如货拉拉白天的流量比较稳定,我们不想在一天的高峰时段有太多的扩容和缩容,即使浪费了一些资源。影响稳定性,但是HPA没有提供这样的配置。3.CronHPA+HPA结合我们在HPA方面的实践经验和货拉拉的业务特点,总结探索出一种符合货拉拉特点的横向弹性伸缩方式,即自研的CronHPA+HPA组合。CronHPA根据设定时间调整HPA对应的最小副本数,实现可预测或有计划的弹性伸缩。考虑到货拉拉流量的特点,我们可以在自研的CronHPA中通过定时+过往指标分析+简单的预测算法实现相对优质的预缩放和定时缩放。例如,早上9点流量开始快速上升。我们可以在早上7点到8点提前把集群扩容到峰值级别。晚上10点以后,车流量就下降了很多。我们可以放心的缩容,有预扩容作为基础,我们可以在晚上将副本数量减少到极低的水平,而不用担心白天没有时间扩容。CronHPA不直接操作部署的副本数,而是操作HPA的最小副本数,因为CronHPA虽然可以覆盖我们99%的扩缩容需求,但是还是需要HPA自动扩缩容来解决1%的偶尔的流量激增虽然我们很少遇到流量激增,但是HPA允许我们在高峰期减少容量或者设置副本数量,而无需为偶尔的流量激增添加额外的buff,从而提高整体的资源利用率。4.架构说完时序扩缩容的原理,我们再来说说时序扩缩容的架构。我们在K8S中部署了一个自研的hll-cronhpa-controller,并添加了一个CronHPACRD。用户主要设置CronHPACRD来设置弹性伸缩,并不直接设置HPA和部署副本数。hll-cronhpa-controller会监听CronHPACRD对象的变化。当它发现有新的添加或更新时,它会同步修改HPA对象。除最少份数外,其他均直接透传。CronHPA的所有业务逻辑最终都会体现在HPA最小副本数上。改变。CronHPA的业务逻辑主要是hll-cronhpa-controller根据cronHPA对象的配置,从Prometheus中拉取过去的指标分析,并结合从应用管理平台拉取的应用画像和从应用管理平台拉取的扩缩容策略综合分析。配置中心。然后在合适的时间点给对应的HPA对象设置一个合适的最小副本数,可能有点抽象。例如,假设我目前有一项服务需要在夜间缩减规模。通过分析过去晚上服务的资源利用率,可以发现剩下的2个副本可以在晚上进行缩容。同时我们从应用画像中发现该服务为核心服务,配置中心的扩缩容策略是核心服务的副本数最少不小于5,所以综合分析我们晚上需要给他设置的副本数是5。5.实现路径下面说一下我们的实现路径。我们在刚开始自研的时候就确定了cronHPA+HPA的架构。第一阶段是纯手动配置。我们通过分析流量设置一个全局收缩时间点和一个全局扩展时间点,然后根据经验人工估算每个服务的收缩比例。现阶段由于整体容量在同一时间点扩容和缩容,扩容瞬间对基础设施的压力比较大,人工估算的缩容比例还是过于保守。第二阶段是根据过往指标分析,自动设定涨缩时机,可以有效分散涨缩压力,同时让涨缩时机更加合理。比如有的服务可能晚上6点就没有流量了,第一阶段需要等到晚上10点才能收缩。第三阶段是根据appid自动计算收缩率。手动估算收缩率还是太保守了。比如高峰期100个pod,其实晚上可能只需要10个,但是一般人工评估只会收缩到50个。但是通过算法自动计算会比较科学,自动计算还是可以通过分析不断修正过去的指标。比如一个pod在晚上计算出来scaledown到2个pod,但是分析过去的指标,发现最近7个晚上HPA扩容到3个pod,那么就会进行scaleddown的复制第八天,数字自动调整为3。第四阶段是自动识别低峰时段。本来是手动设置淡季时间段,程序从这个时间段选择扩容和缩容的时机。这个阶段我们可以自动识别每个服务自身业务的低峰期,实现个性化缩容。第五阶段是自动阶段性扩张和收缩。前几个阶段确定一个扩容和缩容的时间点,直接扩容到目标拷贝数。然而,流量实际上是逐渐变化的。通过这个阶段,我们可以实现在服务高峰期之前,随着流量的增加逐渐增加副本数,在非高峰期,随时在流量减少的时候逐渐减少副本数。这个阶段和HPA最大的区别在于,HPA是基于指标和阈值,而cronHPA是基于预测。6.未来规划最后说一下我们对弹性伸缩的未来规划。我们希望未来弹性伸缩能够形成这样的机制。服务初始化时,可以通过应用画像自动设置reqeust/limit和份数,然后在高低峰时自动通过过去的指标。分析和算法实现预缩放和及时收缩。如果遇到突发流量,可以通过就地升级实现快速垂直扩展。in-placeupgrade和VPA最大的区别是不需要重启pod,可以实现秒级扩容。如果所在节点资源不足,则使用HPA进行横向扩容,同时根据指标不断修改reqeust/limit和服务的副本数分析,从而实现全自动弹性缩放闭环。