4、弹性伸缩策略:让资源像呼吸一样自然——HPA与VPA的黄金组合
说实话,做PaaS平台最头疼的问题是什么?
不是功能不够多,也不是性能不够强。而是——资源到底该给多少?
给多了,成本飙升,老板找你谈话。给少了,业务抖动,用户投诉。我见过太多团队在这上面栽跟头。今天我们就聊聊怎么让资源分配这件事,变得像呼吸一样自然。
4.1 为什么需要两种伸缩?
先问一个问题:你的Pod为什么会扛不住流量?
无非两种情况:
- 实例不够了——需要更多副本(水平伸缩)
- 单个实例太弱了——需要更大规格(垂直伸缩)
HPA(Horizontal Pod Autoscaler)管第一种,VPA(Vertical Pod Autoscaler)管第二种。两者缺一不可。
核心观点:HPA解决“量”的问题,VPA解决“质”的问题。一个管横向,一个管纵向。组合使用,才能做到真正的弹性。
我在项目中遇到过一种情况:某个Java服务,CPU使用率一直很低,但内存持续上涨。如果只用HPA,它永远不会触发扩容——因为CPU没超标。但实际服务已经快OOM了。这时候VPA就派上用场了,它能动态调整内存的Request和Limit。
4.2 HPA:水平伸缩的正确姿势
HPA的原理很简单:监控指标 → 计算期望副本数 → 调整Deployment的replicas。
但很多人用错了。我见过最典型的错误——只盯着CPU和内存。
你想想看,如果你的应用是消息处理型的,CPU可能一直很低,但队列堆积严重。这时候CPU-based HPA根本不会动。怎么办?用自定义指标。
4.2.1 基于CPU/Memory的HPA
这是最基础的用法,适合大部分无状态服务。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
我的习惯:CPU目标值设在60%-80%之间,内存设在70%-85%之间。太低容易频繁伸缩,太高又来不及反应。具体调优要看业务特性。
4.2.2 基于自定义指标的HPA
这才是真正的进阶玩法。比如基于QPS、请求延迟、队列深度等。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: queue-worker-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: queue-worker
minReplicas: 1
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: queue_depth
target:
type: AverageValue
averageValue: 10
这里的关键是:你得先把指标暴露出来。我一般用Prometheus Adapter或者Kubernetes Metrics Server的自定义指标API。
避坑指南:我曾经把HPA的冷却时间设得太短,结果流量一波动,Pod疯狂创建和删除,搞得集群鸡飞狗跳。建议:--horizontal-pod-autoscaler-downscale-stabilization 设成5分钟以上,给系统一点缓冲。
4.3 VPA:让每个Pod都“合身”
VPA的作用是自动调整Pod的CPU和内存Request。说白了,就是帮你回答一个问题:这个Pod到底需要多少资源?
VPA有三种模式:
- Auto:自动调整,并且会重建Pod
- Initial:只在Pod创建时设置初始值
- Off:只给出建议,不自动执行
我个人最推荐的做法是:先用Off模式跑一段时间,收集数据,看看VPA给出的建议是否合理。确认没问题了,再切到Auto模式。
4.3.1 VPA配置示例
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: my-app-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: my-app
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 4
memory: 8Gi
controlledResources: ["cpu", "memory"]
注意:一定要设置minAllowed和maxAllowed。否则VPA可能会给你一个离谱的值——比如给一个sidecar容器分配2核CPU。我见过这种事故,真的。
4.4 HPA + VPA 黄金组合
单独用HPA或VPA都有局限。组合起来才是王道。
怎么组合?我的经验是:
- VPA负责“调优”——让每个Pod的资源申请更精准
- HPA负责“扩缩”——根据整体负载调整副本数
但这里有个坑:HPA和VPA不能同时修改同一个资源指标。比如VPA在调整CPU Request,HPA也在看CPU利用率,两者会打架。
解决办法是什么?
- 让VPA只调整内存,HPA只看CPU
- 或者用VPA的Initial模式,只在Pod启动时设置一次
- 再或者,用HPA基于自定义指标(如QPS),VPA基于资源指标
我一般用第三种方案。举个例子:
# VPA只负责内存
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: web-vpa
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: web
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: "*"
controlledResources: ["memory"]
---
# HPA只负责CPU和QPS
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: requests_per_second
target:
type: AverageValue
averageValue: 1000
一个小技巧:先用VPA跑一周的Off模式,收集建议。然后手动调整Deployment的Request值。等稳定了,再开Auto模式。这样能避免VPA刚上线时频繁重建Pod。
4.5 实战中的避坑指南
这些年踩过的坑,我总结了几条:
| 场景 | 问题 | 解决方案 |
|---|---|---|
| HPA频繁伸缩 | Pod频繁创建/删除,影响稳定性 | 增大冷却时间,设置合理的min/max |
| VPA重建Pod | 业务中断,连接池失效 | 配合PDB使用,设置优雅终止 |
| 指标采集延迟 | 伸缩滞后,扛不住突发流量 | 缩短采集周期,或使用预测式伸缩 |
| 资源碎片化 | 集群节点资源利用率不均 | 结合Cluster Autoscaler使用 |
我曾经遇到过一个经典案例:某个电商大促期间,HPA配置了基于CPU的伸缩。结果流量上来时,CPU还没涨,但QPS已经翻了三倍。等CPU反应过来,服务已经超时了。
后来怎么解决的?加了基于QPS的自定义指标,并且把HPA的指标采集周期从60秒改成了15秒。效果立竿见影。
4.6 总结与建议
弹性伸缩这件事,说白了就是三个字:快、准、稳。
- 快——指标采集要快,决策要快,执行要快
- 准——指标选择要准,阈值设置要准
- 稳——不要频繁抖动,不要影响业务
HPA和VPA的组合,就像汽车的油门和悬挂。HPA决定踩多深,VPA决定车身调多稳。两者配合好了,你的PaaS平台才能真正做到“资源像呼吸一样自然”。
最后说一句:没有银弹。每个业务的流量模型都不一样。我的建议是——先跑起来,再调优。别想着一步到位。弹性伸缩是持续优化的过程,不是一次配置就完事的。