作者:闲鱼技术——影湛
引言
闲鱼的服务端技术架构正向着云原生/Serverless化发展,Serverless具有着运维自动化、按需加载、弹性伸缩、强隔离性、敏捷开发部署等技术特点,带来了降低人力成本、降低风险、降低基础设施成本、降低交付时间等核心优势。这其中,弹性伸缩是Serverless中被广泛关注的一大亮点,甚至有些人将自动扩缩容的能力支持作为应用是否Serverless化的判定标准。另外,在闲鱼与淘系Gaia FaaS平台共建Serverless化的业务迁移时,弹性伸缩也一直是个悬而未决的问题。为此,本文会针对Serverless的弹性伸缩技术的现状做一个初步的调研。
弹性伸缩的基本概念
定义
弹性伸缩所关注的问题主要是容量规划与实际集群负载间的矛盾,当现有集群的资源无法承载流量压力时,如果通过调整集群的规模或者资源的分配,从而保障系统的稳定性,同样在集群负载较低时,尽量降低集群的资源配置从而减少闲置资源的浪费带来的成本开销。弹性伸缩不仅适合业务量不断波动的应用程序, 同时也适合业务量稳定的应用程序。
弹性伸缩可以分为应用伸缩、技术伸缩和资源伸缩,我们通常所说的伸缩大都指的是资源的伸缩性,即调整机器的硬件资源以提高系统性能。而资源伸缩又可以分为横向水平伸缩和纵向垂直伸缩。纵向垂直伸缩是指通过提升集群内每个节点的处理能力以提升整体性能的伸缩方式,简单来说就是给单节点机器配置升级;而横向伸缩是指通过增删集群节点来提升或降低系统的处理能力,优点在于伸缩更加灵活,由于节点间资源是完全隔离的,也不必担心单点故障对其他节点的影响,更加安全可靠。因此在大部分Serverless应用场景中都采用横向水平伸缩方式。
弹性伸缩算法
弹性伸缩算法一般可分为基于某个资源指标阈值的响应式伸缩算法和基于预测型的伸缩算法,前者通过周期性的资源指标数据,与所设定的指标阈值进行对比,从而动态的改变集群节点数量;后者则通过对历史的系统性能与资源配置数据的分析,对未来的容量进行评估预测,相比于基于阈值的响应式伸缩算法,基于预测型的伸缩算法能更快的响应突发状况,弹性伸缩变化更加的平滑,但是分析数据所需要的模型建立也显得尤为关键,具有比较大的挑战难度。
通常在业务场景的落地和大部分的开源解决方案中,弹性伸缩的算法都是基于阈值来实现的,比如CPU使用率、Load、内存使用率、磁盘使用率等,通过设定一个资源的Buffer水位来避免系统的过载发生。
云原生应用Kubernetes弹性实践
目前云原生应用的设计理念已被越来越多的业内人士所认可,而Kubernetes作为云原生的标准接口实现,已经成为了容器编排的事实标准。云服务的能力可以通过Cloud Provider、Operator等方式从Kubernetes的标准接口向业务层透出,而业务开发者可以基于Kubernetes构建自己的云原生应用,基于Kubernetes的各种云原生应用生态让Kubernetes成为了“云OS”。
Kubernetes弹性伸缩组件
Kubernetes提供了一系列标准的弹性伸缩组件,从方向上可分为横向水平和纵向垂直两种伸缩方式,从对象上可分为按Pod伸缩和按节点伸缩。这样就存在节点水平伸缩组件Cluster-Autoscaler、Pod水平伸缩HPA&Cluster-Proportional-Autoscaler以及Pod垂直伸缩VPA&Resizer这三大组件。其中HPA组件最为常用,HPA可以基于CPU利用率自动扩展Pod数量,当然也支持基于其他应用程序提供的自定义度量指标来执行自动扩缩。Pod 水平自动扩缩特性由 Kubernetes API 资源和控制器实现,资源决定了控制器的行为。 控制器会周期性的调整副本控制器或部署中的副本数量,以使得 Pod 的平均 CPU 利用率与用户所设定的目标值匹配。
HPA
Kubernetes资源指标数据
目前HPA的API共有三个版本,分别是autoscaling/v1、autoscaling/v2beta1和autoscaling/v2beta2,其中autoscaling/v1只支持基于CPU指标的伸缩,v2beta版本额外支持内存等其他资源指标,并且还支持自定义指标的伸缩。所有的系统资源指标数据,都可以通过Metrics API来获取,而自定义指标数据的采集是通过Agrregator APIServer扩展机制来实现的,HPA使用这些metrics信息来实现动态伸缩。
HPA算法介绍
HPA的主要伸缩流程如下:
- HPA控制器根据当前指标和期望计算扩缩比例
期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]
- 计算期望指标与当前指标的比值得到一个伸缩系数,如果系数大于1表示扩容,小于1表示缩容,指标数值有平均值、平均使用率、裸值三种类型,每种类型的数值都有对应的算法。需要注意的是,如果伸缩系数未达到某个容忍值,HPA控制器会认为变化太小而忽略这次系数的变化,容忍值默认为0.1。
HPA伸缩算法是一个相对比较保守的算法,如果某个Pod获取不到资源指标或者资源没有准备好的情况下,在进行扩容操作时,该Pod的资源指标均不会加入计算。如果创建HPA的时候指定了多个指标,那个会按每个指标分别计算扩缩容副本数,并取最大值进行扩缩容。另外,在HPA执行伸缩操作前,会记录扩缩建议信息,HPA控制器会在操作时间窗口中考虑所有的建议信息,并选择得分最高的建议。这个值可配置,从而可以让系统更为平滑地进行缩容操作,从而消除短时间内指标值快速波动产生的影响。
HPA应用场景
HPA的特性使得部署在HPA伸缩对象上的服务具有非常灵活的自适应能力,当面对某个系统指标的突增时能够在一定限定范围内快速复制多个副本,也可以在指标持续走低的情形下通过删除副本以腾出资源,从而保障了整个系统的稳定性。比较适合存在周期性较大流量波动的业务场景,如电商、金融等应用。
困难与挑战
Kubernetes弹性伸缩挑战
目前大部分开源组件和解决方案都是基于Kubernetes的弹性伸缩组件来实现的,表面上看起来没什么大的问题,但是通过调研发现,随着用户需求的日趋多样化,基于Kubernetes弹性组件的应用扩缩容策略逐渐暴露出很多不足之处:
- 应用级别指标进行弹性伸缩的支持不佳
Kubernetes的内置能力主要作用对象都是容器级别的,提供了管理和编排容器的能力支持,但是大部分产品都是以应用和用户为核心,这样以容器级别的CPU和内存等作为扩缩容指标就显得有点粗粒度,他们更关注RT、QPS、接口成功率等应用级别细粒度的负载指标,围绕者这些指标以提供弹性伸缩能力才是迫切需要的。但比较尴尬的是,尽管Kubernetes HPA组件提供了自定义指标的功能,它的可扩展性整体上还是不够灵活,自定义指标的可插拔性也不够好,尤其当将自定义指标细化到应用层面时,经常不得不去修改底层的HPA组件代码。因此,如何通过一个扩展性更强的、外部框架来进行细粒度的应用扩缩容策略显得尤为关键。
- 不支持应用Scale To Zero的需求
Scale To Zero是指当容器或者Pod处于闲置状态时可以将副本数缩减至0,然后当有请求流量时,可以快速恢复。 在Serverless场景中,Scale To Zero是一个比较典型的自动弹性伸缩场景,可以有效节省资源开销和降低成本。但是Kubernetes HPA并不关注这种场景,因此也不会提供这种能力支持的。
- 容量规划问题
我们在做传统应用的机器配置选型时,一般是按照应用进行分配的,但是在容器尤其Serverless的场景中,开发者是无需关心底层的资源的,这时候就会缺少实际的容量规划这一步,导致在Kubernetes中对于每个节点的预留资源阈值而言,很有可能会造成小节点预留资源过低,无法满足计算需求,而大节点的预留又过高而造成资源浪费的情形。
- 资源分配碎片问题
在同一个Kubernetes集群中,不同规格的机器、不同的场景容量规划可能会有比较大的差异,那么集群伸缩的百分比会存在较大的不一致性与误导性。如果存在4c8g和16c32g两种规格的Pod,在缩容的场景下,为了保证缩容后集群的稳定性,如果我们按照统一的资源利用率来判定缩容机器的标准,那么很有可能造成大规格的机器被缩容后,容器重新调度后的争抢饥饿问题。如果配置优先缩容小规格的机器,就会造成小规格机器的大量冗余。
- 资源利用率问题
当一个Pod资源利用率很低时,不代表就可以侵占它所申请的资源。在大部分线上业务场景中,资源的利用率都不会保持着一个很高的水位,但是从Kubernetes的调度原则中来说,资源的利用又应该保持在一个较高的水位,这样才能避免资源的浪费,这两者间存在着矛盾性,如果权衡是个比较大的难题。
闲鱼Serverless化实践中弹性伸缩挑战
除了上述的Kubernetes中遇到的典型难题外,闲鱼在对服务端架构Serverless化的探索过程中还遇到了其他的一些困难点,影响自动弹性伸缩能力的落地。
- Runtime冷启动时延问题
业务场景中会经常遇到流量突发情况,大部分的非业务形态的系统故障也都是由流量突增引起的,这就要求Serverless具备秒级弹性的能力,如果扩容时间过长,可能流量峰值已经一闪而过,扩容就显得毫无意义。而影响扩容时长的一个重要步骤就是Runtime的冷启动,不同特性的语言Runtime冷启动的时长有比较大的差别,比如用Nodejs / python等语言实现的纯净的Runtime,冷启动完全可以控制在2s以内。但是在我们闲鱼内部的业务场景中,为了兼容原有应用框架,复用弹内的中间件和中台能力,我们有很多是用Java语言基于Spring框架实现的Serverless,冷启动要经历Spring&Pandora启动、插件加载、中间件Client的初始化、Bean初始化等过程,秒级启动几乎是不可能的。
- 上游依赖容量评估问题
如果仅是针对一个独立的应用进行扩容,风险相对还是可控的,但是往往一个Serverless应用都会有多个上游依赖,这个依赖或是领域服务,或是数据存储BaaS服务等。当某个核心链路比如交易下单应用流量突增,我们需要将提供该服务的Serverless应用的资源扩容,但仅扩容该应用是远远不够的,因为上游查询商品领域服务的依赖应用也必然出现流量突增状况,相应的我们也需要对该领域服务进行重新的流量评估并扩容,这就大大加剧了扩容问题的复杂度。
- 流量高峰的资源分配问题
闲鱼大部分核心服务的流量时间分布还是相对比较集中的,弹性伸缩场景下,在流量峰值到达时这些应用都需要进行扩容操作,但是Serverless基于Pod的扩容都是存在一个比较固定的Pod池的,这就要求Pod池的容量Buffer要基于整个闲鱼业务流量峰值来设定,如果容量评估不足,会造成高峰期资源竞争饥饿现象,而容量评估过高时,又会导致在大多数的非高峰期间内的资源浪费现象。当然,如果存在错峰的业务场景并且相应的峰值流量比较平衡时,这个问题就不复存在了。
弹性伸缩方案实践
弹性按阶段可划分为从0到1的Zero-Scaler阶段以及从1到N的扩容阶段,目前业内大部分的实践场景都是围绕着从1到N的弹性扩容方案展开的,关键技术点包括资源池化、容量评估决策、弹性调度、智能自动化、稳定性保障等,而从0到1则主要关注从资源的分配到应用的冷启动的加速实践,下面会针对几个典型的弹性系统进行介绍。
菜鸟弹性调度系统
业务场景
菜鸟的系统应用主要是协调商家、CP、消费者之间的信息流转,而且物流订单流转的长链路多交互的特定也决定了信息流大于实操流,因此系统面临的导购秒杀型的脉冲峰值场景非常稀有。核心应用也大都属于无状态的在线计算型应用,业务峰谷值差距明显,这就为弹性伸缩的落地提供足够的场景支撑。
方舟弹性调度方案
方舟弹性调度提出了一种闭环反馈式的模式,弹性调度基础能力基于应用分组运行情况和不同应用分组的策略配置参数,做出扩缩容决策,并通过方舟的容器操作服务调整集群容器数量;应用分组集群受到集群容器数量变化的影响,会产生不同的表现行为(例如扩容时集群平均CPU使用率会发生变化,服务rt会在一定范围内下降等);应用分组的表现在以实时数据提供给弹性决策的同时,也会进行历史数据的离线存储;自动策略配置会周期性获取这些历史数据,并依照一定的算法,对不同的应用分组进行不同的策略配置,从而再次影响到弹性调度策略的决策。
这种模式具备一定的自我进化能力,当应用分组刚接入弹性时,大多数策略都是默认指定的,而当弹性运行一段时间后,结合自动评估方式,各种参数会得到不断的修正以达到更好的弹性效果。而且,这种模式能以更高的抽象层次来进行海量参数的配置,以解决普遍问题。
方舟弹性调度架构体系
方舟弹性调度采用三层决策模型,分别是策略决策、聚合决策和执行决策,解决了计算的无状态、幂等和高可用的问题,决策策略支持快速横向扩展,目前已上线的策略包括资源安全策略、资源优化策略、时间策略、服务安全策略等。
目前方舟弹性调度系统能解决实际场景中毛刺问题,能实现整条链路的容量弹性伸缩,面对大促场景也能游刃有余,但是在处理无规律脉冲型流量的应用时,还是存在很大的缺陷的。
新浪微博弹性实践
业务场景
新浪微博总是在特定的时间和特定的事件发生时引来流量高峰,常见的峰值场景为日常的晚高峰、各种运营活动以及明星大V的热门微博带来的流量高峰、极端突发事件引起的流量陡增。这些场景的流量特点可以总结为瞬间峰值高和互动时间短。
微博自动弹性伸缩方案
无人值守的扩缩容的三个核心技术是弹性伸缩、故障迁移和服务自动回收,与之对应的是模板中的原子型API。
在平台扩容时需要向混合云平台发起请求,平台进行资源评估,如果配额足够,可以按照配额量申请,如果配额不足时,需要申请配额,防止资源滥用。上述步骤可以抽象成原子型API任务,以支持自动化的实现。目前新浪研发了一套原子型API任务分发系统,它在每台机器上都有一个Agent,在中心化组件Dispatch端根据任务模板定义任务,并最终对外提供API。
在容量决策方面,该方案支持两种决策方式,一种是定时触发,根据自动压测的指标和经验值触发进行,另一种是自动触发,通过分析业务指标,对容量预估进行同比和环比分析自动进行扩缩容操作。
阿里云平台解决容器资源按需分配的实践
核心痛点是不合理的资源初始化分配和应用容器资源竞争的稳定性问题,以及潮汐应用分时复用和减少资源碎片的资源利用率问题。交付目标是既要稳定性,也要利用率,还要自动化实施和智能化。
方案设计
最初的工具流程设计:当一个应用面临很高的业务访问需求时,体现在 CPU、Memory 或其他资源类型需求量变大,我们根据 Data Collector 采集的实时基础数据,利用 Data Aggregator 生成某个容器或整个应用的画像,再将画像反馈给 Policy engine。 Policy engine 会瞬时快速修改容器 Cgroup 文件目录下的的参数。这种方案对Kubelet进行了侵入式的修改,每次Kubernetes升级对于Policy engine相关组件升级也是一种挑战。
因此需要将Kubelet解耦开,可以将关键应用容器化,这样可以不侵入式修改Kubernetes核心组件、支持快速迭代、借助Kubernetes相关QoS Class机制,容器资源配置和资源开销可控。
这其中,最为关键的就是策略引擎(Policy engine)的设计,Policy engine 是单机节点上进行智能调度并执行 Pod 资源调整的核心组件。它主要包括 api server,指挥中心 command center 和执行层 executor,设计如下图所示。
该方案通过分时复用以及将不同优先级的容器(即在线和离线任务)混合部署,并且通过对容器资源限制的动态调整,保证了在线应用在不同负载情况下都能得到足够的资源,从而提高集群的综合资源利用率。通过对单机节点上的容器资源的智能化动态调整,降低了应用间的性能干扰,保障高优先级应用的性能稳定。各种资源调整策略可以自动化地、智能化地在节点上运行,从而降低运维成本。
阿里CSE的Serverless弹性实践
上述的几种弹性伸缩实践都是关注于从1到N的弹性方案,但是往往在实际的弹性场景中从0到1才是最为关键的技术难点,这其中解决如何将应用冷启动速度加速到毫秒级的问题至关重要。
技术方案
阿里CSE团队在解决应用启动加速的问题上沉淀了丰富的经验:
方案一:应用冷启动资源压缩方案
方案二:应用热复制启动加速方案
L1采用通过fork种子进程达到快速启动的效果,操作系统团队专门为此开发了Fork2技术,与linux native fork的关键区别是可以指定PID来fork一个进程,L2的单个SNAPSHOT可以创建多个进程,一对多关系。Fork2在本地进程的复制场景中还是非常快速的,但是仅适用于本地Fork,而且对于Scale To Zero场景也不支持,这时就需要一种支持水平扩容和从0到1扩容的策略,Local Storage技术方案主要用于解决这种问题,它是一种基于CRIU的进程级热复制技术。此外,CSE在应用的热复制的技术探索之路中还提出一种新的弹性技术方案(Zizz),Zizz的核心是热备,基于离线的实例功耗非常低的假设,研发出一种基于弹性堆的低功耗技术、基于内核态和用户态的Swap能力的内存弹性技术以及基于ASI的Inplace Update的实例规格动态升降技术,并通过Kubernetes自定义CRD的方式进行能力输出。
综合来看这两种方案,方案一的适用场景更广,但是每种语言的VM要单独定制,实现成本更高;方案二会存在UUID问题,如开发者希望应用每个实例启动都赋值一个UUID给一个静态变量,优势是语言无关、方案更容易实现,适用于FaaS类场景。
CSE与AWS Lambda的差异
Lambda为了做到快速扩缩容,要求用户的应用以Function为单位开发,Lambda Runtime动态加载Function来快速增加实例。而CSE通过将一个应用的多个实例启动后,共享相同的指令数据,抽取出不同的指令数据,每次启动实例只需要加载多实例的差异部分,可以透明兼容社区主流技术栈。
总结
通过上述弹性伸缩的调研结果以及我们已经探索的一些实践经验,资源池化、弹性调度、容量决策、运维自动化等都已经具备相对比较成熟的解决方案了,基于Kubernetes所提供的基础能力以及各自业务场景所产出的数据模型所构建的弹性系统能从容应对大多数的弹性需求,但是如何更快、更智能化、更安全的让业务Pod弹起来仍然有很长的路要走。目前,闲鱼团队在对传统胶水层应用做自动拆分FaaS化,另外我们还计划将一些底层的中间件和中台服务剥离开集中治理,这样能使得业务应用更加的轻量化,从而加速应用启动时长,更有利于弹性伸缩。
参考文档
- https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
- https://en.wikipedia.org/wiki/Autoscaling
- https://my.oschina.net/u/1464083/blog/3116884
- https://my.oschina.net/u/1464083/blog/3069464
- https://developer.aliyun.com/article/717318
- https://developer.aliyun.com/article/779281
- https://developer.aliyun.com/article/672398
- https://developer.aliyun.com/article/71021
- https://developer.aliyun.com/article/521784
- https://developer.aliyun.com/article/720556
- https://developer.aliyun.com/article/702070