Kubernetes HPA 指标显示 <unknown> 且不扩容:从指标链路到计算条件的排查实践
服务流量已经升高,Pod 的 CPU 也明显繁忙,但 kubectl get hpa 中的 TARGETS 长时间显示 <unknown>/60%,副本数始终不变。这个现象不能直接归因于 HPA 控制器故障:资源指标要经过 kubelet、Metrics Server、API 聚合层才能到达 HPA,CPU 利用率还依赖工作负载中每个相关容器的 requests.cpu。任何一层缺失,最终都会表现为“拿不到目标值”或“不执行扩容”。
本文以基于 CPU 利用率扩容的 Deployment 为例,给出一套由外到内、可以逐步缩小故障范围的排查方法。
适用场景
- HPA 使用
autoscaling/v2,指标类型为Resource或ContainerResource; kubectl get hpa显示<unknown>,或者AbleToScale=True但ScalingActive=False;kubectl top pod无数据、部分 Pod 无数据,或数据存在但 HPA 仍不扩容;- 工作负载包含 sidecar,资源请求配置不完整;
- 集群自建或升级后,Metrics Server、聚合层、kubelet 证书或网络链路可能发生变化。
如果 HPA 使用 Prometheus Adapter、云厂商监控或队列长度等自定义指标,应继续检查 custom.metrics.k8s.io 或 external.metrics.k8s.io,不能把本文的资源指标链路原样套用。
先理解 HPA 实际在算什么
CPU averageUtilization: 60 不是“CPU 使用 60m”,而是当前 CPU 使用量相对 CPU request 的百分比。简化后的期望副本数为:
期望副本数 = ceil(当前副本数 × 当前平均利用率 ÷ 目标利用率)
假设当前有 3 个 Pod,平均 CPU 利用率为 120%,目标为 60%,理论建议值是:
ceil(3 × 120% ÷ 60%) = 6
这里的分母来自 requests.cpu。若 Pod 中某个参与 Pod 级资源统计的容器没有设置 CPU request,该 Pod 的 CPU 利用率就无法定义,控制器不会基于这项指标执行扩缩容。指标缺失、Pod 尚未就绪或处于启动期时,控制器还会按保守方向重新计算,因此不能只拿某一个 Pod 的瞬时 top 数值推断 HPA 必须扩容。
第一步:读取 HPA 条件与事件
先不要修改资源,完整读取 HPA 状态:
kubectl -n demo get hpa checkout-api
kubectl -n demo describe hpa checkout-api
kubectl -n demo get hpa checkout-api -o yaml
重点看 status.conditions:
AbleToScale:能否读取并更新目标对象的 scale 子资源;ScalingActive:能否取得有效指标并完成副本计算;ScalingLimited:建议副本数是否被minReplicas、maxReplicas或伸缩策略限制;reason与message:例如FailedGetResourceMetric、FailedComputeMetricsReplicas或FailedGetScale,这是下一步排查方向;currentMetrics:控制器最近一次成功读取的指标,若为空或长期不更新,应优先检查指标链路;- Events:错误是否持续出现,还是仅发生在新 Pod 启动后的短窗口。
若 AbleToScale=False,先核对 scaleTargetRef 的 apiVersion、kind、name 和命名空间,不要直接去修 Metrics Server:
kubectl -n demo get deployment checkout-api
kubectl -n demo get deployment checkout-api -o jsonpath='{.spec.replicas}{"\n"}'
kubectl -n demo get hpa checkout-api \
-o jsonpath='{.spec.scaleTargetRef.apiVersion}{" "}{.spec.scaleTargetRef.kind}{" "}{.spec.scaleTargetRef.name}{"\n"}'
第二步:验证 Metrics API,而不只看 Metrics Server Pod
资源指标的数据路径是:
kubelet → metrics-server → kube-apiserver 聚合层 → HPA
Metrics Server 的 Pod 处于 Running,只说明进程在运行,不代表聚合 API 可用。先检查 APIService:
kubectl get apiservice | grep metrics.k8s.io
kubectl describe apiservice v1beta1.metrics.k8s.io
kubectl get apiservice v1beta1.metrics.k8s.io \
-o jsonpath='{.status.conditions[?(@.type=="Available")].status}{" "}{.status.conditions[?(@.type=="Available")].reason}{"\n"}'
当前 HPA 控制器读取资源指标时仍使用 metrics.k8s.io/v1beta1。即使较新的集群同时提供稳定版 v1,排查 HPA 也必须确认 v1beta1.metrics.k8s.io 可用。
然后直接查询 HPA 依赖的 API:
kubectl get --raw '/apis/metrics.k8s.io/v1beta1/namespaces/demo/pods'
kubectl top pod -n demo
kubectl top pod -n demo -l app=checkout-api --containers
结果可以分成三类:
- APIService 不可用:问题在聚合层到 Metrics Server 的服务、端点、证书或网络;
- API 可用但目标命名空间没有 PodMetrics:继续查 Metrics Server 到 kubelet 的抓取;
- PodMetrics 正常且包含目标容器:指标链路基本畅通,转去检查 HPA 选择器、资源 request 和计算条件。
第三步:检查 Metrics Server 到 kubelet 的抓取
查看部署状态、服务端点和近期日志:
kubectl -n kube-system get deployment metrics-server
kubectl -n kube-system get pods -l k8s-app=metrics-server -o wide
kubectl -n kube-system get service metrics-server
kubectl -n kube-system get endpoints metrics-server
kubectl -n kube-system logs deployment/metrics-server --since=15m --tail=200
常见日志与含义包括:
x509: certificate signed by unknown authority:Metrics Server 不信任 kubelet serving 证书;context deadline exceeded或连接拒绝:到节点 kubelet 地址或端口的网络不通;no metrics known for pod:Pod 太新、采样尚未形成,或 kubelet 没有返回该 Pod 的指标;- 目标地址解析到不可达的 Hostname:检查
--kubelet-preferred-address-types与节点地址配置。
生产环境应修复 kubelet serving 证书的签发与信任链。--kubelet-insecure-tls 会关闭证书校验,只适合隔离环境临时验证,不应作为长期修复。还要确认 kube-apiserver 已启用 API aggregation layer,kubelet 开启 Webhook 认证与鉴权,并允许 Metrics Server 到 kubelet 的网络访问。
修复后不要只看日志消失,还要重新执行:
kubectl get apiservice v1beta1.metrics.k8s.io
kubectl get --raw '/apis/metrics.k8s.io/v1beta1/namespaces/demo/pods'
kubectl top pod -n demo -l app=checkout-api --containers
这三条分别验证聚合 API、原始指标对象和人类可读视图。
第四步:核对 Pod 选择范围与 CPU request
先确认 HPA 实际选择了哪些 Pod:
kubectl -n demo get deployment checkout-api \
-o jsonpath='{.spec.selector.matchLabels}{"\n"}'
kubectl -n demo get pods -l app=checkout-api -o wide
kubectl -n demo get pods -l app=checkout-api \
-o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{range .spec.containers[*]}{.name}{"="}{.resources.requests.cpu}{" "}{end}{"\n"}{end}'
最后一条会逐 Pod、逐容器列出 requests.cpu。对于 Pod 级 Resource CPU 指标,业务容器、日志 sidecar、代理 sidecar 都要检查。下面是可用于生产基线的配置片段:
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app: checkout-api
template:
metadata:
labels:
app: checkout-api
spec:
containers:
- name: application
image: registry.example.com/checkout-api:1.8.0
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
- name: log-agent
image: registry.example.com/log-agent:2.3.1
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: checkout-api
namespace: demo
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: checkout-api
minReplicas: 2
maxReplicas: 12
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleDown:
stabilizationWindowSeconds: 300
250m 表示 0.25 个 CPU 核,HPA 的 60% 目标对应业务容器约 150m,但 Pod 级指标会把同一 Pod 内所有容器的使用量和 request 一起计算。若 sidecar 的 CPU 模式与业务流量无关,可能稀释或放大 Pod 级指标。Kubernetes 1.30 及之后可以使用稳定的 ContainerResource 指标,只跟踪业务容器:
metrics:
- type: ContainerResource
containerResource:
name: cpu
container: application
target:
type: Utilization
averageUtilization: 60
切换前必须保证新旧版本 Pod 中都存在指定的容器名。滚动发布期间若只有部分 Pod 存在目标容器,控制器会忽略不匹配的 Pod 并重新计算,可能造成伸缩信号不完整。
第五步:指标存在但仍不扩容时怎么判断
如果 kubectl top 正常,继续逐项检查:
1. 负载是否真的超过 request 比例
不要拿 CPU limit 当分母。例如 request 为 500m,实际使用 250m,利用率是 50%;即使 limit 是 1 核,也不影响这项比例。
kubectl top pod -n demo -l app=checkout-api --containers
kubectl -n demo get deployment checkout-api \
-o jsonpath='{range .spec.template.spec.containers[*]}{.name}{" request="}{.resources.requests.cpu}{" limit="}{.resources.limits.cpu}{"\n"}{end}'
2. 新 Pod 是否尚未进入有效采样窗口
HPA 会结合 Pod Ready 状态处理启动期 CPU 样本。刚启动、未就绪或就绪状态反复变化的 Pod 可能暂时不参与常规平均。应配置可靠的 startupProbe 和 readinessProbe,让 Ready 真正表示实例可以稳定承载流量,而不是简单调小控制器的初始化窗口来追求“立刻扩容”。
3. 是否被上下限或策略限制
ScalingLimited=True 时检查 maxReplicas、当前副本数与 behavior.scaleUp。若建议值为 18 而 maxReplicas 为 12,HPA 正常工作,只是被上限截断。不要通过反复删除重建 HPA 掩盖容量上限问题。
4. 多指标中是否存在失败项
HPA 配置多个指标时会取各指标建议副本数的最大值。如果某个指标建议缩容而另一个指标获取失败,控制器会跳过缩容;但仍可在有效指标建议扩容时执行扩容。此时必须逐项查看 currentMetrics 和事件,不能只看一列聚合输出。
5. 容差与稳定窗口是否让变化看起来“没反应”
轻微越过阈值可能处于 HPA 容差范围内;缩容还常受稳定窗口影响。behavior 只用于控制伸缩速度和稳定性,不能修复 <unknown>。在指标链路未恢复前调整策略没有意义。
一套低风险修复顺序
建议按以下顺序操作,每一步都保留验证证据:
- 修正
scaleTargetRef、标签选择器或不存在的目标对象; - 恢复
v1beta1.metrics.k8s.ioAPIService 可用性; - 修复 Metrics Server 到 kubelet 的证书、地址选择或网络链路;
- 为相关容器补齐合理的 CPU request,通过滚动发布生成新 Pod;
- sidecar 明显干扰业务信号时,评估改用
ContainerResource; - 指标稳定后再调整阈值、最大副本数和
behavior; - 用受控压测观察至少一个完整采样与调谐周期,不在生产环境制造无上限流量。
补 request 会改变调度依据,也可能让原本能调度的 Pod 因节点余量不足而 Pending。上线前先检查节点容量,并用小批量滚动发布验证,而不是一次性修改所有工作负载。
验证与预防
修复后的验证不能只看副本数增加。建议记录下面这组证据:
kubectl -n demo describe hpa checkout-api
kubectl -n demo get hpa checkout-api -w
kubectl -n demo get deployment checkout-api -w
kubectl -n demo get pods -l app=checkout-api -w
期望看到:
ScalingActive=True,currentMetrics有当前值;- 压力超过目标后
desiredReplicas上升,Deployment 创建新 Pod; - 新 Pod 通过启动与就绪探针后进入服务;
- 压力消退时不会立即剧烈缩容,而是遵守稳定窗口;
- 应用错误率、延迟和 Pending Pod 数没有因扩容过程恶化。
长期预防可以落到以下检查:
- 在准入策略或 CI 中要求可扩缩容工作负载显式设置 CPU、内存 request;
- 监控 APIService 的
Available状态、Metrics Server 抓取错误和 HPA 条件; - 对关键 HPA 告警
ScalingActive=False持续时间,而不是只告警副本数不变; - 在版本升级前验证 Metrics Server 与 Kubernetes 的兼容性;
- 不把 Metrics Server 当作历史监控系统,它提供的是自动伸缩所需的短期 CPU、内存信号;
- 为
maxReplicas对应的真实容量准备节点余量,并验证调度、探针和下游承载能力。
总结
HPA 显示 <unknown> 是一个结果,不是根因。可靠的排查顺序是:先看 HPA conditions 和事件,再直接查询 metrics.k8s.io,随后沿聚合层、Metrics Server、kubelet 逐层定位,最后核对 Pod 选择范围、每个容器的 request、就绪状态和伸缩限制。
把“指标能采到”和“指标能计算”分开验证,能避免两类常见误修:Metrics Server 明明正常却不断重装,或者 CPU request 缺失却只调整 HPA 阈值。只有链路、分母和控制策略同时正确,自动扩容才是可预测、可验证的容量机制。
参考资料:
Discussion
评论