HPA 副本伸缩、行为控制与防抖
发布后的第一个流量峰值里,接口延迟和 CPU 同时上升,HPA 从 4 个副本给出 12 个期望副本,Deployment 也迅速创建 Pod;业务容量却没有按比例恢复。新 Pod 在镜像拉取、配置加载和 JVM 预热期间持续 Unready,旧 Pod 继续承担请求,节点余量耗尽后又有一批 Pod Pending。团队看到 SuccessfulRescale 就宣布自动扩容成功,实际上只证明 HPA 写入了 /scale,没有证明副本被调度、Ready、进入 Service endpoints,更没有证明延迟恢复。
另一次事故发生在低峰。一个定时任务让 CPU 短暂下降,HPA 连续缩容;缓存命中率随 Pod 删除下降,流量回到存活 Pod 后 CPU 又升高,于是系统开始扩容。几轮反复之后,Pod 启停成本、连接迁移和节点供给延迟叠加成更大的抖动。问题不是 HPA “不智能”,而是目标值、requests、采样周期、启动就绪、稳定窗口和扩缩速率没有组成同一个控制模型。副本控制器只能执行它看到的数学关系,不能替团队补齐容量事实。
HPA 控制的是副本声明
HorizontalPodAutoscaler 是 autoscaling API 组中的控制对象。控制面中的 HPA controller 周期性读取指标,计算建议副本数,再通过目标工作负载的 /scale 子资源更新期望副本。Deployment、StatefulSet 以及实现 scale 子资源的自定义工作负载可以成为目标;DaemonSet 这类没有可调副本语义的对象不能使用 HPA。
稳定 API 使用 autoscaling/v2。它支持 resource、containerResource、pods、object 和 external 等指标来源,也支持多指标和逐方向 behavior。旧的 autoscaling/v2beta2 从 Kubernetes 1.26 起不再提供服务,升级前必须转换清单和客户端;已有对象仍可经 autoscaling/v2 访问,但旧客户端请求会直接失败。字段的权威结构可从 HorizontalPodAutoscaler v2 API核对,迁移差异见 废弃 API 迁移指南。
HPA 的成功边界要拆成五层:指标 API 返回样本;controller 算出 desiredReplicas;目标 /scale 被写入;调度器和节点供给让 Pod 获得资源;Pod Ready 并进入真实流量。kubectl describe hpa 中的 SuccessfulRescale 只到第三层。生产验收必须关联 Deployment condition、Pending Event、Pod readiness、endpoints 数量和业务 SLI。
用一个公式理解伸缩方向
最基本的比例算法是:
期望副本数 = ceil(当前副本数 × 当前指标值 / 目标指标值)4 个副本的平均 CPU 利用率为 80%,目标为 40%,基础建议就是 ceil(4 × 80 / 40) = 8。利用率不是 CPU limit 的百分比,而是观测用量相对 CPU request 的比例。容器没有相关 request 时,控制器无法为 Pod 计算 CPU 利用率;request 写得过小会放大利用率并过度扩容,写得过大则可能迟迟不扩。
当比值接近 1 时,controller 在容差带内不动作,避免每次轻微波动都改副本。未单独配置时,集群级默认容差是 10%。逐方向 behavior.scaleUp.tolerance 与 behavior.scaleDown.tolerance 在 Kubernetes 1.35 进入 beta 且默认启用,但仍受 HPAConfigurableTolerance feature gate 控制;较旧集群或显式关闭该 gate 的平台不能使用这些字段,应用前要以目标 API Server 的 server-side dry-run 为准。Kubernetes 官方的 HPA 算法说明给出了缺失样本、未就绪 Pod、多指标和稳定窗口的完整行为。
如果配置多个指标,controller 分别计算建议值,取最大的副本数。某个指标读取失败时,只要其余指标建议扩容,仍可以向上扩;如果可用指标建议缩容而另一个指标失败,则跳过这次缩容。这个设计偏向保守保容量,因此“一个非关键指标坏了”也可能让副本长期不降,形成成本漂移。
在隔离集群启用资源指标链
实验需要可销毁 Kubernetes 集群、kubectl、创建 namespace/Deployment/Service/HPA 的权限,以及可用的 Metrics API。HPA controller 属于控制面,托管集群通常已经启用;本地或自管集群还要部署 Metrics Server。资源指标链的职责与限制可从 Kubernetes 官方的 Resource Metrics Pipeline确认。
先盘点再创建负载:
kubectl version
kubectl api-resources | grep horizontalpodautoscalers
kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes | head
kubectl get apiservice v1beta1.metrics.k8s.io -o wide
kubectl top node
kubectl auth can-i create namespaces
kubectl auth can-i create horizontalpodautoscalers.autoscaling -n hpa-lab
kubectl auth can-i create deployments.apps -n hpa-lab
kubectl auth can-i create services -n hpa-lab预期 Metrics API 返回 NodeMetricsList,APIService 为 Available=True,kubectl top 能看到近期 CPU/内存。若 APIService 不存在或不可用,先修复 Metrics Server、聚合层、kubelet TLS、控制面网络与 RBAC;创建 HPA 不会自动安装指标提供者。
自管控制面还应记录 kube-controller-manager 的 --horizontal-pod-autoscaler-sync-period、--horizontal-pod-autoscaler-cpu-initialization-period、--horizontal-pod-autoscaler-initial-readiness-delay 和 downscale stabilization 配置。托管集群不能修改时,把这些值视为平台契约,通过发行方文档和实验观察确认,不在应用 YAML 中伪造同名字段。
配置一个可解释的工作负载
下面的 Deployment 使用 Kubernetes 官方 HPA 演练镜像。正式团队模板应把可变 tag 替换成经过扫描和兼容验证的 digest;示例镜像只用于隔离实验,不承载业务数据。
apiVersion: v1
kind: Namespace
metadata:
name: hpa-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: hpa-lab
spec:
replicas: 1
selector:
matchLabels:
app: hpa-web
template:
metadata:
labels:
app: hpa-web
spec:
containers:
- name: web
image: registry.k8s.io/hpa-example:latest
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 3
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: hpa-lab
spec:
selector:
app: hpa-web
ports:
- port: 80
targetPort: 80kubectl apply -f hpa-workload.yaml
kubectl -n hpa-lab rollout status deployment/web --timeout=180s
kubectl -n hpa-lab get pod -l app=hpa-web
kubectl -n hpa-lab top pod -l app=hpa-webrequests.cpu: 100m 同时参与调度与 HPA 利用率分母,limits.cpu: 500m 只约束容器可用 CPU 上限,不为节点预留 500m。readinessProbe 决定 Pod 何时进入 Service endpoints,也参与 HPA 对启动样本的保守处理。真实应用有高 CPU 预热时,应增加能覆盖启动阶段的 startupProbe,避免尚未具备服务能力的样本误导控制器。
behavior 如何限制动作速度
创建一个 autoscaling/v2 HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: hpa-lab
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
behavior:
scaleUp:
stabilizationWindowSeconds: 0
selectPolicy: Max
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
selectPolicy: Max
policies:
- type: Percent
value: 25
periodSeconds: 60minReplicas 是外部指标失败、冷启动和最小可用性的重要兜底,不等于高可用副本数;后者还要结合故障域和 PDB。maxReplicas 是需求侧保险丝,不保证集群有足够节点、配额、IP、镜像带宽或下游容量容纳这些副本。
scaleUp.policies 提供“每分钟翻倍”和“每分钟增加 4 个”两种上限,selectPolicy: Max 选择允许变化更大的策略。scaleDown 每分钟最多减少当前副本的 25%,并使用 300 秒稳定窗口。稳定窗口保存过去建议,在缩容时取窗口内更保守的高值,减少瞬时低谷触发删除。它不会延迟指标采集,也不会让被删除的 Pod 自动排空长连接;终止优雅期、preStop、负载均衡摘除和 PDB 仍由工作负载负责。
kubectl apply -f hpa.yaml
kubectl -n hpa-lab get hpa web -o yaml
kubectl -n hpa-lab describe hpa web初始阶段可能显示 <unknown>/50%,因为 Metrics Server 尚未获得新 Pod 样本。等待数个采样周期后,AbleToScale、ScalingActive 通常应为 True;ScalingLimited 是否为 True 要结合 min/max 或 policy 判断。
正向实验:让扩容证据闭环
启动一个临时负载 Pod,持续请求 Service:
kubectl -n hpa-lab run load-generator \
--image=busybox:1.37 \
--restart=Never \
-- /bin/sh -c 'while sleep 0.01; do wget -q -O- http://web; done'
kubectl -n hpa-lab get hpa web -w另开终端连续观察五层证据:
kubectl -n hpa-lab top pod -l app=hpa-web
kubectl -n hpa-lab get deployment web -o jsonpath='{.spec.replicas}{" desired / "}{.status.readyReplicas}{" ready\n"}'
kubectl -n hpa-lab get pod -l app=hpa-web -o wide
kubectl -n hpa-lab get endpointslice -l kubernetes.io/service-name=web
kubectl -n hpa-lab get events --sort-by=.lastTimestamp预期在指标样本形成后,HPA 的 current CPU 高于 50%,DESIRED 上升,Event 出现 SuccessfulRescale;随后 Deployment 创建 Pod,Pod 从 Pending/ContainerCreating 进入 Ready,EndpointSlice 中可服务 endpoint 数增加。具体副本数和耗时由机器性能、采样周期与镜像缓存决定,不能把示例数字写成生产阈值。
停止施压后观察缩容:
kubectl -n hpa-lab delete pod load-generator
kubectl -n hpa-lab get hpa web -wCPU 会先下降,但 300 秒稳定窗口与 25%/分钟策略会让副本逐步回落。若副本立即跌到最小值,检查实际生效的 HPA YAML、控制面默认值与是否有人修改 spec.replicas。若长期不降,检查多指标读取错误、窗口内高建议、minReplicas、policy 和 Deployment rollout。
反向实验:移除 CPU request
CPU utilization 必须有 request 作为分母。先保存可恢复清单,再移除 request,保留 limit:
kubectl -n hpa-lab get deployment web -o yaml > web.before-negative.yaml
kubectl -n hpa-lab patch deployment web --type='json' -p='[
{"op":"remove","path":"/spec/template/spec/containers/0/resources/requests/cpu"}
]'
kubectl -n hpa-lab rollout status deployment/web --timeout=180s
kubectl -n hpa-lab describe hpa web
kubectl -n hpa-lab get events --sort-by=.lastTimestamp新 Pod 没有 CPU request 后,Metrics API 仍可能返回 CPU 用量,kubectl top pod 也可能正常,但 HPA 无法计算 utilization。预期 condition 或 Event 出现类似 FailedGetResourceMetric、FailedComputeMetricsReplicas,TARGETS 可能显示 <unknown>/50%。这个反例证明“有 CPU 样本”与“能计算利用率”不同。
恢复 request 并重新观察:
kubectl -n hpa-lab patch deployment web --type='strategic' -p='{
"spec":{"template":{"spec":{"containers":[{"name":"web","resources":{"requests":{"cpu":"100m","memory":"64Mi"},"limits":{"cpu":"500m","memory":"128Mi"}}}]}}}
}'
kubectl -n hpa-lab rollout status deployment/web --timeout=180s
kubectl -n hpa-lab get hpa web -w如果团队使用 LimitRange 注入默认 request,要查看实际 Pod spec,不能只看 Deployment 源清单。Sidecar 缺 request 时,Pod 级 Resource 指标也会受影响;只想基于主容器伸缩时可评估 ContainerResource,但容器名必须在 rollout 前后保持稳定。
缺失样本与启动窗口为何保守
controller 在计算前会排除正在删除或失败的 Pod,并识别缺失指标与尚未稳定 Ready 的 Pod。缩容方向上,缺失样本按其可能使用了 100% 目标值保守估算;扩容方向上按 0% 保守估算,尚未就绪 Pod 也会抑制扩容幅度。如果重新计算后的比值反转方向或落入容差带,就跳过动作。
这意味着 HPA status 展示的平均值不一定等于最终决策中采用的保守比值。排障不能只拿 TARGETS 做手算,还要看 Pod Ready 转换、样本时间戳和控制器 Event。CPU 初始化周期与初始就绪延迟是控制面全局参数;应用侧更可靠的做法是让 startupProbe 覆盖预热,让 readinessProbe 在真正接流前保持失败。
滚动发布时,HPA 绑定 Deployment,而 Deployment 再协调新旧 ReplicaSet 的副本。maxSurge 会让瞬时 Pod 数超过 HPA desired,maxUnavailable 和慢启动会改变有效容量。发布峰值必须把 rollout surge、HPA maxReplicas 和节点余量一起建模,否则自动伸缩与滚动更新会同时索取容量。
故障从 condition 向下追
AbleToScale=False 常见于读取或更新 /scale 失败、目标不存在;ScalingActive=False 常见于指标不可用或无法计算;ScalingLimited=True 表示建议值被 min/max 或 behavior 限制,并不总是错误。先用 condition 分类,再看 Event reason 和对应 API。
kubectl -n hpa-lab describe hpa web
kubectl -n hpa-lab get hpa web -o jsonpath='{range .status.conditions[*]}{.type}{"="}{.status}{" "}{.reason}{" "}{.message}{"\n"}{end}'
kubectl -n hpa-lab get --raw /apis/metrics.k8s.io/v1beta1/pods | head
kubectl -n hpa-lab get deployment web -o yaml
kubectl -n hpa-lab get events --sort-by=.lastTimestamp指标未知时检查 Metrics API 和 Pod request;副本算出但不变时检查 min/max、容差、稳定窗口与 policy;Deployment desired 已变但 Ready 不增时转向调度 Event、配额、节点供给、镜像、探针与应用启动;Ready 增加但 SLI 不恢复时检查 Service selector、EndpointSlice、连接均衡、下游瓶颈和单 Pod 实际吞吐。
不要把手工 kubectl scale 当成稳定修复。HPA 下一轮可能覆盖人工值,GitOps 也可能再次写入 spec.replicas。事故期间需要固定副本时,应明确暂停或删除 HPA、记录原配置和恢复条件,让副本字段只有一个 owner。
RBAC、字段所有权与安全边界
创建 HPA 的交付身份只需要自己 namespace 中相应的 HPA 管理权限;它不需要为了创建对象而获得目标 /scale 的 update 权限。真正持续读取指标并更新 /scale 的是控制面 HPA controller 身份。操作者若还承担人工回退,才按职责额外授予特定工作负载的 get/update scale 权限,不能顺手放大到整个集群。应用团队同样不需要集群级 Metrics Server、APIService 或 controller-manager 配置权限;自定义指标适配器的服务身份再单独约束 custom.metrics.k8s.io 或 external.metrics.k8s.io 及外部凭证。
spec.replicas 的所有权必须写进交付契约。HPA 活跃后,GitOps 清单通常不再持续强制一个固定 replicas 值;发布工具、定时伸缩器、KEDA 和人工脚本也不能同时写同一 scale。可以保留灾难回退值,但启用它时要先停掉自动 owner。
指标标签、HPA Event 和 controller 日志可能暴露 namespace、工作负载名与容量上限。示例和工单要脱敏,不记录真实租户、队列、域名或凭证。HPA 自身不保存云凭证;External Metrics Adapter 或 KEDA 所需凭证应使用工作负载身份或专用 Secret,轮换、审计与撤销由对应平台 owner 负责。
容量成本要算完整时间线
HPA 响应时间至少包含指标采样、Metrics API 可见、controller 同步、Deployment 创建、调度、节点供给、镜像拉取、应用启动、readiness 通过和负载均衡收敛。任何一段超过业务延迟预算,仅提高 maxReplicas 都不会解决问题。突发业务需要预热副本、预测性扩容或队列削峰;冷启动很长的工作负载应把 minReplicas 作为容量保险,而不是把零闲置当成唯一成本目标。
CPU target 要来自压测得到的单 Pod 安全吞吐区间。比如 CPU 达到 60% 后延迟开始陡增,就要在拐点之前留余量;若瓶颈是连接池、锁、内存或下游配额,CPU 可能不是好信号。内存通常不能像 CPU 那样随流量快速回落,用内存 utilization 缩容容易长期高位或误判,应优先选择与需求更接近的请求率、并发或队列年龄。
成本侧同时看平均副本、峰值副本持续时间、Pending 副本、节点碎片、rollout surge 和错误指标导致的保守不缩容。副本成本下降不一定意味着总成本下降:缩得太狠会增加冷启动、缓存重建、下游连接和节点反复创建。容量报表要把业务 SLO 与资源成本放在同一时间线上。
选型时先问负载能否横向复制
无状态、请求可均匀分配、启动较快、单 Pod 吞吐可测的服务最适合 HPA。带分片所有权、长连接、单写者、固定成员或昂贵缓存预热的系统,需要先设计重平衡和退出协议;副本增加不等于有效容量线性增加。DaemonSet、不可分割任务和强状态单体应选择其他伸缩模型。
CPU/内存 Resource Metric 链简单,适合需求与资源消耗相关的服务;Custom Pod/Object Metric 更接近业务容量,但增加采集、映射和权限复杂度;External Metric 适合外部队列和云服务,却把远端延迟、配额与凭证纳入控制环。多指标能提供保护,但每多一条链就多一个阻止缩容的失败点。
HPA 与 VPA 组合时不要同时让两者控制同一 CPU/内存事实。VPA 改 request 会改变 HPA utilization 分母,可能在没有流量变化时触发副本变化。常见做法是 HPA 使用业务指标,VPA 管 CPU/内存 request,或让 VPA 只提供建议并经过人工变更;字段和指标 owner 必须明确。
升级、回滚与责任治理
升级 Kubernetes 前扫描 HPA API 版本、废弃字段、feature gate 和控制面默认参数。把 HPA YAML 经目标版本 server-side dry-run,在测试集群回放高流量、低流量、指标缺失、Pod 慢启动和 rollout 场景。behavior 默认值或 controller 参数变化会影响动作时间,即使资源对象仍能创建也要重新验证。
团队模板应固定 autoscaling/v2、明确 request、min/max、metric 单位、behavior、owner 和回退副本。平台 owner 维护 Metrics API 与控制面参数;应用 owner 维护单 Pod 容量、探针、终止协议和目标值;观测 owner 维护指标新鲜度;值班团队维护从 condition 到业务 SLI 的证据链。每次目标值调整都要关联容量实验与变更记录。
退出自动伸缩时,先冻结 GitOps、定时任务和人工脚本等其他 scale 写入者,记录经过容量评审的回退副本;随后在同一变更中删除 HPA 并立即由新的唯一 owner 写入该副本数。不能先对仍活跃的 HPA 执行一次 kubectl scale 再慢慢删除 HPA,因为下一轮调谐可能覆盖人工值。GitOps 环境应把“移除 HPA”和“恢复 Deployment replicas”作为同一批受审变更。实验环境可按以下顺序清理:
kubectl -n hpa-lab delete pod load-generator --ignore-not-found
kubectl -n hpa-lab delete hpa web
kubectl -n hpa-lab scale deployment web --replicas=1
kubectl delete -f hpa-workload.yaml
kubectl delete namespace hpa-lab --ignore-not-found共享环境中还要确认没有其他自动化继续写 scale,归档调整前后的 HPA condition、Deployment revision 和业务指标,撤销临时 RBAC 与调试日志级别。安全退出的判据是副本字段重新拥有单一 owner、业务容量稳定、告警和运行手册完成迁移,而不只是 HPA 对象被删除。
