Skip to content

自动扩缩容

Kubernetes 的扩缩容分三层:HPA 调 Pod 副本数,VPA 调 Pod 的 requests/limits,Cluster Autoscaler 或 Karpenter 调节点数。三层解决的是三个不同的问题,但故障现场经常同时出现。服务慢了到底是哪一层的问题,要拆开看,不能混成一个"已配置自动扩缩容"的结论。

三类扩缩容的关系

类型调整对象触发依据典型场景
HPADeployment/StatefulSet 副本数CPU/内存/自定义指标/外部指标无状态服务、消费服务的副本调整
VPAPod requests/limits历史资源使用资源画像不清楚、批量调参
节点扩容Node 数量Pending Pod、节点资源池HPA 扩副本后节点资源不够

HPA 和节点扩容是上下段的关系,不是什么神秘的编排能力:

HPA 调完副本数之后,节点资源不够时新 Pod 就是 Pending。这段链路里 HPA 没有失灵,是节点层还没跟上。把 HPA 的 describe 和节点扩容日志放在一起看,才知道问题出在哪一段。

metrics-server——资源指标的数据源

基于 CPU 和内存的 HPA 需要 metrics-server。metrics-server 从各节点 kubelet 的 summary API 拉数据,汇总后通过 metrics.k8s.io API 暴露给 apiserver,HPA controller 再从 apiserver 读这些指标。

验证整条链路:

bash
kubectl top nodes
kubectl top pods -A
kubectl get apiservice v1beta1.metrics.k8s.io
kubectl -n kube-system get deploy metrics-server

kubectl top 没数据时,资源型的 HPA 会显示 unknown 指标,不会扩容也不会缩容。常见原因是 metrics-server Pod 挂了、kubelet 证书校验失败、节点 10250 端口不通或 APIService 状态异常。

metrics-server 提供的是当前快照,不是历史趋势。看趋势需要 Prometheus 和 Grafana;HPA 只需要知道当前值是否越过阈值,所以 metrics-server 就够了。

HPA 的计算方式

HPA 的期望副本数来自一个简单的比例公式:

text
期望副本数 = ceil(当前副本数 × 当前指标值 / 期望指标值)

比如当前 2 个副本,目标 CPU 使用率 60%,当前平均值 120%:

text
2 × 120 / 60 = 4

CPU 使用率的分母是 requests.cpu,不是节点总 CPU。Deployment 没设 requests 时,HPA 拿不到有效分母,CPU 利用率指标就是不准确的。

HPA 配置:

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web
  namespace: demo
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 60   # 目标:当前 CPU 总量 / requests 总量 的平均值不超过 60%
bash
kubectl -n demo get hpa
kubectl -n demo describe hpa web

describe hpa 的关键信息:当前指标值、目标值、当前副本数、期望副本数、最近的事件。HPA 没扩的时候,先确认当前值是否真的超过了阈值,再看 maxReplicas 是否已到上限,或者指标是否为 unknown

扩缩容抖动和 behavior

流量有波动,指标采集有延迟。HPA 不加缓冲策略时,指标一高一低就跟着扩缩,副本数来回抖动。

扩容太快会浪费资源(瞬间拉几十个 Pod),但影响的是成本;缩容太快会把刚预热好的 Pod 下掉,用户连接断开、本地缓存蒸发、下游连接池重新建立,影响的是线上稳定性。

yaml
behavior:
  scaleUp:
    stabilizationWindowSeconds: 60    # 60 秒内取最高值,避免短时抖动触发扩容
    policies:
      - type: Percent
        value: 100                     # 每分钟最多翻一倍
        periodSeconds: 60
  scaleDown:
    stabilizationWindowSeconds: 300   # 5 分钟内取最低值,缩容比扩容更保守
    policies:
      - type: Pods
        value: 2                      # 每分钟最多缩 2 个 Pod
        periodSeconds: 60

缩容稳定窗口设长一些是线上常见做法。扩容慢了是容量不够,缩容快了是稳定性风险——两种后果的严重程度不对称。

