为什么 top node、free、Grafana 的数据对不上

1. top 查看节点资源使用率超过 100%1

2

3

4

5

6

kubectl top node

NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%

master-1 995m 16% 13760Mi 118%

master-2 827m 13% 10672Mi 92%

master-3 889m 14% 10244Mi 88%

这是由于在计算使用率时,默认使用的是可分配的资源,排除了 Kubelet 保留的部分。在 kubectl 源码中可以看到:

1

2

3

4

5

6

7

for _, n := range nodes {

if !o.ShowCapacity {

availableResources[n.Name] = n.Status.Allocatable

} else {

availableResources[n.Name] = n.Status.Capacity

}

}

如果需要查看节点总的资源使用情况,可添加 --show-capacity 参数:

1

2

3

4

5

6

kubectl top node --show-capacity

NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%

master-1 1161m 14% 13822Mi 87%

master-2 998m 12% 10640Mi 67%

master-3 877m 10% 10298Mi 65%

实际上 Allocatable 和 Capacity 在节点对象上可以直接看到:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

kubectl get node master-1 -oyaml

...

status:

allocatable:

cpu: "6"

ephemeral-storage: "284333649859"

hugepages-1Gi: "0"

hugepages-2Mi: "0"

memory: 11877928Ki

pods: "110"

capacity:

cpu: "8"

ephemeral-storage: 308521756Ki

hugepages-1Gi: "0"

hugepages-2Mi: "0"

memory: 16174632Ki

pods: "110"

在 Kubelet 的配置文件 /var/lib/kubelet/config.yaml 或者启动命令参数 --system-reserved=cpu=1,memory=2Gi --kube-reserved=cpu=1,memory=2Gi 可以查看具体的资源预留额度。详情可以参考 https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/reserve-compute-resources/ 。

Allocatable = Capacity - Reserved - Evicted Threshold(驱逐容忍度),其中 Evicted Threshold 根据不同资源,通常为一个很小的数值或比例。

2. top node 与 Grafana 数据不一致2.1 free 与 node_memory_Mem 同源使用 free 查看节点资源使用情况如下:

1

2

3

4

free -h

total used free shared buff/cache available

Mem: 503Gi 62Gi 243Gi 12Gi 198Gi 426Gi

Swap: 0B 0B 0B

Grafana 节点资源使用情况如下:

使用的 PromQL 为:

总内存, node_memory_MemTotal_bytes{instance=~\"$node\"}已用, node_memory_MemTotal_bytes{instance=~\"$node\"} - node_memory_MemAvailable_bytes{instance=~\"$node\"}从数值上看,free 与 Grafana 数据基本一致。

因为 Grafana 使用的 Node Exporter 采集的 node_memory_Mem 这些指标来自主机的 /proc/meminfo 与 free -h 的数据同源。

2.2 top 使用的是 metrics-server 采集的指标top 查看节点资源使用情况

1

2

3

kubectl top node my-node-name

NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%

my-node-name 4809m 8% 132883Mi 25%

模拟 top 命令向 metrics-server 请求数据:

1

2

3

4

5

6

7

8

9

10

kubectl get --raw /apis/metrics.k8s.io/v1beta1/nodes/my-node-name

{

"kind": "NodeMetrics",

"window": "10.292s",

"usage": {

"cpu": "5094380203n",

"memory": "136278224Ki"

}

}

这里的内存使用量约 130 Gi,130 / 503 = 25.8% 与 kubectl top node 基本一致。

2.3 metrics-server 的数据来自 Kubelet从 metrics-server 的源码可以看到,其在请求 Kubelet 的数据。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

func (kc *kubeletClient) GetMetrics(ctx context.Context, node *corev1.Node) (*storage.MetricsBatch, error) {

port := kc.defaultPort

path := "/metrics/resource"

nodeStatusPort := int(node.Status.DaemonEndpoints.KubeletEndpoint.Port)

if kc.useNodeStatusPort && nodeStatusPort != 0 {

port = nodeStatusPort

}

if metricsPath := node.Annotations[AnnotationResourceMetricsPath]; metricsPath != "" {

path = metricsPath

}

addr, err := kc.addrResolver.NodeAddress(node)

if err != nil {

return nil, err

}

url := url.URL{

Scheme: kc.scheme,

Host: net.JoinHostPort(addr, strconv.Itoa(port)),

Path: path,

}

return kc.getMetrics(ctx, url.String(), node.Name)

}

模拟 metrics-server 向 Kubelet 请求数据

1

2

3

4

5

6

7

8

kubectl get --raw /api/v1/nodes/my-node-name/proxy/metrics/resource |grep node_

# HELP node_cpu_usage_seconds_total [ALPHA] Cumulative cpu time consumed by the node in core-seconds

# TYPE node_cpu_usage_seconds_total counter

node_cpu_usage_seconds_total 1.2683530100816046e+08 1721957059813

# HELP node_memory_working_set_bytes [ALPHA] Current working set of the node in bytes

# TYPE node_memory_working_set_bytes gauge

node_memory_working_set_bytes 1.39524251648e+11 1721957059813

符合预期,请求 metrics-server 与 Kubelet API 提供的监控数据相同。

2.4 node_memory_working_set_bytes 指标有什么不同top 使用的是 node_memory_working_set_bytes,是 Kubelet 提供的指标包括当前正在使用的内存,活跃的缓存,不包括可以被立即回收的缓存、缓冲区,主要是非活跃的文件缓存,其数据来源于 /sys/fs/cgroup。

Grafana 使用的是 node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes,是 Node Exporter 提供的指标包括当前正在使用的内存,但不包括缓存,其数据来源于 /proc/meminfo。

前面可以看到 top 看到的内存使用量大约为 130 Gi,而 Grafana 看到的内存使用量大约是 77 Gi,相差 53 Gi 内存存储的就是一些不能立即被回收的缓存。但由于这两种方式的数据源不同,无法对 53 Gi 进行更详细的分析。

2.5 Kubelet limit 使用的是 container_memory_working_set_bytes对于 Pod 来说,通过 top 和 Grafana 看到的内存使用量可能是相同的,因为,大部分 Grafana 面板绘制 Pod 内存使用量用的是 container_memory_working_set_bytes,这与 top 的计算方式是一致的。

这里需要重点关注的是 Kubelet 会以哪个指标驱逐 Pod? 答案是,container_memory_working_set_bytes 。

container_memory_working_set_bytes 更能代表容器的真实内存使用量。

下面这张图体现的是 container_memory_working_set_bytes (大约 18GiB) 与 container_memory_usage_bytes (大约 33GiB) 的区别。

3. 总结本文采集数据的主机内核版本为 5.4.0-48-generic,主要内容如下:

因为 Kubelet 预留资源,top node 资源使用率可能超过 100%,使用 --show-capacity 可以看到总的资源使用情况常用的节点资源使用率 (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)/ node_memory_MemTotal_bytes ,因为忽略了活跃的缓存资源,所以使用率会比 top node 看到的低一些。在上面例子中,Grafana 展示的是 15% 使用率,top node 展示的是 28%。Kubelet 对 Pod 驱逐使用的是 container_memory_working_set_bytes,与 top pod 看到的内存使用量相同

Back to top: