概述

自动扩缩容是 Kubernetes 最吸引人的能力之一——流量来了自动扩容,流量走了自动缩容,既保证服务质量又控制成本。但"自动"不等于"无脑",配置不当的扩缩容可能导致:扩容不及时造成服务降级、缩容太激进中断长连接、抖动扩缩导致资源浪费。

K8s 的自动扩缩容体系包含三个层次:

层次组件扩缩维度触发条件
Pod 水平扩缩HPAPod 副本数CPU/内存/自定义指标
Pod 垂直扩缩VPAPod 资源配额CPU/内存历史用量
节点扩缩Cluster Autoscaler节点数Pending Pod
事件驱动KEDAPod 副本数事件源(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.cpuresources.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 的指标,可能继续扩容导致过度扩容。解决方案:

  1. 配置 startupProbe 让 Pod 完全就绪后才参与负载
  2. 设置合理的 scaleUp 策略限制扩容速度
  3. 使用 KEDA 的预热机制

VPA:垂直 Pod 自动扩缩容

工作原理

VPA(Vertical Pod Autoscaler)分析 Pod 的历史资源使用情况,自动调整 Pod 的 resources.requestsresources.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 可以协作,但不能同时管理同一资源维度

方案HPAVPA说明
方案一CPU 扩容内存调整HPA 管 CPU,VPA 管内存
方案二自定义指标扩容CPU+内存调整HPA 管业务指标,VPA 管资源
方案三CPU 扩容Off 模式VPA 只做推荐,人工调整

VPA 生产建议

  1. 生产用 Initial 模式:只在新 Pod 创建时应用推荐值,不影响运行中 Pod
  2. 先用 Off 模式评估:运行一段时间观察推荐值是否合理
  3. 设置 minAllowed/maxAllowed:防止异常推荐值导致问题
  4. 不要和 HPA 同时管 CPU:这是最常见的配置冲突

Cluster Autoscaler

工作原理

Cluster Autoscaler(CA)关注的是Pending Pod。当 HPA 扩容导致集群资源不足、Pod 处于 Pending 状态时,CA 会自动新增节点:

HPA 扩容 → Pod Pending(资源不足)→ CA 新增节点 → Pod 调度成功
流量下降 → HPA 缩容 → 节点利用率低 → CA 删除节点

CA 的决策逻辑:

  1. 定期扫描 Pending Pod
  2. 如果 Pending 是因为资源不足,模拟调度计算需要多少节点
  3. 调用云厂商 API 创建新节点
  4. 新节点加入集群后,Pending Pod 被调度

缩容逻辑

CA 缩容更谨慎:

  1. 找出利用率低的节点(所有 Pod CPU/内存请求总和 < 阈值)
  2. 模拟把该节点上的 Pod 迁移到其他节点
  3. 如果可以迁移,则排空节点并删除
# 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 EKSAuto Scaling Group成熟稳定
GCP GKE原生集成开箱即用
Azure AKSVM 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 的工作方式:

  1. 部署 ScaledObject CRD,定义事件源和扩缩容规则
  2. KEDA Operator 监听 ScaledObject,创建对应的 HPA
  3. KEDA External Scaler 从事件源获取指标
  4. 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 搞定

扩缩容策略组合

生产推荐组合

场景HPAVPACAKEDA
Web APICPU + QPSInitial
消息消费者Initial
批处理OffScaledJob
数据库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 显示 unknownPod 没有 requests 或 metrics-server 异常检查 requests 配置和 metrics-server
HPA 不扩容指标值低于目标值检查实际指标
HPA 扩容到 max 但仍不足maxReplicas 太小或节点不足调大 max 或检查 CA
缩容太慢stabilizationWindow 太长调小窗口
缩容抖动流量波动大加大窗口或用最小副本数兜底
CA 不扩容节点组已到上限调大 ASG 最大值
VPA 不生效updateMode 是 Off改为 Initial 或 Auto

总结

K8s 自动扩缩容是一个多层次的体系,核心要点:

  1. HPA 是主力:基于 CPU/内存的 HPA 是最基础也最实用的扩缩容手段。务必配置 behavior 控制扩缩容速度,避免抖动。
  2. 自定义指标更精准:CPU 不能反映真实负载,QPS、队列深度等业务指标更贴近实际。部署 Prometheus Adapter 实现自定义指标 HPA。
  3. VPA 慎用 Auto 模式:VPA 的 Auto 模式需要重启 Pod,生产环境用 Initial 模式或 Off 模式做推荐。
  4. CA 补足节点层:HPA 只管 Pod 副本数,节点不够时需要 CA 自动新增节点。注意 CA 的缩容有延迟,不要期望实时。
  5. KEDA 补足事件驱动:消息队列、批处理场景用 KEDA,支持 Scale-to-Zero,极致省钱。
  6. PDB 必须配:没有 PDB 的缩容可能导致服务不可用,尤其是 CA 驱逐节点时。
  7. 监控和告警不能少:HPA 达到上限、CA 无法扩容都需要告警,否则扩缩容失败你根本不知道。

自动扩缩容不是配置完就万事大吉,需要持续观察和调优。建议每周 review 一次扩缩容历史,根据实际流量模式调整参数。

参考资料与致谢

本文在撰写过程中参考了以下资料,感谢原作者的贡献:

  1. Kubernetes 自动扩缩文档 — Kubernetes 官方,参考了Kubernetes 自动扩缩文档相关内容