FaaS扩缩容
Node 维度
Scheduler:调度模块负责将请求打到指定的函数实例上(Pod),同时负责为集群中的 Node 标记状态,记录在 etcd 中;
Local-controller:Node 上的本地控制器,负责管理 Node 上所有函数实例的生命周期,以 DeamonSet 的形式存在;
AutoScaler:自动扩缩容模块,会定期检测集群中 Node 和 Pod 的使用情况,同时根据自定义策略进行扩缩容。扩容时,往往会向底层的 PaaS 资源进行申请,例如百度智能云的 CCE、阿里云的 ACK/ASK 等;
Pod :也就是上图标识的 Cold 和 Warm 两种状态的容器,Cold 表示该 Pod 未被使用,Warm 表示正在被使用或者处于等待回收的状态;
Node:包含闲置、占用两种状态,如果一个 Node 上所有 Pod 都是“Cold”,那么该 Node 为闲置状态,否则为占用。
当系统运转时,AutoScaler 会定期检查集群中所有 Node,如果检测到 Node 处于一个需要扩容的状态,则根据策略进行扩容。当然,这个策略完全可以由自己定义,通常可以按照“占用除以总数的比例是否处于一个合理区间”来进行判定。在这种形态下, Pod 通常会作为函数实例的通用形态。代码以及不同运行时往往也是以挂载的形式注入到容器内。Autoscaler 也会在轮询时根据 Warm Pod 的闲置时间将其重置为 Cold Pod,从而让整个集群资源达到一个较高复用能力的水平。
在缩容时,理想情况下 AutoScaler 可以将 Node 缩容到 0,但通常为了应对突发的流量请求,也会预留一部分 Buffer 存在。会发现,Node 的调度方式对于函数计算调度引擎来说更加的灵活。但“权限越大责任越大”,Node 维度的方案,除了需要管理 Node 的调度之外,还需要处理 Node 中 Pod 的安全隔离和使用。这种形态下,可以通过“空 Pod”的方式来提前占用,预加载一部分 Pod 出来,加快“真实 Pod”的创建速度。
Pod 维度
在 K8s 中,不同的 Metric 会由对应的 Metrics Server 持续采集(Heapster 或自定义 Metrics Server),HPA 会定期通过 Metrics Server 的 API 或者聚合的 API Server 获取到这些 Metric 指标数据(CPU 和 内存使用情况),从而根据自己定义的扩缩容规则计算出 Pod 的期望个数,最后,根据 Pod 当前的实际数量对 RC/Deployment 做出调整,使 Pod 达到期望的数量。
因为 Serverless 语义下的动态扩缩容是可以让服务缩容到 0 的,但 HPA 不能。HPA 的机制是通过监测 Pod 的 Metrics 指标来完成 Deployment 的扩缩容,如果 Deployment 的副本数缩容到 0,流量没有了,指标为 0,那 HPA 也无能为力了。
极致扩缩容
从 0 到 1 的扩缩容过程,是需要额外的机制来加持的。由于目前社区和各大云厂商都非常重视 Knative,部分的传统企业在转型过程中也选择 Knative 作为私有化部署方案的底座首选。选择以 Knative 的 KPA 来做说明。
Knative 的扩缩容主要包括三个方面:流量指标的收集、实例数量的调整、从 0 到 1 的过程。
流量指标的收集
在 Knative 中,Revision 代表一个不变的、某一时刻的代码和 Configuration 的快照。每个 Revision 会引用一个特定的容器镜像和运行它所需要的任何特定对象(例如环境变量和卷),再通过 Deployment 控制函数实例的副本数,而我所说的实例,就是图中的 User Pod 了。和我们之前介绍的 Node 维度扩缩容机制中的函数实例不同,这里,你可以看到每个函数实例(User Pod)中都有两个容器:Queue Proxy 和 User Container。其中,User Container,我相信你一定猜到了,是部署和启动我们自身业务代码的容器,那么 Queue Proxy 呢?实际上,在每个函数实例被创建时,都会被以 Sidecar 的方式将 Queue Proxy 注入,也就是在原来的业务逻辑上再新加一个抽象层。Queue Proxy 作为每一个 User Pod 的流量入口,负责限流和流量统计的工作。每隔一定时间,AutoScaler 都会收集 Queue Proxy 统计的流量数量,作为后续扩缩容的重要依据。通俗地来讲,其实就是在自身业务代码的容器旁边,又加了一个双胞胎 Queue Proxy,双胞胎加入的方法,就是 Sidecar。后续不管在这个房子的(User Pod)兄弟干了什么,都会让这个双胞胎去记录。在最外层的 AutoScaler,则会去找这位双胞胎收集所有的信息,为后续的扩缩容做准备。
实例数量的调整
当收集到流量的指标后,AutoScaler 就需要根据指标调整实例的数量了。Autoscaler 会通过改变实例的 Deployment 来决定实例最终的个数,以便确定扩容出来多少个 Pod。简单的算法,就是按照将当前总的并发数平均划分到期望数量的实例上,使其符合设定的并发值。这里简单举个例子,比如当前总并发数为 100,设定的并发值为 10,那么最终调整出的实例数量就是:100/10=10 个。当然,扩缩容的实例数量还会考虑到系统的当前的负载和调度周期等因素。
从 0 到 1
当 Revision 存在实例时流量接收的情况,那么如果 Revision 实例缩容到了 0,流量又是怎么被接收的呢?是直接丢弃,还是暂存在某个地方呢?
为了解决这一问题,Knative 专门引入了一个叫做 Activator 的组件来做流量暂存和代理负载。
当 AutoScaler 将函数实例缩容为 0 时,会控制 Activator 作为实例为 0 时的流量接收入口,也就是图中红线的部分。Activator 在收到流量后,会将请求和信息暂时缓存,并主动告知 AutoScaler 进行扩容,直至成功扩容出函数实例,Activator 才会将暂存的流量转发到新生成的函数实例上。
扩缩容模型设计思路
指标
无论是 Node 纬度还是 Pod 纬度,会发现最终系统都是根据一些监控的指标来作为扩缩容的依据,同时,这些指标的采集任务往往都是通过定时轮询的方式来完成。比如,Node 扩缩容案例中用的是 Node 处于空闲或占用的数量,HPA 中采用 CPU 利用率、内存使用率来作为 Pod 扩缩容的依据,而 Knative 是通过 Collector 统计 Pod 的并发情况来进行扩缩容判断。所以,指标的选取是扩缩容的首要因素,需要按照平台特性,来确定需要收集哪些指标,并且通过某种手段将它们收集上来为你所用。
决策
拿到指标之后,最后都会通过一个专门负责扩缩容的模块(如 AutoScaler)来根据这些指标进行决策。决策的过程,就是以这些指标作为输入,最终输出是否扩缩容、扩容多少或者缩容多少。决策过程完全可以由自己定,比如按照比例、固定数量,按照操作后的指标平均值等等。也可以根据系统调度情况,设置不同的调度方式,比如针对流量突然上涨采取及时扩容,针对流量的平滑升降采取延时扩容等等。像 Knative 就有两种模式:Stable / 稳定模式和 Panic / 恐慌模式。
数量
决策结果出来之后就简单了。你可以根据结果来让资源数量处于一个预期值,这一步完全不需要你操心,不论是 Node 或者 Pod 维度,K8s 的 HPA 都会来做好。