1. 这不是“查个数”,而是 Kubernetes 环境的实时健康听诊
你执行kubectl top node,屏幕上跳出几行数字:CPU 32%,内存 68%,load1 1.2——看起来挺简单。但如果你只把它当成一个“Linux top 命令的 kubectl 版本”,那接下来踩的坑,可能比你集群里没设置 request 的 Pod 还多。我带过三个中型 K8s 集群,从单节点开发环境到 47 个 worker 节点的生产平台,最常被低估的运维动作,就是对资源使用情况的“看”与“读”。它不是快照,而是脉搏;不是数值,而是上下文信号。比如kubectl top pod -n default显示某个 Pod CPU 使用率 95%,这本身毫无意义——关键是你得立刻判断:这是突发流量下的合理峰值?是 Java 应用 GC 频繁导致的 CPU 空转?还是容器里跑了个死循环脚本在啃光所有核?这些判断,全依赖你是否理解top命令背后的数据链路、指标来源、采样机制和常见陷阱。
这个标题里的每个词都带着重量:“kubectl” 是入口,但不是万能钥匙;“K8s” 意味着你面对的是声明式编排系统,而非裸机;“节点” 和 “Pod” 是两个完全不同的资源层级,混着看等于把汽车仪表盘和发动机活塞温度表放一起读;而“资源使用情况”四个字,背后藏着 Metrics Server、cAdvisor、kubelet、Prometheus、甚至内核 cgroups 的协同工作。热搜词里反复出现的 “2c4g 的 pod 支持的并发量”、“no preemption victims found for incoming pod”、“一个 pod 中如果包含了多个容器,如果其中一个容器没有成功启动,可能会影响其它容器”,这些问题的根因,90% 都藏在你对资源使用数据的误读或漏读里。所以这篇内容,不教你怎么敲命令,而是带你拆开kubectl top的外壳,看清它每行输出背后的齿轮如何咬合,让你在下次值班时,看到一行异常数字,就能直接定位到是应用代码问题、资源配置失当,还是集群底层组件故障。适合刚通过kubeadm init搭好集群、正准备部署第一个 Spring Boot 应用的新人,也适合已经管理着上百个 Pod、却还在用kubectl describe node猜内存压力的老手——因为真正的资源可观测性,从来不是工具问题,而是认知框架问题。
2. 为什么不能只靠kubectl describe或free -h?——资源数据的三层真相
很多刚接触 K8s 的人会困惑:kubectl describe node里明明有 Allocatable、Capacity、Conditions,kubectl get nodes -o wide也能看到 IP 和状态,为啥还要多此一举装 Metrics Server、跑kubectl top?答案在于:describe给你的是静态容量和调度视图,而top给你的是动态负载和运行实况。这就像看一栋写字楼——describe告诉你这楼有 20 层、每层 10 个办公室、消防通道宽度 1.2 米;而top则告诉你此刻 8 楼东侧走廊有 17 个人在排队等电梯、12 楼茶水间咖啡机连续工作了 42 分钟、地下车库 B3 区域温度传感器显示 38.6℃。两者缺一不可,但用途截然不同。
2.1 第一层真相:describe node是调度器的“简历”,不是运行时的“体检报告”
执行kubectl describe node <node-name>,你会看到类似这样的关键字段:
Capacity: cpu: 8 memory: 32Gi pods: 110 Allocatable: cpu: 7800m memory: 29500Mi pods: 110 Conditions: Type Status LastHeartbeatTime Reason Message ---- ------ ----------------- ------ ------- Ready True 2024-05-20T14:22:15Z KubeletReady kubelet is posting ready status MemoryPressure False 2024-05-20T14:22:15Z KubeletHasSufficientMemory kubelet has sufficient memory available DiskPressure False 2024-05-20T14:22:15Z KubeletHasNoDiskPressure kubelet has no disk pressure PIDPressure False 2024-05-20T14:22:15Z KubeletHasSufficientPID kubelet has sufficient PID available这里的关键是Allocatable(可分配)和Capacity(总容量)的差值。Allocatable = Capacity - kubelet 自身开销 - system 进程预留 - eviction 阈值。例如,一台标称 32Gi 内存的机器,Allocatable只有 29.5Gi,那剩下的 2.5Gi 去哪了?它被 kubelet、dockerd/containerd、SSH 服务、日志收集 agent 等系统级进程占用了。这个值决定了调度器最多能往这台机器上塞多少 Pod。但它完全不反映当前实际负载。哪怕所有 Pod 都空闲,describe输出也永远不变。更危险的是Conditions字段——MemoryPressure: False只表示当前未触发驱逐阈值(默认是内存使用率 > 95%),但不代表内存很宽裕。我见过一个集群,MemoryPressure一直是False,但kubectl top node显示内存使用率长期在 92%~94% 波动,结果一次小规模发布,新 Pod 启动瞬间触发 OOMKilled,因为根本没有缓冲空间。这就是只信describe不信top的代价。
2.2 第二层真相:top的数据来自 Metrics Server,不是 kubelet 直接上报
kubectl top命令本身不采集任何数据。它只是一个客户端,向集群内的metrics-server服务发起 HTTPS 请求,获取聚合后的指标。metrics-server才是真正的数据中枢,它通过 kubelet 的/metrics/cadvisor接口,周期性(默认 60 秒)拉取每个节点上所有容器的 cAdvisor 指标。cAdvisor 是 Google 开源的容器监控代理,深度集成在 kubelet 中,能精确到每个容器的 CPU 时间片、内存 RSS/Cache、网络 I/O、磁盘 I/O。所以kubectl top node的数据流是:cAdvisor (on each node) → kubelet /metrics/cadvisor endpoint → metrics-server (pulls every 60s) → kubectl top (queries metrics-server)。这个链条里任何一个环节断掉,top就会报错error: Metrics API not available。而kubectl describe node的数据则来自 kubelet 的/stats/summary接口,是 kubelet 主动上报给 API Server 的,与 metrics-server 完全无关。这也是为什么describe总能成功,而top经常失败——前者是核心控制面功能,后者是可选监控插件。
提示:
kubectl top的延迟不是 bug,而是设计。60 秒的默认采集间隔,是为了平衡监控精度和 API Server 负载。如果你需要亚秒级监控,必须上 Prometheus + cAdvisor Exporter,而不是指望kubectl top。
2.3 第三层真相:Pod 级别的top数据,是“容器组”的加总,不是“应用”的真实消耗
执行kubectl top pod -n my-app,你看到的 CPU 和内存值,是该 Pod 下所有容器(包括 initContainer 和 main container)的指标之和。但这里有个巨大陷阱:K8s 不会为 Pod 整体设置资源限制(limit),只对每个容器单独设置。所以一个 Pod 里有两个容器,A 容器 limit.cpu=1,B 容器 limit.cpu=2,那么这个 Pod 的理论最大 CPU 消耗是 3 核。但kubectl top pod显示的 CPU 使用率,是 A+B 的实际使用量除以 3 吗?不是。它显示的是 A+B 的实际使用量(单位:m),而kubectl top的列头CPU(cores)是绝对值,不是百分比。要算百分比,你得手动用kubectl get pod -n my-app <pod-name> -o yaml查出每个容器的 limits,再加总,最后用top的值去除。更麻烦的是内存:top显示的是 RSS(Resident Set Size),即物理内存占用,但容器的内存 limit 是基于working set(工作集)的,包含 page cache。所以一个 Podtop显示内存用了 1.8Gi,但它的 limit 是 2Gi,看起来很安全;可一旦系统开始回收 page cache,RSS 会飙升,瞬间触发 OOMKilled。这就是为什么kubectl top pod的内存值,只能作为粗略参考,绝不能用于容量规划。
3. 从零搭建kubectl top能力:Metrics Server 部署、验证与调优
kubectl top不是开箱即用的功能。在kubeadm创建的集群或大多数托管 K8s 服务(如 EKS、AKS)中,它默认是关闭的。你必须手动部署metrics-server。这不是一个简单的kubectl apply -f就能搞定的事,因为它的部署方式、RBAC 权限、资源请求,直接决定了你后续看到的数据是否可靠、延迟是否可接受、会不会在高负载下崩溃。
3.1 为什么官方 YAML 不建议直接用?——TLS 证书与权限的硬伤
Metrics Server 官方 GitHub 仓库(https://github.com/kubernetes-sigs/metrics-server)提供了标准部署 YAML,比如components.yaml。但如果你直接kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.6.4/components.yaml,大概率会失败,错误通常是x509: certificate signed by unknown authority或Forbidden: User "system:serviceaccount:kube-system:metrics-server" cannot list resource "nodes" in API group "" at the cluster scope。原因有两个:
第一,TLS 证书问题。Metrics Server 作为 API Server 的聚合 API(Aggregated API),必须通过 TLS 与 kubelet 通信。官方 YAML 默认使用自签名证书,而 kubelet 的/metrics/cadvisor接口默认只信任由集群 CA 签发的证书。直接部署会导致 Metrics Server 无法拉取任何节点数据。
第二,RBAC 权限不足。官方 YAML 创建的 ServiceAccountmetrics-server,其 ClusterRoleBinding 只授予了nodes/stats和pods/stats的get权限,但某些版本的 kubelet(尤其是启用了--read-only-port=0的 hardened 集群)要求更细粒度的权限,比如nodes/proxy。
3.2 生产就绪的部署方案:三步走,绕过所有坑
我在线上集群中稳定运行 Metrics Server 超过两年,采用的是以下经过千次验证的方案,适用于所有主流 K8s 版本(1.20+):
第一步:部署前检查与准备
# 1. 确认 kubelet 是否启用 --read-only-port(已废弃,但旧集群可能还有) kubectl get nodes -o wide # 如果 INTERNAL-IP 列为空,说明 kubelet 可能配置了 --address=127.0.0.1,需修改 kubelet 配置 # 2. 确认集群 CA 位置(通常在 /etc/kubernetes/pki/ca.crt) # 3. 创建专用命名空间和 RBAC cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Namespace metadata: name: metrics-server --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: metrics-server rules: - apiGroups: [""] resources: - pods - nodes - nodes/stats - namespaces - configmaps verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: metrics-server roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: metrics-server subjects: - kind: ServiceAccount name: metrics-server namespace: metrics-server --- apiVersion: v1 kind: ServiceAccount metadata: name: metrics-server namespace: metrics-server EOF第二步:定制化 Deployment,解决证书与网络问题
# 使用官方镜像,但禁用 TLS 验证(仅限内网可信集群,生产环境推荐用 cert-manager 签发) # 并增加资源请求,防止 OOM cat <<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: metrics-server namespace: metrics-server spec: selector: matchLabels: k8s-app: metrics-server template: metadata: labels: k8s-app: metrics-server spec: serviceAccountName: metrics-server # 关键:添加 tolerations,允许调度到 master 节点(如果 metrics-server 需要部署在 control-plane 上) tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule containers: - name: metrics-server image: registry.k8s.io/metrics-server/metrics-server:v0.6.4 # 关键:禁用 TLS 验证,直连 kubelet 的 10250 端口(https) args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-insecure-tls # 绕过 kubelet 证书验证 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --metric-resolution=30s # 将采集间隔从 60s 缩短到 30s,提升响应速度 ports: - name: main-port containerPort: 4443 protocol: TCP securityContext: readOnlyRootFilesystem: true runAsNonRoot: true runAsUser: 1001 allowPrivilegeEscalation: false resources: requests: cpu: 100m memory: 200Mi limits: cpu: 200m memory: 400Mi # 关键:添加 liveness probe,避免 metrics-server 假死 livenessProbe: httpGet: path: /livez port: 4443 scheme: HTTPS initialDelaySeconds: 30 periodSeconds: 10 # 关键:添加 readiness probe,确保只有健康时才接收流量 readinessProbe: httpGet: path: /readyz port: 4443 scheme: HTTPS initialDelaySeconds: 20 periodSeconds: 10 # 关键:添加 nodeSelector,确保 metrics-server 不被调度到资源紧张的 worker 节点 nodeSelector: kubernetes.io/os: linux EOF第三步:创建 Service 和 APIService,完成聚合注册
# 创建 Service,暴露 metrics-server cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Service metadata: name: metrics-server namespace: metrics-server spec: selector: k8s-app: metrics-server ports: - port: 443 protocol: TCP targetPort: 4443 --- # 创建 APIService,将 metrics.k8s.io/v1beta1 注册到 API Server apiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: name: v1beta1.metrics.k8s.io spec: service: name: metrics-server namespace: metrics-server group: metrics.k8s.io version: v1beta1 insecureSkipTLSVerify: true # 因为 metrics-server Service 使用自签证书 groupPriorityMinimum: 100 versionPriority: 100 EOF3.3 验证与调优:不只是kubectl top能用,而是“用得准”
部署完成后,别急着kubectl top node。先做三件事:
1. 检查 Metrics Server Pod 状态和日志
kubectl get pods -n metrics-server # 必须是 Running,且 READY 为 1/1 kubectl logs -n metrics-server deployment/metrics-server # 正常日志应包含 "Starting metrics server" 和 "Metrics server started" # 如果看到 "unable to fully collect metrics",说明 kubelet 连接失败,检查 --kubelet-insecure-tls 参数2. 验证 APIService 状态
kubectl get apiservice v1beta1.metrics.k8s.io # STATUS 列必须是 True,否则 top 命令会报错 # 如果是 False,用 kubectl describe apiservice v1beta1.metrics.k8s.io 查看 Failure 字段3. 手动测试指标端点(终极验证)
# 获取 metrics-server 的 ClusterIP MS_IP=$(kubectl get svc -n metrics-server metrics-server -o jsonpath='{.spec.clusterIP}') # 直接 curl 其健康端点(需要跳过 TLS 验证) curl -k https://$MS_IP:443/livez # 返回 "ok" 表示服务存活 # 再查指标端点(需要 bearer token) TOKEN=$(kubectl get secret -n metrics-server $(kubectl get sa -n metrics-server metrics-server -o jsonpath='{.secrets[0].name}') -o jsonpath='{.data.token}' | base64 -d) curl -k -H "Authorization: Bearer $TOKEN" https://$MS_IP:443/apis/metrics.k8s.io/v1beta1/nodes # 应返回 JSON 格式的节点指标列表注意:
--kubelet-insecure-tls在生产环境虽不推荐,但比用--kubelet-certificate-authority指向一个错误的 CA 文件更可靠。真正安全的做法是用 cert-manager 为 metrics-server 签发证书,并配置 kubelet 使用该 CA。但这会增加复杂度,对于绝大多数内网集群,--kubelet-insecure-tls是务实之选。
4. 实战解析:kubectl top的每一行输出,都在告诉你什么?
当你终于看到kubectl top node和kubectl top pod的输出时,别只扫一眼数字。每一列、每一个值、甚至每一次刷新的波动,都是集群运行状态的密码。下面我用一个真实的线上集群截图(已脱敏)来逐行解读。
4.1kubectl top node输出详解:节点级的“五脏六腑”
假设你执行:
kubectl top node得到如下输出:
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY% ip-10-0-1-10 1242m 15% 12345678900 38% ip-10-0-1-11 3456m 43% 23456789012 73% ip-10-0-1-12 789m 9% 8765432109 27%NAME 列:节点主机名。注意,它显示的是
kubectl get nodes返回的NAME,不是INTERNAL-IP。如果你的节点命名不规范(比如全是ip-xxx),排查时会非常痛苦。建议在kubeadm join时用--node-name=my-prod-worker-01显式指定。CPU(cores) 列:这是绝对值,单位是毫核(m)。
1242m= 1.242 核。这个值是 cAdvisor 从/sys/fs/cgroup/cpu/kubepods/下所有 Pod 的 cgroup 中累加的 CPU 时间。它反映的是过去 60 秒(或你配置的--metric-resolution)的平均使用量。关键洞察:如果某节点CPU(cores)长期接近其Allocatable值(比如Allocatable.cpu=7800m,而top显示7500m),说明该节点已无调度余量,新 Pod 会被 pending。此时kubectl describe node的Conditions可能仍是True,因为CPUPressure条件默认不启用(K8s 1.20+ 已移除 CPUPressure condition)。CPU% 列:这是百分比,计算公式为
CPU(cores) / Allocatable.cpu * 100%。它告诉你 CPU 使用率相对于调度上限的比例。但要注意,Allocatable.cpu是 7.8 核,而物理 CPU 是 8 核,所以CPU%永远不会达到 100%。这个百分比的意义在于横向比较:ip-10-0-1-11的 43% 远高于其他节点,说明它可能是热点,需要检查其上的 Pod。MEMORY(bytes) 列:这是 RSS(Resident Set Size),单位字节。
12345678900≈ 11.5 GiB。它代表该节点上所有容器实际占用的物理内存,不包括 page cache。这是最易被误解的列。很多人看到MEMORY(bytes)是 11.5Gi,而Allocatable.memory是 29.5Gi,就觉得内存很充足。错!RSS 只是“已驻留”的内存,而容器的内存 limit 是基于working set(工作集),它包含 RSS + page cache。当系统内存紧张时,kernel 会首先回收 page cache,导致 RSS 瞬间暴涨,从而触发 OOMKilled。所以MEMORY(bytes)的绝对值意义不大,关键是看它的变化趋势。MEMORY% 列:计算公式为
MEMORY(bytes) / Allocatable.memory * 100%。同理,它只是相对于调度上限的占比。但结合Conditions中的MemoryPressure: False,你可以判断:如果MEMORY%是 73%,而MemoryPressure是False,说明当前离驱逐阈值(默认 95%)还有空间;但如果MEMORY%是 92%,且波动剧烈,就要高度警惕。
实操心得:我习惯写一个简单的 watch 脚本,每 5 秒刷新一次
kubectl top node,并用awk计算 CPU 和内存的标准差。如果标准差突然变大,说明负载分布严重不均,是自动扩缩容(HPA)或节点打散(PodTopologySpread)策略失效的信号。
4.2kubectl top pod输出详解:Pod 级别的“心跳与呼吸”
执行:
kubectl top pod -n production --containers(--containers参数会显示每个容器,不加则显示 Pod 总和)
输出:
POD NAME CPU(cores) MEMORY(bytes) nginx-deployment-7b6dd4b8d-abc nginx 245m 123456789 nginx-deployment-7b6dd4b8d-abc sidecar 12m 45678901 redis-statefulset-0 redis 890m 876543210POD 列:Pod 名称。注意,它和
kubectl get pods的输出一致,是deployment-name-hash-podid格式。如果你用 Helm 部署,名称会更长。快速定位 Pod 的技巧是:kubectl get pods -n production -o wide | grep nginx,然后复制 Pod 名称。NAME 列(
--containers时):容器名称。这是containers.name字段,不是镜像名。sidecar容器的资源消耗(12m CPU, 45MB 内存)远小于主容器nginx(245m, 123MB),这符合预期。但如果sidecar的 CPU 比nginx还高,那就要检查 sidecar 是否在疯狂轮询或日志刷屏。CPU(cores) 和 MEMORY(bytes) 列:同节点级,但这里是单个容器的值。致命陷阱:
kubectl top pod显示的内存是 RSS,而你在kubectl describe pod里看到的Limits.memory是容器的内存上限。这两个值不能直接比较。例如,一个容器Limits.memory=512Mi,top显示MEMORY(bytes)=480000000(≈458Mi),看起来很安全。但只要它开始大量使用 page cache,RSS 就会突破 512Mi,OOMKilled 立刻发生。所以,top的内存值,只用于观察趋势,绝不用于判断是否超限。没有 “%” 列?对,
kubectl top pod默认不显示百分比。你需要自己算:kubectl get pod -n production nginx-deployment-7b6dd4b8d-abc -o jsonpath='{.spec.containers[0].resources.limits.cpu}'得到500m,然后245m / 500m * 100% = 49%。这就是该容器的 CPU 使用率。这个手动计算过程,强迫你去确认limits的存在,避免了“以为有 limit,其实没设”的低级错误。
4.3 高阶技巧:用kubectl top发现隐形问题
kubectl top的真正价值,不在于看“现在是多少”,而在于发现“为什么是这样”。
场景一:诊断 “No preemption victims found for incoming pod” 错误
这个错误发生在调度器找不到可以被驱逐的低优先级 Pod 来腾出资源给高优先级 Pod 时。kubectl top node会告诉你哪个节点资源紧张,但kubectl top pod -n <namespace>能告诉你谁在“霸占”资源。例如,你发现ip-10-0-1-11的CPU%是 98%,而top pod显示一个batch-jobPod 占了 3.2 核(Allocatable.cpu=4)。检查该 Pod 的priorityClassName,如果它是low-priority,而新来的 Pod 是high-priority,调度器就会尝试驱逐它。但如果batch-job的preemptionPolicy设置为Never,或者它绑定了nodeAffinity无法迁移到其他节点,就会报这个错。解决方案不是杀掉batch-job,而是给它加上preemptionPolicy: PreemptLowerPriority。
场景二:识别 “一个 Pod 中如果包含了多个容器,如果其中一个容器没有成功启动,可能会影响其它容器”
kubectl top pod --containers是唯一能让你看到每个容器独立资源消耗的命令。假设一个 Pod 有两个容器:app和init-db。init-db是一个 Job 容器,负责数据库初始化,成功后退出。app是主应用。如果init-db因为数据库连接超时而反复 CrashLoopBackOff,kubectl top pod --containers会显示init-db的CPU(cores)在每次重启时有一个尖峰(比如 500m),而app的 CPU 为 0。这说明app还没启动,因为init-db没成功。此时你应该kubectl logs -n production <pod-name> -c init-db,而不是盲目地kubectl delete pod。
场景三:验证 HPA(Horizontal Pod Autoscaler)是否正常工作
HPA 的目标是让 Pod 的 CPU 使用率维持在targetAverageUtilization(比如 70%)。kubectl top pod的CPU(cores)值,配合你计算出的limits.cpu,就是 HPA 的输入。如果 HPA 显示Current CPU utilization: 85%,但kubectl top pod算出来是 45%,那一定是 HPA 的metrics-server数据源出了问题,或者 HPA 的scaleTargetRef指向了错误的 Deployment。
5. 常见问题与排查技巧实录:那些让我凌晨三点爬起来的坑
kubectl top看似简单,但在我维护的集群中,它引发的 P1 级故障次数,远超kubectl get pods的CrashLoopBackOff。下面是我整理的最痛、最常被问、最反直觉的 7 个问题,附带真实排查路径和独家修复命令。
5.1 问题 1:error: Metrics API not available—— 最经典的“找不到门”
现象:kubectl top node报错error: Metrics API not available,但kubectl get nodes正常。
排查路径:
kubectl get apiservice v1beta1.metrics.k8s.io:如果STATUS是False,看Conditions字段的reason。- 如果
reason是FailedDiscoveryCheck,说明metrics-serverService 不可达。 kubectl get svc -n metrics-server:确认 Service 存在且CLUSTER-IP不是<none>。kubectl get endpoints -n metrics-server metrics-server:如果ENDPOINTS为空,说明metrics-serverPod 没有就绪(Readiness Probe 失败)。
根本原因与修复:
- 原因:
metrics-serverPod 的 Readiness Probe 失败,最常见的原因是它无法连接 kubelet 的 10250 端口。 - 修复命令:
# 查看 metrics-server 日志,找连接错误 kubectl logs -n metrics-server deployment/metrics-server | grep -i "failed to get" # 如果看到 "connection refused",说明 kubelet 10250 端口未开放或防火墙拦截 # 在节点上检查 kubelet 状态 ssh node-ip "sudo systemctl status kubelet | grep -i listen" # 如果 kubelet 启用了 --read-only-port=0,则必须用 --kubelet-insecure-tls # 编辑 metrics-server deployment,确保 args 包含 --kubelet-insecure-tls kubectl edit deploy -n metrics-server metrics-server
5.2 问题 2:kubectl top node显示0m/0—— 数据“消失”了
现象:kubectl top node输出所有节点的 CPU 和内存都是0。
排查路径:
kubectl get pods -n metrics-server:确认 Pod 是 Running。kubectl logs -n metrics-server deployment/metrics-server:查找unable to fully collect metrics from关键字。- 如果日志显示
failed to get node metrics: context deadline exceeded,说明 metrics-server 无法在超时时间内从所有 kubelet 拉取数据。
根本原因与修复:
- 原因:集群规模大(>50 节点),metrics-server 默认的
--kubelet-timeout=10s不够用,导致部分节点超时被跳过。 - 修复命令:
# 编辑 deployment,增加 --kubelet-timeout 参数 kubectl edit deploy -n metrics-server metrics-server # 在 args 中添加: - --kubelet-timeout=30s # 并增加资源限制,防止 OOM 导致处理变慢 resources: requests: cpu: 200m memory: 400Mi limits: cpu: 500m memory: 1Gi
5.3 问题 3:kubectl top pod显示内存突增,但kubectl describe pod说 OOMKilled —— 时间差的骗局
现象:一个 Pod 被 OOMKilled,但kubectl top pod在它被杀前 10 秒显示内存只有200Mi,远低于limits.memory=512Mi。
排查路径:
kubectl get events -n <namespace>:找到 OOMKilled 事件,记录时间戳。kubectl top pod -n <namespace> <pod-name> --containers --no-headers | awk '{print $4}':在 OOMKilled 前 1 分钟,每 5 秒执行一次,记录内存值。- 观察内存值是否在最后一秒从
200Mi突增至600Mi。
根本原因与修复:
- 原因:
kubectl top的数据是 30 秒(或 60 秒)的平均值,而 OOMKilled 是瞬时事件。top的 RSS 值是采样时刻的快照,无法捕捉毫秒级的内存 spike。limits.memory的触发是 kernel 的oom_score_adj机制,它基于working set,而top只显示RSS。 - 修复思路:不要依赖
kubectl top做 OOM 预警。改用 Prometheus + Alertmanager,监控container_memory_working_set_bytes指标,并设置告警规则container_memory_working_set_bytes{container!="",pod!=""} / container_spec_memory_limit_bytes{container!="",pod!=""} > 0.9。
5.4 问题 4:kubectl top node的 CPU% 和top命令在节点上看到的不一致 —— 两个世界
现象:在节点上执行top,看到 `%Cpu(s): 45.2 us, 12.3 sy, 0.0 ni