自定义指标和 KEDA

CPU 和内存不是所有业务的合适扩容指标。队列消费服务 CPU 可能不高,但消息堆积已经很多;网关服务 CPU 低,P95 延迟已经上去了。

HPA v2 支持的指标类型:

类型数据来源示例场景
Resourcemetrics-serverCPU/内存
Pods每个 Pod 的 /metrics每 Pod QPS、并发连接数
ObjectK8s 对象上的指标Ingress QPS
ExternalK8s 外部系统队列长度、云监控指标

Prometheus Adapter 可以把 Prometheus 里的查询结果暴露给 HPA 的 custom/external metrics API。

KEDA 针对的是事件驱动扩缩容。比如 Kafka 消费者,按 lag 扩容比按 CPU 扩容更直接:

yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: order-consumer
  namespace: demo
spec:
  scaleTargetRef:
    name: order-consumer
  minReplicaCount: 1
  maxReplicaCount: 20
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: kafka.demo.svc:9092
        topic: orders
        consumerGroup: order-consumer
        lagThreshold: "100"   # 每个副本平均 lag 超过 100 就扩

按 lag 扩容有一个硬限制:Kafka 分区数。Topic 只有 6 个分区时,消费者副本扩到 20 个,多出来的 14 个不会有分区分给它,lag 也不会降——白白多了连接数和资源占用。分区数、消费者线程数、扩容上限要一起设计。

VPA

VPA 根据历史资源使用推荐或调整 requests/limits。适合资源画像长期不清楚、想批量校准的服务:

yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web
  namespace: demo
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  updatePolicy:
    updateMode: "Off"    # 只推荐,不自动驱逐 Pod

三种模式:

模式行为适合
Off只推荐,不改 Pod观察阶段,看过推荐值再人工调整
Initial新建 Pod 时注入推荐资源有资源浪费历史、希望逐步改善
Auto自动驱逐 Pod 并更新资源信任 VPA 推荐、服务能接受重建

Auto 模式会驱逐 Pod。单副本服务、启动慢的服务、有状态服务要特别评估驱逐的影响。

HPA 和 VPA 同时用 CPU/内存指标时容易互相影响——HPA 按利用率扩缩,VPA 又改 requests 让利用率基准跟着变。常见做法是让 HPA 管副本,VPA 用 Off 或只调内存,避免两者在同一个维度上互相打架。

扩容后仍然慢

副本扩上去了,服务还是慢——这是最容易被误判成"HPA 没生效"的场景。实际是瓶颈根本不在 Pod CPU 上:

现象瓶颈位置分析方向
新 Pod Pending节点资源或调度describe pod、节点可分配资源
Pod Ready 慢镜像、启动、探针镜像大小、预热脚本、initialDelaySeconds
副本多了但错误率没降下游服务DB 连接数、Redis、外部 API 的容量和限流
CPU 不高但延迟高应用内部IO 等待、锁竞争、线程池、网络
扩容后 DB 连接池打满数据库每个 Pod 都开了连接池,连接数 = 副本数 × 池大小
队列 lag 不降消费或分区单条耗时、分区数、下游依赖慢

出现接口超时,能拿出来的证据是:扩容前后的副本数、当前指标趋势、Pod Ready 的时间线、Pending 事件、入口 5xx 比例、下游连接数和延时。

排查入口

bash
kubectl -n demo describe hpa web
kubectl top pods -n demo
kubectl top nodes
kubectl -n demo get pod -l app=web -o wide
kubectl -n demo get events --sort-by=.lastTimestamp | tail -50
现象方向
HPA 显示 unknownmetrics-server、Pod 未 Ready、APIService
指标超了但没扩maxReplicas 到顶、scaleTargetRef 写错、hpa controller 异常
扩了但 Pod Pending节点资源不够、调度约束、PVC、污点
扩容速度慢HPA sync 周期、behavior 策略、镜像拉取慢
副本频繁波动指标震荡、缩容窗口太短、阈值过于敏感
扩了仍慢瓶颈在下游或应用内部,不在 Pod 数量上