概述
自动扩缩容是 Kubernetes 最吸引人的能力之一——流量来了自动扩容,流量走了自动缩容,既保证服务质量又控制成本。但"自动"不等于"无脑",配置不当的扩缩容可能导致:扩容不及时造成服务降级、缩容太激进中断长连接、抖动扩缩导致资源浪费。
K8s 的自动扩缩容体系包含三个层次:
| 层次 | 组件 | 扩缩维度 | 触发条件 |
|---|---|---|---|
| Pod 水平扩缩 | HPA | Pod 副本数 | CPU/内存/自定义指标 |
| Pod 垂直扩缩 | VPA | Pod 资源配额 | CPU/内存历史用量 |
| 节点扩缩 | Cluster Autoscaler | 节点数 | Pending Pod |
| 事件驱动 | KEDA | Pod 副本数 | 事件源(Kafka/Redis/…) |
本文基于 Kubernetes v1.30。参考 Kubernetes 自动扩缩文档
HPA:水平 Pod 自动扩缩容
工作原理
HPA(Horizontal Pod Autoscaler)是一个控制循环,默认每 15 秒执行一次:
1. 从指标 API 获取 Pod 的当前指标值(CPU/内存/自定义)
2. 计算期望副本数 = ceil(当前副本数 * (当前指标值 / 目标指标值))
3. 与当前副本数比较,决定扩容或缩容
4. 调用 Deployment/ReplicaSet 的 Scale API 修改副本数
核心公式:
期望副本数 = ceil(当前副本数 × (当前指标值 ÷ 目标指标值))
例如:当前 4 个副本,CPU 使用率 80%,目标 50%,则期望副本数 = ceil(4 × 80/50) = ceil(6.4) = 7。
基于 CPU/内存的 HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3 # 最小副本数
maxReplicas: 50 # 最大副本数
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization # 利用率百分比
averageUtilization: 60 # 目标CPU使用率60%
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70 # 目标内存使用率70%
前提条件:Pod 必须配置
resources.requests.cpu和resources.requests.memory,否则 HPA 无法计算利用率。这是最常见的 HPA 不工作原因。
基于自定义指标的 HPA
CPU/内存是通用指标,但很多时候业务需要基于更贴近应用的指标扩缩容:QPS、消息队列深度、活跃连接数。
部署 Metrics Pipeline
自定义指标需要完整的指标流水线:
Pod 指标 → cAdvisor → kubelet → Metrics Server (resource metrics)
↓
应用指标 → Prometheus → Prometheus Adapter → K8s API (custom metrics)
↓
HPA 查询
安装 Prometheus Adapter:
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus-adapter prometheus-community/prometheus-adapter \
--namespace monitoring \
--create-namespace \
--set prometheus.url=http://prometheus-server.monitoring.svc.cluster.local \
--set prometheus.port=80
配置自定义指标规则:
# Prometheus Adapter 配置
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-adapter
namespace: monitoring
data:
config.yaml: |
rules:
- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
resources:
overrides:
namespace: {resource: "namespace"}
pod: {resource: "pod"}
name:
matches: "^(.*)_total"
as: "${1}_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
HPA 使用自定义指标:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa-custom
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 50
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000" # 每个Pod目标1000 QPS
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
多指标组合
HPA 可以同时配置多个指标,取每个指标计算出的最大副本数:
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 70
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
# HPA 会分别计算每个指标的期望副本数,取最大值
扩缩容行为控制
K8s v1.23+ 支持通过 behavior 字段精细控制扩缩容行为:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp
minReplicas: 3
maxReplicas: 50
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
# 扩容策略
scaleUp:
stabilizationWindowSeconds: 0 # 扩容不需要稳定窗口,立即执行
policies:
- type: Percent
value: 100 # 每次最多扩容100%(翻倍)
periodSeconds: 30 # 30秒内最多扩容一次
- type: Pods
value: 4 # 或每次最多扩4个Pod
periodSeconds: 30
selectPolicy: Max # 取两个策略中的最大值
# 缩容策略
scaleDown:
stabilizationWindowSeconds: 300 # 缩容需要5分钟稳定窗口
policies:
- type: Percent
value: 10 # 每次最多缩容10%
periodSeconds: 60 # 60秒内最多缩容一次
行为控制策略说明
| 策略 | 说明 | 推荐场景 |
|---|---|---|
stabilizationWindowSeconds | 稳定窗口,指标持续满足条件才执行 | 缩容用 300s,扩容用 0s |
type: Percent | 按百分比扩缩 | 通用 |
type: Pods | 按绝对数量扩缩 | 精确控制 |
selectPolicy: Max | 多策略取最大值 | 扩容优先 |
selectPolicy: Min | 多策略取最小值 | 缩容保守 |
selectPolicy: Disabled | 禁止该方向 | 只扩不缩 |
HPA 生产实践
常见问题与解决:
| 问题 | 原因 | 解决 |
|---|---|---|
| HPA 不工作 | Pod 没有 resources.requests | 添加 requests 配置 |
| 指标获取失败 | Metrics Server 未部署 | 部署 metrics-server |
| 自定义指标不生效 | Prometheus Adapter 配置错误 | 检查 rules 配置 |
| 扩容太慢 | stabilizationWindow 太长 | 扩容窗口设为 0 |
| 缩容抖动 | 缩容窗口太短 | 设为 300s 以上 |
| 冷启动异常 | 新 Pod 指标还没采集到 | 暂不可解,用预热 |
冷启动问题:当 HPA 扩容出新 Pod 时,新 Pod 需要时间启动并上报指标。在这期间,HPA 无法获取新 Pod 的指标,可能继续扩容导致过度扩容。解决方案:
- 配置
startupProbe让 Pod 完全就绪后才参与负载 - 设置合理的
scaleUp策略限制扩容速度 - 使用 KEDA 的预热机制
VPA:垂直 Pod 自动扩缩容
工作原理
VPA(Vertical Pod Autoscaler)分析 Pod 的历史资源使用情况,自动调整 Pod 的 resources.requests 和 resources.limits。与 HPA 不同,VPA 改变的是单个 Pod 的资源配额而非副本数。
三种模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
Off | 只推荐,不应用 | 评估阶段 |
Initial | 创建 Pod 时应用推荐值,运行中不修改 | 生产推荐 |
Auto | 自动应用推荐值,需要重启 Pod | 慎用 |
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: myapp-vpa
namespace: production
spec:
targetRef:
apiVersion: "apps/v1"
kind: Deployment
name: myapp
updatePolicy:
updateMode: "Initial" # 只在Pod创建时应用推荐值
resourcePolicy:
containerPolicies:
- containerName: '*'
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 4
memory: 8Gi
controlledResources: ["cpu", "memory"]
VPA 的限制
VPA 最大的问题:
Auto模式下修改资源配额需要重启 Pod,因为 K8s 不支持运行时修改 Pod 的 resource requests。这意味着 VPA 的 Auto 模式会导致业务中断。
| 限制 | 说明 |
|---|---|
| 需要重启 Pod | 修改资源需要重建 Pod |
| 不能与 HPA 同时用于同一维度 | HPA 基于 CPU 扩副本,VPA 改 CPU 配额,会冲突 |
| 最小资源限制 | 需要设置 minAllowed/maxAllowed 防止异常值 |
| 准入 webhook | 需要 VPA Admission Controller,增加复杂度 |
VPA 推荐值查看
# 查看 VPA 推荐值
kubectl describe vpa myapp-vpa -n production
# 输出示例
# Recommendation:
# Target:
# CPU: 250m
# Memory: 500Mi
# Lower Bound:
# CPU: 100m
# Memory: 200Mi
# Upper Bound:
# CPU: 500m
# Memory: 1Gi
# Uncapped Target:
# CPU: 250m
# Memory: 500Mi
VPA 与 HPA 协作
VPA 和 HPA 可以协作,但不能同时管理同一资源维度:
| 方案 | HPA | VPA | 说明 |
|---|---|---|---|
| 方案一 | CPU 扩容 | 内存调整 | HPA 管 CPU,VPA 管内存 |
| 方案二 | 自定义指标扩容 | CPU+内存调整 | HPA 管业务指标,VPA 管资源 |
| 方案三 | CPU 扩容 | Off 模式 | VPA 只做推荐,人工调整 |
VPA 生产建议
- 生产用 Initial 模式:只在新 Pod 创建时应用推荐值,不影响运行中 Pod
- 先用 Off 模式评估:运行一段时间观察推荐值是否合理
- 设置 minAllowed/maxAllowed:防止异常推荐值导致问题
- 不要和 HPA 同时管 CPU:这是最常见的配置冲突
Cluster Autoscaler
工作原理
Cluster Autoscaler(CA)关注的是Pending Pod。当 HPA 扩容导致集群资源不足、Pod 处于 Pending 状态时,CA 会自动新增节点:
HPA 扩容 → Pod Pending(资源不足)→ CA 新增节点 → Pod 调度成功
流量下降 → HPA 缩容 → 节点利用率低 → CA 删除节点
CA 的决策逻辑:
- 定期扫描 Pending Pod
- 如果 Pending 是因为资源不足,模拟调度计算需要多少节点
- 调用云厂商 API 创建新节点
- 新节点加入集群后,Pending Pod 被调度
缩容逻辑
CA 缩容更谨慎:
- 找出利用率低的节点(所有 Pod CPU/内存请求总和 < 阈值)
- 模拟把该节点上的 Pod 迁移到其他节点
- 如果可以迁移,则排空节点并删除
# Cluster Autoscaler 配置示例(AWS EKS)
apiVersion: apps/v1
kind: Deployment
metadata:
name: cluster-autoscaler
namespace: kube-system
spec:
template:
spec:
containers:
- image: registry.k8s.io/autoscaling/cluster-autoscaler:v1.30.0
name: cluster-autoscaler
command:
- ./cluster-autoscaler
- --scale-down-unneeded-time=10m # 节点低利用率持续10分钟才缩容
- --scale-down-delay-after-add=10m # 扩容后10分钟内不缩容
- --scale-down-unempty-time=30m # 排空节点后等30分钟再删除
- --max-node-provision-time=15m # 节点创建最大等待时间
- --balance-similar-node-groups=true # 平衡相似节点组
- --expander=least-waste # 扩容策略:最小浪费
- --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/my-cluster
env:
- name: AWS_REGION
value: us-east-1
扩容策略(Expander)
| 策略 | 说明 | 适用场景 |
|---|---|---|
random | 随机选择(默认) | 简单场景 |
most-pods | 选择能调度最多 Pod 的节点组 | 优先满足 Pod 调度 |
least-waste | 选择资源浪费最少的节点组 | 成本优化 |
price | 选择最便宜的节点组 | 云环境成本优化 |
priority | 按优先级选择 | 混合节点组 |
各云厂商 CA 实现
| 云厂商 | 实现方式 | 特点 |
|---|---|---|
| AWS EKS | Auto Scaling Group | 成熟稳定 |
| GCP GKE | 原生集成 | 开箱即用 |
| Azure AKS | VM Scale Set | 成熟稳定 |
| 自建 | Cluster API | 需要自己管理基础设施 |
CA 的限制
| 限制 | 说明 |
|---|---|
| 不是实时扩容 | 节点创建需要 1-5 分钟 |
| 不跨可用区调度 | 节点组绑定可用区 |
| Pod 驱逐有 PDB 限制 | PDB 可能阻止缩容 |
| Spot 实例回收 | 需要 Node Termination Handler |
| 不做节点选型 | 只在节点组范围内扩容 |
KEDA:事件驱动扩缩容
为什么需要 KEDA
HPA 基于 CPU/内存/自定义指标扩缩容,但很多场景的扩缩容信号不是资源指标而是事件:
- Kafka 队列堆积 → 扩容消费者
- Redis 队列长度增加 → 扩容 Worker
- Cron 定时 → 定时扩容
- PostgreSQL 连接数高 → 扩容
KEDA(Kubernetes Event-Driven Autoscaling)是 CNCF 项目,专门解决事件驱动扩缩容。
KEDA 架构
事件源(Kafka/Redis/...) → KEDA Scaler → KEDA Operator → HPA → Deployment
↓
ScaledObject (CRD)
ScaledJob (CRD)
KEDA 的工作方式:
- 部署
ScaledObjectCRD,定义事件源和扩缩容规则 - KEDA Operator 监听 ScaledObject,创建对应的 HPA
- KEDA External Scaler 从事件源获取指标
- HPA 根据指标扩缩容
安装 KEDA
helm repo add kedacore https://kedacore.github.io/charts
helm install keda kedacore/keda \
--namespace keda-system \
--create-namespace
Kafka 消费者扩缩容示例
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: kafka-consumer-scaledobject
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: kafka-consumer
minReplicaCount: 0 # 没消息时缩到0
maxReplicaCount: 50 # 最多50个消费者
pollingInterval: 30 # 30秒检查一次
cooldownPeriod: 300 # 缩容到0前等待300秒
triggers:
- type: kafka
metadata:
bootstrapServers: kafka-broker:9092
consumerGroup: my-consumer-group
topic: orders
lagThreshold: "100" # 每个分区滞后超过100条就扩容
offsetResetPolicy: latest
partitionLimitation: "0,1,2,3" # 只监控指定分区
支持的事件源
KEDA 支持 60+ 种事件源:
| 类别 | 事件源 |
|---|---|
| 消息队列 | Kafka, RabbitMQ, AWS SQS, Azure Service Bus, NATS |
| 数据库 | PostgreSQL, MySQL, MongoDB, Redis |
| 监控 | Prometheus, Datadog |
| 云服务 | AWS CloudWatch, Azure Monitor, GCP Pub/Sub |
| 定时 | Cron |
| 自定义 | External Scaler |
ScaledJob:批处理扩缩容
对于批处理任务,KEDA 提供 ScaledJob,直接创建 Job 而非扩缩 Deployment:
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: image-processor
namespace: production
spec:
jobTargetRef:
template:
spec:
containers:
- name: processor
image: myapp/processor:v1
command: ["./process"]
restartPolicy: Never
maxReplicaCount: 10
pollingInterval: 30
triggers:
- type: aws-sqs
metadata:
queueURL: https://sqs.us-east-1.amazonaws.com/123456789012/image-queue
queueLength: "5" # 每5条消息创建一个Job
awsRegion: us-east-1
KEDA 的优势
| 优势 | 说明 |
|---|---|
| Scale to Zero | 没有事件时缩容到 0,极致省钱 |
| 事件源丰富 | 60+ 种事件源 |
| 与 HPA 兼容 | 底层生成 HPA,可共存 |
| 简单易用 | ScaledObject 一个 CRD 搞定 |
扩缩容策略组合
生产推荐组合
| 场景 | HPA | VPA | CA | KEDA |
|---|---|---|---|---|
| Web API | CPU + QPS | Initial | 是 | 否 |
| 消息消费者 | 否 | Initial | 是 | 是 |
| 批处理 | 否 | Off | 是 | ScaledJob |
| 数据库 | 否 | Off | 否 | 否 |
| WebSocket | 连接数 | Initial | 是 | 否 |
完整扩缩容配置示例
# Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: myapp/api:v1
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
---
# HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api-server-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api-server
minReplicas: 3
maxReplicas: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "500"
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
---
# PDB(配合 HPA 缩容时的优雅驱逐)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
namespace: production
spec:
minAvailable: 2 # 至少保持2个Pod可用
selector:
matchLabels:
app: api-server
PodDisruptionBudget 配合
HPA 缩容和 CA 缩容节点都会驱逐 Pod,需要 PDB 保护:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-server-pdb
spec:
minAvailable: 50% # 至少保持50%可用
# 或 maxUnavailable: 1 # 最多不可用1个
selector:
matchLabels:
app: api-server
| PDB 参数 | 说明 | 适用场景 |
|---|---|---|
minAvailable: N | 至少 N 个可用 | 固定副本数 |
minAvailable: 50% | 至少 50% 可用 | 弹性副本数 |
maxUnavailable: 1 | 最多 1 个不可用 | 保守策略 |
maxUnavailable: 25% | 最多 25% 不可用 | 激进策略 |
生产实践
扩缩容监控
# Prometheus 告警规则
groups:
- name: hpa-alerts
rules:
# HPA 达到最大副本数
- alert: HPAAtMaxReplicas
expr: kube_hpa_status_condition{condition="ScalingLimited",status="true"} == 1
for: 10m
annotations:
summary: "HPA {{ $labels.hpa }} 达到扩容上限"
# HPA 无法获取指标
- alert: HPAMetricsUnavailable
expr: kube_hpa_status_condition{condition="ScalingActive",status="false"} == 1
for: 5m
annotations:
summary: "HPA {{ $labels.hpa }} 无法获取指标"
# CA 无法扩容
- alert: ClusterAutoscalerUnschedulable
expr: cluster_autoscaler_unschedulable_pods_count > 0
for: 10m
annotations:
summary: "{{ $value }} 个 Pod 无法调度"
扩缩容测试
# 压测触发 HPA 扩容
kubectl run load-generator --image=busybox:latest --restart=Never \
-- /bin/sh -c "while true; do wget -q -O- http://api-server.production.svc.cluster.local:8080/; done"
# 观察 HPA 状态
kubectl get hpa -n production -w
# 观察 Pod 扩容过程
kubectl get pods -n production -l app=api-server -w
扩缩容排障
# 查看 HPA 详细状态
kubectl describe hpa myapp-hpa -n production
# 查看 HPA 指标
kubectl get --raw "/apis/autoscaling/v2/namespaces/production/horizontalpodautoscalers/myapp-hpa" | jq .
# 查看自定义指标
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/http_requests_per_second" | jq .
# 查看 CA 日志
kubectl logs -n kube-system -l app=cluster-autoscaler --tail=50
# 查看节点扩容事件
kubectl get events --field-selector reason=TriggeredScaleUp
常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
HPA 显示 unknown | Pod 没有 requests 或 metrics-server 异常 | 检查 requests 配置和 metrics-server |
| HPA 不扩容 | 指标值低于目标值 | 检查实际指标 |
| HPA 扩容到 max 但仍不足 | maxReplicas 太小或节点不足 | 调大 max 或检查 CA |
| 缩容太慢 | stabilizationWindow 太长 | 调小窗口 |
| 缩容抖动 | 流量波动大 | 加大窗口或用最小副本数兜底 |
| CA 不扩容 | 节点组已到上限 | 调大 ASG 最大值 |
| VPA 不生效 | updateMode 是 Off | 改为 Initial 或 Auto |
总结
K8s 自动扩缩容是一个多层次的体系,核心要点:
- HPA 是主力:基于 CPU/内存的 HPA 是最基础也最实用的扩缩容手段。务必配置
behavior控制扩缩容速度,避免抖动。 - 自定义指标更精准:CPU 不能反映真实负载,QPS、队列深度等业务指标更贴近实际。部署 Prometheus Adapter 实现自定义指标 HPA。
- VPA 慎用 Auto 模式:VPA 的 Auto 模式需要重启 Pod,生产环境用 Initial 模式或 Off 模式做推荐。
- CA 补足节点层:HPA 只管 Pod 副本数,节点不够时需要 CA 自动新增节点。注意 CA 的缩容有延迟,不要期望实时。
- KEDA 补足事件驱动:消息队列、批处理场景用 KEDA,支持 Scale-to-Zero,极致省钱。
- PDB 必须配:没有 PDB 的缩容可能导致服务不可用,尤其是 CA 驱逐节点时。
- 监控和告警不能少:HPA 达到上限、CA 无法扩容都需要告警,否则扩缩容失败你根本不知道。
自动扩缩容不是配置完就万事大吉,需要持续观察和调优。建议每周 review 一次扩缩容历史,根据实际流量模式调整参数。
参考资料与致谢
本文在撰写过程中参考了以下资料,感谢原作者的贡献:
- Kubernetes 自动扩缩文档 — Kubernetes 官方,参考了Kubernetes 自动扩缩文档相关内容