一、 HPA解决的问题
HPA全称是 Horizontal Pod Autoscaler,也就是对k8s的workload的副本数进行自动水平扩缩容(scale)机制,也是k8s里使用需求最广泛的一种Autoscaler机制,在开始详细介绍HPA之前,先简单梳理下k8s autoscale的整个大背景。
k8s被誉为新一代数据中心操作系统(DCOS),说到操作系统我们自然想到其定义:管理计算机的软硬件资源的系统,k8s也一样其核心工作也是管理整个集群的计算资源,并按需合理分配给系统里的程序(以Pod为基础的各种workload)。
其本质是解决资源与业务负载之间供需平衡的问题,随着业务需求和部署规模的增大,k8s集群就要相应扩容计算资源,集群扩容的最直接的办法是新增资源,一般单机器很难垂直扩展(k8s node也不支持),所以一般都是直接增加节点。但是随着机器的不断增加成本也不断加大,而实际上大量服务大部分时间负载很低导致机器的整体使用率很低,一方面业务为了应对每日随机流量高峰会把副本数尽量扩得很高,另一方面业务方并不能准确评估服务实际需要的CPU等资源,也出现大量浪费。
为了解决业务服务负载时刻存在的巨大波动和资源实际使用与预估之间差距,就有了针对业务本身的“扩缩容”解决方案:Horizontal Pod Autoscaler(HPA)和 Vertical Pod Autoscaler(VPA)。
为了充分利用集群现有资源优化成本,当一个资源占用已经很大的业务需要扩容时,其实可以先尝试优化业务负载自身的资源需求配置(request与实际的差距),只有当集群的资源池确实已经无法满足负载的实际的资源需求时,再调整资源池的总量保证资源的可用性,这样可以将资源用到极致。
所以总的来说弹性伸缩应该包括:
Cluster-Autoscale: 集群容量(node数量)自动伸缩,跟自动化部署相关的,依赖iaas的弹性伸缩,主要用于虚拟机容器集群
Vertical Pod Autoscaler: 工作负载Pod垂直(资源配置)自动伸缩,如自动计算或调整deployment的Pod模板limit/request,依赖业务历史负载指标
Horizontal-Pod-Autoscaler: 工作负载Pod水平自动伸缩,如自动scale deployment的replicas,依赖业务实时负载指标
其中VPA和HPA都是从业务负载角度从发的优化,VPA是解决资源配额(Pod的CPU、内存的limit/request)评估不准的问题,HPA则要解决的是业务负载压力波动很大,需要人工根据监控报警来不断调整副本数的问题,有了HPA后,被关联上HPA的deployment,后续副本数修改就不用人工管理,HPA controller将会根据业务忙闲情况自动帮你动态调整。当然还有一种固定策略的特殊HPA: cronHPA,也就是直接按照cron的格式设定扩容和缩容时间及对应副本数,这种不需要动态识别业务繁忙度属于静态HPA,适用于业务流量变化有固定时间周期规律的情况,这种比较简单可以算做HPA的一种简单特例。
二、原理架构
三、HPA的metrics的分类
要支持最新的custom(包括external)的metrics,也需要使用新版本的HPA:autoscaling/v2beta1,里面增加四种类型的Metrics:Resource、Pods、Object、External,每种资源对应不同的场景,下面分别说明:
Resource支持k8s里Pod的所有系统资源(包括cpu、memory等),但是一般只会用cpu,memory因为不太敏感而且跟语言相关:大多数语言都有内存池及内置GC机制导致进程内存监控不准确。
Pods类型的metrics表示cpu,memory等系统资源之外且是由Pod自身提供的自定义metrics数据,比如用户可以在web服务的pod里提供一个promesheus metrics的自定义接口,里面暴露了本pod的实时QPS监控指标,这种情况下就应该在HPA里直接使用Pods类型的metrics。
Object类型的metrics表示监控指标不是由Pod本身的服务提供,但是可以通过k8s的其他资源Object提供metrics查询,比如ingress等,一般Object是需要汇聚关联的Deployment下的所有的pods指标总和。
External类型的metrics也属于自定义指标,与Pods和Object不同的是,其监控指标的来源跟k8s本身无关,metrics的数据完全取自外部的系统。
在HPA最新的版本 autoscaling/v2beta2 中又对metrics的配置和HPA扩缩容的策略做了完善,特别是对 metrics 数据的目标指标值的类型定义更通用灵活:包括AverageUtilization(平均利用率)、AverageValue(平均值)和Value,但是不是所有的类型的Metrics都支持三种目标值的,具体对应关系如下表。
HPA里的各种类型的Metrics和Metrics Target Type的对应支持关系表
| Metrics Type I Target Type | AverageUtilization | AverageValue | Value | 备注(query metrics) |
|---|---|---|---|---|
| Resource(pod's cpu/memory etc. | Yes | Yes | No | pods metrics list |
| Pods(pod's other metrics) | No | Yes | No | pods metrics list |
| Object(k8s object) | No | Yes | Yes | object metrics |
| External(not k8s object) | No | Yes | Yes | external metrics list |
四、 HPA的使用说明
先看个最简单的HPA的定义的例子
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: php-apache spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx minReplicas: 1 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50解析
# API版本,表示这是autoscaling API的v2beta2版本 apiVersion: autoscaling/v2beta2 # 资源类型,表示这是一个HorizontalPodAutoscaler资源 kind: HorizontalPodAutoscaler # Metadata信息,定义了HPA的 metadata: # HPA的名称 name: php-apache # Spec配置,定义了HPA的具体配置参数 spec: # 缩放目标引用,指定了要自动扩展的Deployment资源 scaleTargetRef: # API版本,这是apps API的v1版本 apiVersion: apps/v1 # 资源类型,表示这是一个Deployment资源 kind: Deployment # 要扩展的Deployment资源的名称 name: php-apache # 最小副本数,HPA将确保Pod的副本数不会低于这个值 minReplicas: 1 # 最大副本数,HPA将确保Pod的副本数不会超过这个值 maxReplicas: 10 # 监控指标,用于触发Pod副本数的扩展 metrics: # 类型,表示这是资源类型的监控指标 type: Resource # 资源名称,这里是指CPU资源 resource: # 资源名称,这里是CPU name: cpu #目标类型,表示监控的是资源的使用率 target: # 目标类型,表示监控的是平均使用率 type: Utilization # 期望的平均CPU使用率,当实际使用率高于这个值时,HPA会扩展Pod的副本数 averageUtilization: 50五、HPA扩缩容算法具体实现
5.1 算法模型
在HPA控制器里,针对不同类型的metrics和不同metrics下的target 类型,都有独立的计算算法,虽然有很多细节差异,但是总的来说,计算公式可以抽象为:
desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
例如如果配置 target value 是100m,当前从metrics接口读取到的 metrics value 是 200m,说明最新的副本数应该是当前的 200m/100m=2.0倍, 如果当前副本数为 2,则HPA计算后的期望副本数是2*2.0=4;
而如果当前从metrics接口读取到的 metrics value是 50m,说明最新的副本数应该是 当前的 50m/100m=0.5倍,也就是最终scale的副本数将为1。
当然实际上当前的metrics value并不一定就只有一个值,如果是 Resource或者Pods类型的metrics,实际上 GetMetrics 会返回一批关联的Pods对应的metrics数据,一般需要做一个平均后再与target的metrics的做比较。
此外,为了保证结果尽量精确,metrics的计算都是浮点数计算,但是最终的副本数肯定要是整数,为了统一HPA控制器在最后,都会对计算出的浮点数副本数向上取整,也就是上面公式里最外层的ceil函数。
5.2 扩缩容threshold控制
当然上面的公式也只是纯数学模型,实际工程实现还要考虑很多现实细节问题:比如监控数据可能会有一定的误差,如果GetMetrics里读到数据不稳定就会造成算出的期望副本数不稳定,导致deployment一会扩缩1个副本,一会又扩容1副本。所以为了避免这种问题kube-controller-manager里有个HPA的专属参数 horizontal-pod-autoscaler-tolerance 表示HPA可容忍的最小副本数变化比例,默认是0.1,如果期望变化的副本倍数在[0.9, 1.1] 之间就直接停止计算返回。那么如果相反,某个时间点开始metrics数据大幅增长,导致最后计算的副本数变化倍数很大,是否HPA控制器会一步扩容到位呢? 事实上HPA控制器为了避免副本倍增过快还加了个约束:单次倍增的倍数不能超过2倍,而如果原副本数小于2,则可以一次性扩容到4副本,注意这里的速率是代码写死不可用配置的。(这个也是HPA controller默认的扩缩容速率控制,autoscaling/v2beta2的HPA Behavior属性可以覆盖这里的全局默认速率)
5.3 缩容冷却机制(cooldown delay)
虽然HPA同时支持扩容和缩容,但是在生产环境上扩容一般来说重要性更高,特别是流量突增的时候,能否快速扩容决定了系统的稳定性,所以HPA的算法里对扩容的时机是没有额外限制的,只要达到扩容条件就会在reconcile里执行扩容(当前一次至少扩容到原来的1.1倍)。但是为了避免过早缩导致来回波动(thrashing ),而容影响服务稳定性甚,HPA的算法对缩容的要求比较严格,通过设置一个默认5min(可配置horizontal-pod-autoscaler-downscale-stabilization)的滑动窗口,来记录过去5分钟的期望副本数,只有连续5分钟计算出的期望副本数都比当前副本数小,才执行scale缩容操作,缩容的目标副本数取5分钟窗口的最大值。
总的来说k8s HPA算法的默认扩缩容原则是:快速扩容,谨慎缩容。
5.4 Pod的metrics数据过滤检查机制
一般情况下HPA的数据指标都来自k8s的Pod里,但是实际上每次创建deployment、对deployment做扩缩容,Pod的副本数和状态都会不断变化,这就导致HPA controller在reconcile里获取到的metrics的指标肯定会有一定的异常,比如Pod还没有Running、Pod刚刚启动还在预热期、或者Pod中间临时OOM恰逢采集时刻、或者Pod正处在删除中,这些都可能导致metrics指标缺失。如果有任何 pod 的指标缺失,HPA控制器会采取最保守的方式重新计算平均值, 在需要缩小时假设这些 pod 消耗了目标值的 100%, 在需要放大时假设这些 pod 消耗了0%目标值, 这可以在一定程度上抑制伸缩的幅度。
具体来说,HPA算法里把deployment下的所有Pod的metrics的指标数据分为三类:
ready pods list, deployment下处于Running状态的Pod且HPA controller成功通过GetMetrics获取的pod metrics的列表
ignore pods list, deployment下处于pending状态的Pods或者(仅对Resouce类似的cpu metrics有效)虽然pod running了但controller成功通过GetMetrics获取的pod metrics,但是pod的启动时间在配置的initial-readiness-delay和cpu-initialization-period 保护期内。
missing pods list,deployment下处于running状态的pod(非pending、非failed、非deleted状态)但是HPA controller通过GetMetrics无法获取的pod metrics的列表
在计算pod的平均metrics值的时候,统一把 ignore pods的metrics设置为最小值0,如果HPA扩缩容的方向是扩容,把missing pods的metrics也设置为最小值0,如果是缩容方向则把missing pods的metrics也设置为最大值(如果是Resouce类型,最大值是Pod的request值,否则最大值就是target value)。
六、总结
总的来说,从k8s v1.18开始HPA的机制已经算比较灵活了,在扩缩容识别指标上可以使用Pod的系统cpu、内存指标,也可以Pods自身暴露的自定义metrics指标,还可以支持外部的业务指标;在具体自定义实现上也提供了标准的扩展框架,还有社区其他人贡献的promesheus adapter。在扩缩容速度上也通过相对百分比和绝对 Pods数变化,可以独立控制单位时间内最大的扩容和缩容,此外还通过自定义窗口时间机制保证副本变化的稳定性。
需要说明下,HPA特性还依然处于非正式GA版本,社区相关的issue有些没有解决,包括HPA缩容的最小副本不允许为0(Resouce和Pods类型的metrics如果在pod副本为0时,将采集不到metrics,需要依赖额外的流量激活机制,Knative集成了service mesh有对流量的劫持所以可以直接实现0副本),参数控制粒度还不够灵活,而且HPA controller的reconcile循环不支持多线程并发,所以也一定程度上影响了一个k8s集群内HPA的对象数过多的时效性,随着k8s HPA关注和使用人数的增多,相信这些问题也都会逐步优化掉。