bigywater.tech
Kubernetes · 108

metrics API:kubectl top 的数据从哪来

kubectl top 的数字是怎么来的?为什么刚起的 Pod 看不到数据?CPU 用量是怎么算出来的?内存为什么和 free、cgroup 的 usage 都对不上?这篇从 kubectl top 一路追到 kubelet 的原始指标和 cgroup,把每一层的数字对上。

300m = 301m
kubelet 累计 CPU 计数器按采样时间戳算出的速率,与 kubectl top 显示的值
23 秒
新建 Pod 在 kubectl top 里第一次出现所用的时间(采集周期 15 秒,需要两次样本)
238 → 88 MiB
cgroup memory.current 扣掉 inactive_file 得到 working set,即 kubectl top 显示的 87Mi

从 kubectl top 到 Metrics API

实测 · 本机执行输出
  kubectl top nodes:
    NAME CPU(cores) CPU(%) MEMORY(bytes) MEMORY(%)
    kx-main-control-plane 125m 1% 1173Mi 14%
    kx-main-worker 332m 3% 483Mi 6%
    kx-main-worker2 24m 0% 386Mi 4%
  APIService v1beta1.metrics.k8s.io:service=kube-system/metrics-server;Available=[('Available', 'True')](聚合 API:apiserver 把 /apis/metrics.k8s.io 的请求转发给 metrics-server)
  GET /apis/metrics.k8s.io/v1beta1/nodes 的一项:name=control-plane;timestamp=18:44:06;window=20.031s;usage={'cpu': '124109031n', 'memory': '1202164Ki'}
  对应 kubectl top 里的数字:CPU 124109031n = 124m;内存 1202164Ki = 1174Mi

kubectl top nodes 给出每个节点的 CPU 和内存。背后是聚合 API:APIService v1beta1.metrics.k8s.io 指向 kube-system/metrics-server,Available=True,apiserver 把 /apis/metrics.k8s.io 的请求转给它。GET /apis/metrics.k8s.io/v1beta1/nodes 的一项带 timestamp、window=20.031s 和 usage: {cpu: 124109031n, memory: 1202164Ki}——124109031n 就是 kubectl top 里的 124m,1202164Ki 就是 1174Mi。

数据源:kubelet 的累计计数器

实测 · 本机执行输出
  GET /api/v1/nodes/worker/proxy/metrics/resource 里的指标名:['container_cpu_usage_seconds_total', 'container_memory_working_set_bytes', 'container_start_time_seconds', 'container_swap_limit_bytes', 'container_swap_usage_bytes', 'node_cpu_usage_seconds_total', 'node_memory_working_set_bytes', 'node_swap_usage_bytes', 'pod_cpu_usage_seconds_total', 'pod_memory_working_set_bytes', 'pod_swap_usage_bytes', 'resource_scrape_error']
  burn 创建后 24 秒(从 Ready 起算),kubectl top pod burn 才第一次有数据
  burn 容器的 container_cpu_usage_seconds_total 两次读数:6.18(采样时刻 …877s)和 17.24(…914s)——每个读数后面都带着 kubelet 采样的时间戳;累计秒数只增不减;用采样时间戳算间隔 36.9 秒 → 平均 CPU = (17.24-6.18)/36.9 = 300m(limit 是 300m)
  同一时间 kubectl top pod burn:burn 301m 3Mi(与上面的速率相符)
  metrics-server 做的事:每 15 秒向每个 kubelet 取一次这个累计值,相邻两次相减再除以时间,得到 CPU 用量

metrics-server 的数据来自每个节点 kubelet 的 /metrics/resource(用 kubectl get --raw /api/v1/nodes/<n>/proxy/metrics/resource 能读到):container_cpu_usage_seconds_total、container_memory_working_set_bytes、pod_*、node_* 等。CPU 是累计计数器:burn 容器(limit 300m 的死循环)两次读数 6.18 和 17.24,读数后面带着 kubelet 的采样时间戳,时间戳间隔 36.9 秒,平均 CPU = (17.24−6.18)/36.9 = 300m;同一时间 kubectl top pod burn 是 301m。(必须用采样时间戳算间隔:kubelet 的读数有缓存,用本地时钟会算错。)

新 Pod 的盲区

实测 · 本机执行输出
  不存在的 Pod:Error from server (NotFound): pods "nope" not found
  fresh 刚 Ready(0.5 秒)时 kubectl top:退出码 1;Error from server (NotFound): podmetrics.metrics.k8s.io "k102/fresh" not found
  fresh 从创建到 top 里第一次出现:23 秒(metrics-server 每 15 秒采集一次,新容器要有两次样本才能算出速率)

新建的 fresh Pod 刚 Ready(0.5 秒)时 kubectl top 返回 podmetrics.metrics.k8s.io "k102/fresh" not found;直到创建后 23 秒才第一次出现——metrics-server 每 15 秒采集一次,新容器要有两次样本才能算出 CPU 速率。刚起的 Pod 看不到 top 数据是正常的,HPA 也因此会对刚启动的 Pod 保持谨慎(见 k50)。

内存是 working set

实测 · 本机执行输出
  mem 容器(进程 80 MiB 匿名内存 + 写了 150 MiB 文件的页缓存):cgroup memory.current=238 MiB;其中 anon=83 MiB,file(页缓存)=150 MiB,inactive_file=150 MiB
  working set = memory.current − inactive_file = 88 MiB
  kubectl top pod mem:mem 37m 87Mi(与 working set 一致,而不是 memory.current;页缓存里不活跃的部分被扣掉了,kubelet 的节点内存压力驱逐也看 working set,见 k31)

mem 容器有 80 MiB 匿名内存加 150 MiB 页缓存:cgroup memory.current 是 238 MiB,其中 anon 83 MiB、file 150 MiB、inactive_file 150 MiB;working set = memory.current − inactive_file = 88 MiB;kubectl top pod mem 显示 87Mi,与 working set 一致,而不是 238 MiB。不活跃的页缓存可回收,不算进去。kubelet 的节点内存压力驱逐也看 working set(见 k31)。

分步演示

Metrics API
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / kind(Docker 里的单节点或多节点真实集群,节点是 Docker Desktop 虚拟机里的容器)/ kubectl;用空的 kubeconfig 之外的独立 kubeconfig,不碰用户自己的集群;计时用 date +%s%N / kubectl 事件时间戳,数字每次运行会有小幅抖动。三节点 kind 集群 kx-main(Kubernetes v1.37.0),metrics-server v0.7.2(采集周期 15 秒);kubelet 指标用 kubectl get --raw /api/v1/nodes//proxy/metrics/resource 读取;cgroup 数据用节点上的 memory.current、memory.stat;burn Pod 是 limit 300m 的死循环,mem Pod 是 python 分配 80 MiB 并写 150 MiB 文件;每种情形各一次
    • 没有验证:custom.metrics.k8s.io / external.metrics.k8s.io 自定义指标 API;metrics-server 的高可用与 --kubelet-insecure-tls;Metrics API 的聚合认证(front-proxy 证书);cAdvisor 完整指标(/metrics/cadvisor)与 /metrics/resource 的差别;kubectl top 的 --containers、--sort-by 选项;Windows 节点
    京ICP备2026067502号-1京公网安备11010502063487号