5.4 弹性伸缩与自动扩缩容

一句话

云上的弹性不是 “打开自动扩缩容”,而是用钱买容量追赶负载的速度:先选伸缩单元(实例 / Pod / 函数),再算追赶延迟(30 秒–5 分钟),最后用 “预留打底 + 弹性补峰” 的组合把成本压到最低;策略怎么设计(指标、阈值、冷启动)见 37.6 弹性伸缩,这里讲云平台给的原语、配额与计费约束。

三种伸缩单元:先选 “扩什么”

单元 谁负责扩 粒度 扩容生效量级 代价
实例级(ASG / 实例组) 云平台 一台 VM(1–64 vCPU) 1–3 分钟(含系统启动) 粒度粗,缩容时整台释放
容器级(节点池 + 工作负载控制器) 编排器 + 云平台 一个 Pod(0.1–4 vCPU) 30 秒–2 分钟(含镜像与就绪) 需要节点池有余量,否则先扩节点
请求级(FaaS / Serverless) 平台完全托管 一次调用 毫秒–秒级(冷启动) 单价高、无长连接、状态受限

三者不是替代关系:实例级管容量底座,容器级管服务副本,请求级管脉冲。一个常见错误是 “用容器级去扛 10 倍脉冲”——节点池扩容本身要 2–3 分钟,脉冲早过去了。

追赶延迟:1–5 分钟的账,决定架构而不是参数

一次完整的横向扩容要依次经过:指标采集(15–60 秒)→ 决策(15–30 秒)→ 调度(5–30 秒)→ 拉镜像(10–60 秒)→ 就绪探针通过(10–60 秒)。合计 1–3.5 分钟,加上节点池扩节点还要再加 1–2 分钟。

由此得到一条硬结论:任何在 1 分钟内翻倍的流量,都必须靠 “预留容量 + 过载保护”,不能靠反应式扩容。 秒杀、开票日、热点新闻都是这一类。过载时先限流还是先扩容,判据同 34.5 性能测试与负载模型 的拐点分析。

配额:弹性真正的天花板

弹性撞上配额时不会报 “你该提额”,而是表现为 “扩不出来”:

配额 撞上时的症状 提前动作
单地域 vCPU 配额 新实例 Pending / 创建失败 按峰值 × 1.5 提额,提前 1–2 周申请
子网可用 IP 数 Pod 起不来、节点 NotReady 子网按 /24 起规划,别用 /28
公网 IP / EIP 配额 新节点无出网地址 用 NAT 网关共享出口,减少 EIP 依赖
负载均衡连接数 / 新建速率 连接被拒、5xx 上升 提前压测长连接规模
磁盘 IOPS / 快照数 扩容后更慢 用云盘性能公式核对(容量决定基线 IOPS)

配额是可申请的资源,但申请有审批周期。把它当 “上线前的检查项”,而不是 “出事时的紧急工单”。

用钱买弹性的四种容量

形态 折扣量级 承诺 中断风险 适合的负载
按需 基准价 无 无 不可预测的峰值、短期项目
预留 / 节省计划 省 30%–60% 1–3 年 无 稳定的基线(70% 的常态负载)
Spot / 抢占式 省 60%–90% 无 有,提前 2 分钟通知 无状态、可重试、批处理、CI
Serverless 无折扣,按量计费 无 无 脉冲型、低频、事件驱动

组合方式几乎总是同一条:基线用预留,峰值用按需,可中断部分用 Spot。比例按 “常态负载 ÷ 峰值负载” 定:常态占峰值 60% 时,预留 60%、按需 25%、Spot 15%,成本通常能压到纯按需的 60%–70%。容量冗余系数的算法见 57.3 冗余系数。

缩容比扩容危险:四道检查

扩缩容天然不对称:扩慢了会慢,缩错了会挂。缩容前至少要过四关:

检查 不做会怎样
长连接是否迁完 缩掉的实例上还有 10 万 WebSocket,客户端批量重连打垮剩余实例
缓存是否已预热 新实例冷缓存,DB 瞬间被打穿(缓存击穿)
优雅下线是否生效 请求打到正在退出的进程,出现 502 尖刺
批处理是否在跑 缩掉正在跑批的机器,任务中断且无断点

对应的机制是:缩容前摘流量 → 等待 terminationGracePeriod → 完成在途请求 → 再释放。稳定窗口(stabilization window)建议 5–10 分钟,避免负载抖动导致反复扩缩(震荡的成本比不弹性更高)。有状态服务的伸缩前提见 37.2 无状态化。

什么时候不该弹性

情况 为什么 更好的做法
有状态主库 加副本解决读,不加写;扩缩容伴随选主与数据搬迁 垂直扩 + 读写分离 + 分片
启动要 10 分钟的重型服务 追赶延迟远大于负载变化周期 预留固定容量 + 排队削峰
批处理 / 离线任务 目标是 “尽快跑完”,不是 “扛住并发” 队列 + 工作池,按积压量扩 worker
峰均比 < 2 弹性的复杂度换不来成本节省 固定容量,把精力放在降单价
流量不可预测且瞬时 扩容永远慢一步 预留 + 限流 + 排队 + 降级

判断句式只有一句:“弹性带来的成本节省,是否大于它增加的复杂度与故障面?” 峰均比低的时候,答案通常是否。

决策表

