Appearance
自动扩缩容
Kubernetes 的扩缩容分三层:HPA 调 Pod 副本数,VPA 调 Pod 的 requests/limits,Cluster Autoscaler 或 Karpenter 调节点数。三层解决的是三个不同的问题,但故障现场经常同时出现。服务慢了到底是哪一层的问题,要拆开看,不能混成一个"已配置自动扩缩容"的结论。
三类扩缩容的关系
| 类型 | 调整对象 | 触发依据 | 典型场景 |
|---|---|---|---|
| HPA | Deployment/StatefulSet 副本数 | CPU/内存/自定义指标/外部指标 | 无状态服务、消费服务的副本调整 |
| VPA | Pod 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-serverkubectl 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 = 4CPU 使用率的分母是 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 webdescribe 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 支持的指标类型:
| 类型 | 数据来源 | 示例场景 |
|---|---|---|
| Resource | metrics-server | CPU/内存 |
| Pods | 每个 Pod 的 /metrics | 每 Pod QPS、并发连接数 |
| Object | K8s 对象上的指标 | Ingress QPS |
| External | K8s 外部系统 | 队列长度、云监控指标 |
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 显示 unknown | metrics-server、Pod 未 Ready、APIService |
| 指标超了但没扩 | maxReplicas 到顶、scaleTargetRef 写错、hpa controller 异常 |
| 扩了但 Pod Pending | 节点资源不够、调度约束、PVC、污点 |
| 扩容速度慢 | HPA sync 周期、behavior 策略、镜像拉取慢 |
| 副本频繁波动 | 指标震荡、缩容窗口太短、阈值过于敏感 |
| 扩了仍慢 | 瓶颈在下游或应用内部,不在 Pod 数量上 |