从生产数字的一侧来看 — cAdvisor 与 node exporter
一句话总结
PromQL 只是挑选并计算已经存在的数字。这些数字数的是什么,是由 exporter 决定的。cAdvisor 的 CPU 为什么是累积的,working set 为什么与 RSS 不同,限制(limit)为什么不在 cAdvisor 里——从这里开始,就是 exporter 这一侧的故事了。
为什么需要它
如果有人问过你“CPU 使用量是 658855,这算多吗?”,那他并不是查询写错了,而是不了解指标的类型。container_cpu_usage_seconds_total 是把容器诞生以来用掉的 CPU 时间一直累加起来的值,所以活得越久的容器永远越大。数值的大小什么也说明不了,斜率才是答案。
出于同样的原因,以 _total 结尾的指标总是要经过 rate、increase 来读取,而内存这样的仪表则直接读取。后缀不是惯例,而是对读取方法的声明。
工作原理
cAdvisor 在 kubelet 里面。抓取节点的 /metrics/cadvisor,就能得到该节点上运行的所有容器的 cgroup 统计。标签 id 就是原样的 cgroup 路径,其中包含 QoS 类别、Pod UID 和容器 ID。
内存有三个指标,数的各不相同。
| 指标 | 含义 |
|---|---|
container_memory_usage_bytes |
内核认为这个 cgroup 正在使用的全部内存 |
container_memory_working_set_bytes |
从 usage 中减去可回收的非活动文件缓存后的值 |
container_memory_rss |
匿名内存(没有对应文件的内存) |
cAdvisor 源码的计算是 usage - total_inactive_file(cgroup v1)或 usage - inactive_file(cgroup v2),结果为负时下限取 0。Kubernetes 判定驱逐时使用的值也是这个 working set——官方文档写道:“kubelet 在计算中排除 inactive_file,因为它认为在内存压力下可以回收。”担心 OOM 时应该看 working set 而不是 usage,原因就在于此。
限制可能不在 cAdvisor 里。cAdvisor 原始输出中明明有 container_spec_cpu_quota,但 kube-prometheus-stack 默认通过 metric_relabel_configs 丢弃了该系列的大部分指标。所以要看“使用量相对于限制的比例”,就必须以 namespace、pod、container 为连接键,把 kube-state-metrics 的 kube_pod_container_resource_limits 拼接上去。这不只是这个集群的情况,大多数技术栈都是如此。
node exporter 的 CPU 是按模式分类的累积时间。node_cpu_seconds_total 是每个核心 × 每个模式对应一条序列。使用率用“没有空闲的比例”来求——1 - avg(rate(...{mode="idle"}[5m]))。如果用 sum,结果会按核心数放大。
MemFree 与 MemAvailable 回答的是不同的问题。free 是没人使用的内存,available 是“可以让给新任务的量”。页缓存的大部分是可回收的,所以 available 要大得多。这个集群的 cubi01 是 free 1.5 GiB、available 9.0 GiB——只看 free 来设告警,正常的节点每天都会报警。
在现场相遇的样子
很常见的做法是把 CPU throttling“大于 0 就算事故”来设告警,但实际测量就会发现,设了限制的容器大多总是经历 0.0x% 左右的 throttle。这个集群的实测里,最高的容器是 0.19%,其余为 0。所以判断要用比例——rate(throttled_periods) / rate(periods)。而且没有设置限制的容器根本没有 CFS 指标。没有与为 0 是不同的。
下一项实验要做什么
实验 Pod 的 Prometheus 中装有从运行本站的 7 节点集群中采集的 3 小时实测数据。共 674 条序列——真实的节点、真实的容器、真实的 GPU。用这份数据来测量容器 CPU 的斜率,用 throttle 比例判断是否超出限制,确认 usage 与 working set 的差值同哪个指标相等,从 cgroup 路径读出 QoS 类别,再与 kube-state-metrics 拼接,算出相对于限制的使用率。