你的情况 选择
无状态 API,日内峰谷 3–5 倍 容器级 HPA(按 QPS/并发)+ 节点池自动扩缩
秒杀 / 定时开抢,1 分钟内涨 10 倍 预留容量 + 队列削峰 + 限流,不指望自动扩容
核心基线负载稳定 预留实例覆盖 60%–70%,其余按需
可重试的离线计算 Spot + 检查点 + 断点续跑
低频事件(上传转码、webhook) FaaS,按调用付费
有状态数据库 不做自动扩缩,做垂直扩 + 只读副本 + 分片

代码:一条能跑的 HPA(含“为什么这些数字”)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: {name: api}
spec:
  scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: api}
  minReplicas: 3          # 至少跨 3 个 AZ 各 1 个
  maxReplicas: 30
  metrics:
    - type: Resource
      resource: {name: cpu, target: {type: Utilization, averageUtilization: 60}}
  behavior:
    scaleUp:   {stabilizationWindowSeconds: 0}     # 扩容要快
    scaleDown: {stabilizationWindowSeconds: 300}   # 缩容要稳,防抖
kubectl get hpa api -o wide          # 看 TARGETS 与当前副本数
kubectl describe hpa api | tail -8   # 看“为什么没扩”:指标缺失/已达上限/冷却中
kubectl top pods -l app=api          # 确认指标本身是否可信

判据:扩容看“快”(窗口 0,宁可多扩)、缩容看“稳”(窗口 5 分钟,防抖动);minReplicas 必须覆盖 AZ 故障。

反模式

反模式 后果
只配 HPA,不看节点池是否还有空间 副本 Pending,扩容静默失败
缩容稳定窗口设为 0 负载抖动导致反复扩缩,成本与服务都抖
用 CPU 作为唯一指标做 Web 服务伸缩 CPU 滞后于真实瓶颈(等 IO 时 CPU 空闲)
弹性上限不设 一次异常流量把配额与预算同时打穿
把有状态服务交给自动扩缩 数据搬迁 + 选主,故障面被放大
从未核对过配额 大促当天才发现 vCPU 配额只有平时的一半
用 Spot 跑核心交易 两分钟中断通知,业务直接受损
弹性被当成 “不用做容量规划” 峰值来临时既没预留、也没预案

三条铁律

🔑 追赶延迟决定架构:1 分钟内翻倍的流量只能靠预留 + 限流,不能靠反应式扩容。

🔑 配额是弹性的天花板,按 “峰值 × 1.5” 提前申请,别在故障时申请。

🔑 扩缩容不对称:扩容可以激进,缩容必须慢且带四道检查。

自检

Q1 团队给无状态 API 配了 HPA(CPU 60%),压测时 QPS 涨 3 倍,Pod 却一直是 Pending。最可能的原因是什么?

▸ 答案:节点池没有可调度容量,且已到配额上限。HPA 只负责改副本数,副本要落成 Pod 必须有节点资源;节点池扩容(Cluster Autoscaler)又要受 vCPU 配额、子网 IP、EIP 配额限制。排查顺序:kubectl describe pod 看 Pending 原因 → 节点池上限 → 云配额。修法是 “预留 buffer 节点 + 提前提额”,而不是调 HPA 阈值。

Q2 一次大促峰值是常态的 10 倍,持续 20 分钟。为什么 “把 HPA 上限调大” 不是答案?

▸ 答案:因为追赶延迟 1–3.5 分钟 + 节点扩容 1–2 分钟,等机器起来峰值已经过去一半;而缩容又慢,最后付了 10 倍容量的钱却只用了 20 分钟。正确组合是:预留打底(覆盖 2–3 倍)+ 队列削峰 + 入口限流 + 关键路径降级,把 “瞬时 10 倍” 变成 “持续 5 分钟的 3 倍”。这也是把峰值削平比堆机器便宜的原因。

Q3 为什么缩容要设 5–10 分钟的稳定窗口,而扩容可以设 30 秒?

▸ 答案:因为两者的错误代价不对称。扩容慢 → 少量请求变慢(可恢复);缩容快 → 长连接被切断、缓存被清空、批处理被中断(不可逆且会放大成故障)。稳定窗口的作用是过滤 “负载的短时抖动”,避免扩缩振荡;窗口越短,成本省得越多但风险越高,5–10 分钟是常见平衡点。

Q4 一个服务启动要 8 分钟(加载模型 + 预热索引),能不能上弹性?

▸ 答案:可以做弹性,但必须换形态:不能靠 “按需拉起”,而要靠 “常驻预热池 + 池内切换”。具体做法是保持 N 个已预热实例,扩容时从池里取(秒级可用),池子空了才触发慢速补充,并在这期间靠限流保护。判断标准是 “扩出来的实例要多久才能接流量”,超过 1 分钟就不该走反应式路径。

Q5 峰均比只有 1.5,团队仍要上自动扩缩容,你会怎么说服?

▸ 答案:算两笔账:① 成本:峰均比 1.5 时,弹性最多省 33% 的容量费用,而它带来的复杂度是多一套控制器、一套指标、一套缩容保护与一堆边界情况;② 风险:缩容引发的故障(长连接断、缓存击穿)在低频系统上更难排查。结论通常是 “固定容量 + 单位成本优化(预留、降规格、存储分层)” 更划算——把精力花在降低单价上,而不是追逐峰值。

跨篇相关:伸缩策略(指标选择、快上慢下、冷启动与预热、预扩 vs 弹性的成本账)见 37.6 弹性伸缩;无状态化是弹性的前提见 37.2 无状态化;过载时先限流还是先扩容见 34.5 性能测试与负载模型;容量冗余系数见 57.3 冗余系数;跨 AZ 扩容的分布约束见 5.3 地域与可用区;预留与 Spot 的账单结构见 5.6 云成本结构。