12 Kubernetes HPA:云原生应用扩缩容管理之道
你好,我是潘野。
上一讲的末尾我讲了公有云上引起浪费的两个大类情况,其实云资源浪费是一个普遍存在的问题,无论是在公有云还是私有云环境中都可能发生,如何解决云资源浪费也是基础架构管理中非常重要的一个部分。
今天我们展开讲讲,哪些原因会造成云资源浪费,以及我们怎么利用好云原生技术来解决云资源的浪费问题,提升我们的资源使用率。
哪些情况会造成资源浪费?
云资源利用率低是指已分配的云资源没有被充分利用,导致闲置浪费。我们看看哪些情况会造成这个结果。
首先是过度或错误配置。 在创建云实例时,为了满足峰值需求,应用维护者往往会配置过多的资源。然而,实际应用中,资源需求通常会低于峰值,导致大部分时间资源处于闲置状态。
比如我就遇到过这种情况:开发申请了十台300G内存的机器做Redis Cluster,而上线之后,发现实际使用率只有20%,这就相当于8台机器被浪费了。
错误配置的现象也很常见,配置云资源时,由于缺乏经验或相关知识,可能会出现错误配置。例如应用程序比较消耗CPU,不太消耗内存,但是却申请了一个内存很大的机器,导致资源浪费。
然后是资源预留问题。 为了保证资源的可用性,应用维护者会预留一定量的资源保障应用做横向或者纵向扩展。然而,预留资源往往无法完全利用,导致资源闲置浪费。
最后是不必要的服务, 有时候我们可能会启用不必要的云服务,导致资源浪费。
解决办法
在云原生体系中,解决上面这些问题并不难做,我们解决的思路是让应用动态地调整资源使用。在流量高峰时期,又能及时动态扩容上去。当流量进入低峰时候,又能释放掉闲置资源。用一个词来总结就是 动态扩缩容。
动态扩缩容里主要包括两个层级的动态扩缩容。
第一个层级是应用本身 水平扩展与纵向扩展。
水平扩展是指通过增加节点数量来扩展应用的容量。例如,可以将一个应用部署在多个服务器上,每个服务器都运行应用的一部分。水平扩展可以有效地提高应用的处理能力,并可以应对更大的负载。
纵向扩展是指通过增加单个节点的资源来扩展应用的性能。例如,可以增加服务器的 CPU、内存或存储空间。纵向扩展可以提高应用的处理速度,并可以处理更复杂的任务。
在应用本身这个层级的动态扩缩容,我们可以使用Kubernetes HPA和VPA来实现应用层面的动态扩缩容。
第二个层级是Kubernetes集群本身容量的动态扩缩容,指根据集群的负载情况自动增加或减少节点数量,以满足应用的需求。这样做可以有效地提高资源利用率,并降低成本。
在这个层级的动态扩缩容中,可以使用Kubernetes 官方社区支持的集群自动扩缩容工具Cluster Autoscaler,它可以根据 Pod 的资源需求和节点的资源可用情况自动扩缩容集群。
今天我们主要讲解第一个层级,应用本身的 水平扩展与纵向扩展。
HPA
我们先来看HPA,也就是水平扩缩容。HPA 可以根据 CPU 利用率、内存利用率和其他自定义指标自动扩缩容 Pod 数量,以满足应用程序的负载需求。
HPA工作原理是这样的,控制器会定期(默认每 30 秒)查询目标资源的 Pod 的资源使用情况,并将其与 HPA 对象中指定的指标进行比较。如果资源使用情况超过或低于目标指标,HPA 控制器会根据扩缩容策略来扩缩容 Pod 数量。
Kubernetes默认支持根据容器的CPU和内存的使用率。我们结合例子来理解一下。
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
targetAverageUtilization: 80
从上述配置可以看到,我们针对web-app这个deployment设置了最小副本数1,最大副本数10,在CPU平均使用率80%的时候开始扩容。
基于CPU和内存指标这种配置非常简单,但是这在实际生产中并不够用。 我们举个常见例子,比如在一个生产者消费者服务里,当消费者的消费速度不足,我们希望启动更多的消费者服务的时候,CPU和内存的使用率这种指标显然就不能满足需求了。
怎么扩展HPA的能力
现在的HPA支持了从其他的API中获取指标来进行扩容, Kubernetes API 中有一个资源叫 apiservices
, 用于注册和管理非核心 API,非核心 API 是指 Kubernetes 核心代码之外的 API。这些 API 可以由第三方开发人员提供,也可以由 Kubernetes 社区提供。
HPA控制器会从 apiservices
管理的API中获取一些指标,然后根据 定义好指标的阈值 来触发一些扩缩容。 我们可以用 kubectl get apiservices
这个命令,查看集群现在支持的第三方API。
>>> kubectl get apiservices.apiregistration.k8s.io
v1beta1.custom.metrics.k8s.io monitoring/prometheus-adapter True 30h
v1beta1.external.metrics.k8s.io addons-system/keda-operator-metrics-apiserver True 5h37m
v1beta1.metrics.k8s.io kube-system/metrics-server True 26d
- 对于资源指标,使用
metrics.k8s.io
API, 一般由 metrics-server 提供。 它可以作为集群插件启动。 - 对于自定义指标,使用
custom.metrics.k8s.io
API。 它由其他“适配器(Adapter)” API 服务器提供。从上面的命令输出可以看出,这里是由prometheus-adapter
来提供的。 - 对于外部指标,将使用
external.metrics.k8s.io
API。
接下来,我们来看看怎么使用Prometheus-Adapter来扩展HPA的能力。
Prometheus-Adapter
Prometheus-Adapter 是一款用于将 Prometheus 指标转换为 Kubernetes 自定义指标的工具。它可以用于将 Prometheus 监控的应用程序的指标暴露给 Kubernetes Horizontal Pod Autoscaler (HPA) 等工具。
Prometheus-Adapter 的工作过程要经历三个阶段。
- Prometheus-Adapter 会定期(默认每 30 秒)从 Prometheus 服务器拉取指标数据。
- Prometheus-Adapter 会将拉取到的指标数据转换为 Kubernetes API Server 可以理解的形式。
- Prometheus-Adapter 会将转换后的指标数据暴露给 Kubernetes API Server。
假设我们有一个 Web 应用程序,我们希望通过Prometheus监控这个应用的性能指标。在这个前提下,我们就可以使用HPA配合Prometheus-Adapter来自动扩缩容 Pod 以满足应用程序的负载需求。
接下来,我们再通过一个例子,了解一下Prometheus-Adapter是怎么实现HPA扩展的。
首先,我们需要安装Prometheus和Prometheus-Adapter,这里你参照 官方文档 安装即可。
然后,我们需要在Prometheus-Adapter的configmap中加上我们需要监控的指标,这里是从 Prometheus 查询 HTTP 请求总数,排除某些指标,用"_qps "后缀重命名,并计算 30 秒窗口内的查询率。
custom:
- seriesQuery: '{__name__=~"^http_requests.*_total$",container!="POD",namespace!="",pod!=""}'
resources:
overrides:
namespace: { resource: "namespace" }
pod: { resource: "pod" }
name:
matches: "(.*)_total"
as: "${1}_qps"
metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[30s])) by (<<.GroupBy>>)
一切都成功的话,我们运行下面这条命令即可获得对应的输出。
最后就是HPA的配置了,你可以参考后面的例子。
kind: HorizontalPodAutoscaler
apiVersion: autoscaling/v2
metadata:
name: sample-httpserver
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: sample-httpserver
minReplicas: 1
maxReplicas: 10
behavior:
scaleDown:
stabilizationWindowSeconds: 30
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
metrics:
- type: Pods
pods:
metric:
name: http_requests_qps
target:
type: AverageValue
averageValue: 50000m
这里的配置有三个需要注意的地方。
- 最小最大的副本数分别设置 1、10。
- 为了测试效果的时效性,设置扩缩容的行为 behavior。
- 指定指标 http_requests_qps、类型 Pods 以及目标值 50000m(它表示平均每个 pod 的 RPS 50 )。比如以 300 的 RPS 访问,副本数就是 300/50=6 。
测试工具我们选用 vegeta,因为它可以指定 RPS。
# 240
echo "GET http://192.168.1.92:31617" | vegeta attack -duration 60s -connections 10 -rate 240 | vegeta report
# 120
echo "GET http://192.168.1.92:31617" | vegeta attack -duration 60s -connections 10 -rate 120 | vegeta report
# 40
echo "GET http://192.168.1.92:31617" | vegeta attack -duration 60s -connections 10 -rate 40 | vegeta report
我们describe下deployment,就可以看到这样的日志,horizontal-pod-autoscaler New size: 2; reason: pods metric http_requests_qps above target。
如果日志如上图所示,就说明配置成功了。
HPA 的不足
回顾刚才的过程,我们发现,基于Prometheus-adapter的HPA整体功能还是比较完全的,但也有几个不足之处。
第一,反应慢半拍。HPA 是根据历史数据来扩缩容的,所以可能来不及应对突然增加的负载,导致服务中断。
第二,服务抖动问题。 HPA 频繁扩缩容 Pod 数量,可能会导致服务响应时间变长,影响用户体验。我在生产环境中遇到过用户明确不要启用自动扩容的情况,因为他们的应用不能很好的兼容水平扩展。
第三,配置复杂。 HPA的基础配置比较简单,但是要扩展它的能力就需要一定的Kubernetes知识,小白用户可能搞不定。比如Prometheus-Adapter依赖Prometheus,这要求我们先要维护一套Prometheus,同时配置上需要指定要转换的指标、转换规则等。如果配置不正确,可能会导致 HPA 无法正常工作。
第四,应用场景有限。 HPA 只适用于可以根据资源指标进行扩缩容的应用。不能根据事件来进行扩缩容。
VPA
前面我们学习的HPA用于水平扩展,那么垂直扩展VPA又适合用在什么样的场景,它又具备什么样的优势呢?
我们以 Prometheus 为例来讨论一下。刚开始集群应用的 Pod 数量比较少,一个 4 CPU、4GB 内存的 Prometheus Pod 就够用了。不过,随着集群规模的扩大,Pod 数量越来越多,Prometheus 需要处理大量涌入的指标,那么性能就会成为瓶颈。
但是Prometheus不支持水平扩展, 这时,VPA 就能派上用场了。它可以根据单个 Pod 的负载情况,自动扩充其 CPU 和内存资源,有效解决性能问题,同样类似的场景在DevOps常用的Jenkins上也存在。
所以Pod的纵向自动扩缩VPA具有以下优势。
- 不需要再预估Pod初始启动的时候要分多少的CPU和内存,一切交给VPA去处理。
- VPA会根据节点剩余的资源量来确定分配多少CPU和内存给Pod,最大化地利用节点的可分配资源。
- VPA可以自动调整Pod的CPU 和内存请求,无需手工干预。。
但是我们在实际的生产中用上VPA的机会并不大,为什么呢? 因为垂直扩缩容面对的问题比水平扩缩容要复杂很多。
首先,VPA不像HPA,HPA不会更改正在运行的Pod,而VPA会更新正在运行的Pod资源配置,这会导致Pod的重建和重启。而且Pod有可能被调度到其他的节点上,这有可能会中断应用程序,这点可以说是VPA落地的最大的阻碍。
其次,预测资源需求难度比较高。有的时候,应用负载在不断地变化,Pod需要多少资源可能不是线性增长的。这种情况下VPA 要想合理地扩缩容,就需要准确预测 Pod 的资源需求,这件事并不容易。
最后,有些应用程序并不支持VPA方式。例如基于 JVM 的工作负载是无法使用VPA的,因为对此类工作负载的实际内存用量并没有暴露出来。
所以社区对VPA的推进比较慢,2017 年社区开始讨论VPA的方案,但是一直到kubernetes 1.25这个版本,VPA才演进到1.0的版本。
总结
云资源浪费是一个普遍存在的问题,无论是在公有云还是私有云环境中都可能发生。今天,我们分析了造成云资源浪费的原因,主要是资源利用率低和资源浪费两种情况。
我们解决云资源浪费的关键手段是引入动态扩缩容。动态扩缩容是指根据应用的负载情况自动调整资源配置,以满足应用的需求。在Kubernetes 中常用的两种动态扩缩容工具是水平扩缩容 HPA 和垂直扩缩容 VPA。
这一讲里,我也结合例子,为你讲解了如何使用Prometheus-Adapter来扩展Kubernetes HPA的能力,支持更多的扩缩容指标。建议你参考课程里的讲解,课后自己动手练习实践一下。
但是HPA 和 VPA 并不是完美的解决方案,都存在一些局限。例如原生的HPA只支持基于CPU和内存的指标进行水平扩容,VPA可能会造成应用的中断等。我们需要根据实际需求选择合适的工具,并进行合理配置。
思考题
今天的课程里,我提到了HPA的一些不足,比如在真正流量突发的时候,有可能会遇到反应慢半拍的情况,无法及时跟上扩展,导致应用服务中断。你有哪些思路来解决这个问题呢?
欢迎在评论区与我讨论。如果这一讲对你有启发,也欢迎分享给身边更多朋友。