Repository Reading Site
本轮操作记录:HPA 自动扩缩容实验
这一轮我要把自动扩缩容最核心的几件事做成真实证据: 1. HPA 的指标到底从哪里来 2. CPU 型 HPA 为什么必须依赖 `requests.cpu` 3. 为什么 HPA 刚建出来时常常会先显示 `<unknown>` 4. 为什么高 CPU 压力会触发扩容 5. 为什么低负载后不会立刻缩容 6. Deployment 滚动更新和 HPA 缩放是怎样
本轮操作记录:HPA 自动扩缩容实验
本轮目标
这一轮我要把自动扩缩容最核心的几件事做成真实证据:
- HPA 的指标到底从哪里来
- CPU 型 HPA 为什么必须依赖
requests.cpu - 为什么 HPA 刚建出来时常常会先显示
<unknown> - 为什么高 CPU 压力会触发扩容
- 为什么低负载后不会立刻缩容
- Deployment 滚动更新和 HPA 缩放是怎样叠加的
Step 0:先修正 kubectl 上下文,确认自己连的是哪套集群
实际命令
echo "KUBECONFIG=$KUBECONFIG"
kubectl config current-context
KUBECONFIG=~/.kube/config-k8s-lab kubectl config current-context
KUBECONFIG=~/.kube/config-k8s-lab kubectl get nodes -o wide
为什么第一步必须先做这个
这是平台工程里最容易被忽略、但事故率极高的一步。
因为本机 kubectl 默认会去读:
~/.kube/config
如果你没有显式指定 KUBECONFIG,你以为自己在操作实验集群,实际上可能连的是另一套环境。
这次就真实碰到了这个问题:
- 直接执行
kubectl时,默认上下文是kind-mylab - 而且它指向一个失效的本地端口
127.0.0.1:52382 - 所以一开始报的是
The connection to the server 127.0.0.1:52382 was refused
我看到的结果
KUBECONFIG=
说明当前 shell 里没有显式设置 KUBECONFIG。
kubectl config current-context
kind-mylab
说明默认指向的是本地 kind 环境,不是这次实验集群。
KUBECONFIG=~/.kube/config-k8s-lab kubectl config current-context
kubernetes-admin@kubernetes
这才是本课实验要操作的真实集群。
节点核对结果:
us480851516617a:control-plane,10.10.0.1us590068728056:worker,10.10.0.2cp-3:worker,10.10.0.3hk652699382121:worker,10.10.0.4wk-1:worker,10.10.0.5
原理解释
这一步你要掌握的是 kubeconfig 选择逻辑:
- 如果设置了
KUBECONFIG,优先按它指定的文件 - 否则默认读
~/.kube/config current-context决定当前连接哪一个 cluster/user 组合
所以以后做生产操作时,第一反应应该不是直接 kubectl apply,而是先确认:
- 我连的是哪套集群
- 当前用户是谁
- 这个 kubeconfig 是不是临时代理环境
Step 1:验证 HPA 的指标链路,而不是假设它“应该可用”
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl api-resources --api-group=metrics.k8s.io
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n kube-system get deploy metrics-server -o yaml | sed -n '1,220p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get --raw '/apis/metrics.k8s.io/v1beta1/namespaces/autoscaling-lab/pods' | sed -n '1,120p'
ssh root@107.148.176.193 \
'sudo sed -n "1,260p" /etc/kubernetes/manifests/kube-controller-manager.yaml'
为什么要先查这一层
很多人看见 HPA 不工作,就直接改 YAML。
这是不对的。
HPA 是否能工作,至少依赖三层事实:
- 集群是否暴露
metrics.k8s.io metrics-server是否在正常抓取指标- controller-manager 是否正常运行 HPA controller,且没有奇怪的全局覆盖参数
如果不先确认这一层,你后面的现象解释全部可能是错的。
我看到的结果
kubectl api-resources --api-group=metrics.k8s.io 输出了:
NodeMetricsPodMetrics
说明资源指标 API 已注册。
metrics-server Deployment 里看到关键参数:
--metric-resolution=15s--kubelet-use-node-status-port--kubelet-insecure-tls
这说明:
- 指标采样分辨率大约是 15 秒
metrics-server会向 kubelet 拉指标- 当前环境为简化实验启用了不校验 kubelet TLS
我还直接读取了资源指标 API,拿到类似下面的原始数据:
{
"items": [
{
"metadata": {
"name": "cpu-demo-6845f656c6-g4sch"
},
"timestamp": "2026-04-10T05:48:24Z",
"window": "16.405s",
"containers": [
{
"name": "main",
"usage": {
"cpu": "8777n",
"memory": "348Ki"
}
}
]
},
{
"metadata": {
"name": "no-request-demo-56c5f5fdf-cm27k"
},
"timestamp": "2026-04-10T05:48:21Z",
"window": "15.987s",
"containers": [
{
"name": "main",
"usage": {
"cpu": "999248264n",
"memory": "388Ki"
}
}
]
}
]
}
kube-controller-manager 静态 Pod 清单里没有看到显式的:
--horizontal-pod-autoscaler-downscale-stabilization--horizontal-pod-autoscaler-sync-period
之类的覆盖参数。
原理解释
这一步要建立三条判断:
kubectl top不是凭空出现的,它背后依赖的是metrics.k8s.io- HPA 的资源指标,本质上也是从这条链路读取
- 如果 controller-manager 没有覆盖全局参数,那么很多 HPA 行为要按对象级配置或默认逻辑理解
另外,原始数据里的 999248264n 约等于:
0.999core- 也就是约
999m
所以你以后看到:
kubectl top pod里的1000m- 原始 API 里的
999248264n
要知道它们说的是同一件事,只是单位不同。
Step 2:应用本课实验清单,建立两个对照样本
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f manifests/12-hpa/00-namespace-autoscaling-lab.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f manifests/12-hpa/10-cpu-demo-burn.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f manifests/12-hpa/20-cpu-demo-hpa.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f manifests/12-hpa/30-no-request-demo.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f manifests/12-hpa/31-no-request-hpa.yaml
这些清单各自是干什么的
00-namespace-autoscaling-lab.yaml
作用:
- 创建独立命名空间
autoscaling-lab
目的:
- 让实验对象和其他课题隔离
- 避免事件流、Pod 列表、HPA 列表互相干扰
10-cpu-demo-burn.yaml
作用:
- 创建
cpu-demoDeployment - 容器通过
MODE=burn持续空转烧 CPU - 配置了
requests.cpu = 100m
目的:
- 构造一个能让 HPA 正确计算 CPU 利用率并触发扩容的正样本
20-cpu-demo-hpa.yaml
作用:
- 为
cpu-demo创建 HPA - 目标 CPU 利用率
50% - 最小
1副本,最大4副本 - 缩容稳定窗口显式设置为
30s
30-no-request-demo.yaml
作用:
- 创建
no-request-demo - 同样持续烧 CPU
- 但故意不配置
requests
目的:
- 构造一个“指标有值但 HPA 算不出来”的反样本
31-no-request-hpa.yaml
作用:
- 给
no-request-demo配同样的 CPU utilization 型 HPA
目的:
- 让你看到同样是高 CPU,为什么有的工作负载能扩,有的不能扩
原理解释
这一组实验不是单纯为了“证明 HPA 可以用”。
它真正要建立的是对照思维:
- 只看成功样本,你容易误以为“高 CPU 就一定扩容”
- 只看失败样本,你又可能误以为“这套系统坏了”
把正反两个样本并排放,你才能看清真正决定 HPA 行为的变量是什么。
Step 3:观察 HPA 刚创建时的“预热期”
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab get hpa,pod,deploy -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab describe hpa cpu-demo
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab describe hpa no-request-demo
为什么要专门看这一段
因为新手最容易把“暂时没有指标”误判为“系统故障”。
而真实系统里,HPA 刚建出来时先经历一个短暂的无数据阶段是非常正常的。
我看到的结果
在早期事件里,两个 HPA 都先报过:
FailedGetResourceMetricunable to get metrics for resource cpu: no metrics returned from resource metrics API
原理解释
这表示:
- Pod 刚启动
- 指标采样窗口还没形成
metrics-server还没把这批 Pod 的数据暴露给 API
因此你以后看到 HPA 是 <unknown> 时,排障顺序应该是:
- 先看 Pod 是否刚启动
- 再看
kubectl top - 再看
metrics.k8s.io原始 API - 最后才去怀疑 HPA 自己
Step 4:观察 cpu-demo 的成功扩容
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl top pod -n autoscaling-lab
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab get hpa,pod,deploy -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab describe hpa cpu-demo
我看到的关键结果
预热完成后,kubectl top pod -n autoscaling-lab 看到:
cpu-demo-*大约999mno-request-demo-*也大约1000m
当时 kubectl get hpa,pod,deploy -o wide 看到:
horizontalpodautoscaler.autoscaling/cpu-demo Deployment/cpu-demo cpu: 999%/50% 1 4 4
horizontalpodautoscaler.autoscaling/no-request-demo Deployment/no-request-demo cpu: <unknown>/50% 1 3 1
describe hpa cpu-demo 里可以看到:
ScalingActive=TrueValidMetricFoundScalingLimited=TrueTooManyReplicas
以及事件:
SuccessfulRescale到3SuccessfulRescale到4
命令解释
kubectl top pod
用途:
- 看实时近似资源使用量
注意:
- 这不是 cgroup 文件的原始值
- 它来自
metrics-server - 适合做“此刻大致用了多少”的快速判断
kubectl get hpa,pod,deploy -o wide
用途:
- 一次性把 HPA、Pod、Deployment 放在同一个视图里看
适合回答:
- HPA 算出来多少
- Deployment 期望多少
- 实际跑了多少 Pod
kubectl describe hpa
用途:
- 看 HPA 计算状态、条件和事件
这是 HPA 排障最重要的命令之一。
原理解释
为什么 cpu-demo 会暴冲到 999%?
因为它的 request 只有 100m,而实际 CPU 使用接近 1000m:
1000m / 100m = 1000%
HPA 目标只有 50%,所以从算法角度它会强烈倾向扩容。
但为什么最后只停在 4?
因为:
maxReplicas: 4
已经把上限封住了。
这就是为什么 describe hpa 里能看到:
ScalingLimited=True
这句话的真实含义不是“出问题了”,而是:
- HPA 想再扩
- 但被你配置的安全边界挡住了
Step 5:观察 no-request-demo 的失败场景
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab describe hpa no-request-demo
我看到的关键结果
describe hpa no-request-demo 里明确出现:
ScalingActive=FalseFailedGetResourceMetricfailed to get cpu utilization: missing request for cpu in container main
同时它的 Metrics 一栏显示:
resource cpu on pods (as a percentage of request): <unknown> / 50%
命令解释
describe hpa 的价值在这里体现得非常明显。
如果你只看 kubectl get hpa,你只是知道它是 <unknown>。
但 describe hpa 会进一步告诉你:
- 是拿不到指标
- 还是拿到了指标但算不出 utilization
- 还是算出来了但被上下限限制
原理解释
这个失败场景非常重要。
因为它证明了一件事:
CPU 数值本身存在,不等于 CPU utilization 一定能被计算。
no-request-demo 明明也烧到了一个核附近,但 HPA 仍然不扩。
根因不是没有 CPU 压力,而是:
- 没有
requests.cpu - 所以没有利用率分母
这就是为什么平台治理里,资源规范不是“锦上添花”,而是自动化能力的基础前提。
Step 6:把 cpu-demo 从高负载切到空闲,观察缩容
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab set env deployment/cpu-demo MODE=idle
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab rollout status deployment/cpu-demo --timeout=240s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab get events --sort-by=.metadata.creationTimestamp
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab get rs -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab rollout history deployment/cpu-demo
KUBECONFIG=~/.kube/config-k8s-lab kubectl top pod -n autoscaling-lab
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n autoscaling-lab describe hpa cpu-demo
为什么我用 set env,而不是直接重新 apply 一个 Deployment
因为本课我想保留 HPA 对副本数的控制权,不想让 YAML 的固定副本值和 HPA 抢控制。
kubectl set env 的作用是:
- 修改 Deployment Pod 模板里的环境变量
- 触发滚动更新
- 不直接把
replicas改成某个固定值
它很适合做这种“只换业务行为,不碰副本控制权”的实验。
我看到的关键结果
rollout status显示新版本成功发布。kubectl top pod -n autoscaling-lab最终看到:cpu-demo-*大约1m
describe hpa cpu-demo最终看到:resource cpu on pods (as a percentage of request): 1% (1m) / 50%Deployment pods: 1 current / 1 desired
describe hpa cpu-demo事件里出现:New size: 1; reason: All metrics below target
事件时间线里最关键的证据
kubectl get events --sort-by=.metadata.creationTimestamp 显示了完整过程:
- 早期高负载时,
cpu-demo先扩到3 - 随后扩到
4 - 执行
set env后,Deployment 创建了新的 ReplicaSet:cpu-demo-6845f656c6 - 旧 ReplicaSet 是:
cpu-demo-7588dc5965 - 新旧副本短时间并存,Deployment 在做滚动更新
- 随后 HPA 给出:
SuccessfulRescale ... New size: 1; reason: All metrics below target
从事件年龄可以推断:
- 新空闲版 ReplicaSet 开始接管,大约在
3m54s - HPA 给出最终缩回
1,大约在3m21s
两者相差大约 30 多秒。
原理解释
这一步你要看懂三件事。
第一件:低负载出现,不等于立刻缩容
即使 CPU 很快从 999m 掉到 1m,HPA 也不会立即收缩。
因为对象上配置了:
scaleDown.stabilizationWindowSeconds: 30
这等于告诉 HPA:
先观察一下,确认这次下降不是短暂抖动,再决定是否真的缩。
第二件:Deployment 滚动更新和 HPA 是两个控制器
当你改环境变量时:
- Deployment 负责创建新 ReplicaSet
- HPA 负责根据新指标决定最终副本数
所以时间线上会出现:
- 发布带来的副本波动
- HPA 带来的目标副本变化
两套控制循环交错进行。
第三件:ReplicaSet 的 hash 变化,说明 Pod 模板确实变了
你可以看到旧的 ReplicaSet 是:
cpu-demo-7588dc5965
新的 ReplicaSet 是:
cpu-demo-6845f656c6
这说明 MODE 变化导致 Pod 模板摘要改变,Deployment 正常生成了新版本。
这在排障里很有价值,因为它能帮助你确认:
- 到底是不是你期待的模板变更生效了
Step 7:整理本轮最重要的学习结论
结论 1:HPA 的前提不是“有 YAML”,而是“有指标链路”
至少要同时成立:
metrics.k8s.io可用metrics-server正常- HPA controller 正常工作
结论 2:CPU utilization 型 HPA 离不开 requests.cpu
没有 request,并不是“精度差一点”,而是:
- 根本算不出利用率
结论 3:<unknown> 不一定是故障,常常只是预热
尤其是新建 Pod、新 HPA、刚滚动发布完的场景。
结论 4:缩容通常比扩容保守
这是为了抑制抖动,而不是系统反应迟钝。
结论 5:HPA 扩容不是容量扩容
HPA 只增加 Pod 数量。
如果节点不够、调度失败、下游瓶颈没变,业务仍然可能没有改善。
本轮命令背后的方法论
这一轮你不应该只记住命令本身,更应该记住它们各自回答的问题。
kubectl config current-context
回答:
- 我到底在操作哪套环境
kubectl api-resources --api-group=metrics.k8s.io
回答:
- 集群里有没有注册资源指标 API
kubectl get --raw '/apis/metrics.k8s.io/...'
回答:
- HPA 依赖的原始数据到底有没有、长什么样
kubectl top pod
回答:
- 此刻近似资源使用是多少
kubectl describe hpa
回答:
- HPA 为什么扩、为什么不扩、为什么被限制
kubectl get events --sort-by=.metadata.creationTimestamp
回答:
- 这整件事按时间顺序到底发生了什么
kubectl get rs
回答:
- 是否出现了新旧版本并存
- 当前到底是哪一个 ReplicaSet 在生效
这一课给你的专家能力提升点
如果你把这轮实验真正吃透,你已经不再只是“会写一个 HPA YAML”。
你开始具备的是:
- 验证指标链路的能力
- 解释 HPA 算法输入与边界的能力
- 区分预热、算法失败、上限限制、滚动发布干扰的能力
- 把 HPA 放回整个控制面体系里排障的能力
这才是以后能做公司级平台架构和复杂问题排查的基础。