前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >揭开K8s适配CgroupV2内存虚高的迷局

揭开K8s适配CgroupV2内存虚高的迷局

作者头像
zouyee
发布2023-11-15 18:45:04
3610
发布2023-11-15 18:45:04
举报
文章被收录于专栏:Kubernetes GOKubernetes GO

在Almalinux替换CentOS的过程中,我们通过kubectl top nodes命令观察到了两个相同规格的节点(只有cgroup版本不同)。在分别调度两个相同的Pod后,我们预期它们的内存使用量应该相近。然而,我们发现使用了cgroupv2的节点的内存使用量比使用了cgroupv1的节点多了约280Mi。

编辑|zouyee

初步分析表明,可能是cAdvisor在统计cgroupv1和v2的内存使用量时存在逻辑上的不一致。

理论上,无论使用cgroupv1还是cgroupv2,两个相同配置的节点的内存使用量应该相近。实际上,在比较/proc/meminfo时,我们发现了总内存使用量近似的情况。那么问题出在哪里呢?

我们发现,这个问题只影响了节点级别的内存统计数据,而不影响Pod级别的统计数据。

问题的根本原因是cAdvisor调用了runc的接口,其计算root cgroup的内存数据方面存在差异。在cgroupv2中,root cgroup不存在memory.current这个文件,但在cgroupv1中root cgroup是存在memory.usage_in_bytes文件的。这导致了在统计cgroupv2内存使用量时出现了不一致的情况。

这个问题可能需要在cAdvisor或runc的逻辑中进行修复,以确保在cgroupv1和cgroupv2中的内存统计一致性。下面我们基于社区issue展开介绍。

v1.28.3 commit:a8a1abc25cad87333840cd7d54be2efaf31a3177

NOTE: Containerd:1.6.21,K8s:1.28, Kernel:5.15.0

技术背景

在Kubernetes中,Google的cAdvisor项目被用于节点上容器资源和性能指标的收集。在kubelet server中,cAdvisor被集成用于监控该节点上kubepods(默认cgroup名称,systemd模式下会加上.slice后缀) cgroup下的所有容器。从1.29.0-alpha.2版本中可以看到,kubelet目前还是提供了以下两种配置选项(但是现在useLegacyCadvisorStats为false):

代码语言:javascript
复制
if kubeDeps.useLegacyCadvisorStats {
    klet.StatsProvider = stats.NewCadvisorStatsProvider(
      klet.cadvisor,
      klet.resourceAnalyzer,
      klet.podManager,
      klet.runtimeCache,
      klet.containerRuntime,
      klet.statusManager,
      hostStatsProvider)
  } else {
    klet.StatsProvider = stats.NewCRIStatsProvider(
      klet.cadvisor,
      klet.resourceAnalyzer,
      klet.podManager,
      klet.runtimeCache,
      kubeDeps.RemoteRuntimeService,
      kubeDeps.RemoteImageService,
      hostStatsProvider,
      utilfeature.DefaultFeatureGate.Enabled(features.PodAndContainerStatsFromCRI))
  }

kubelet以Prometheus指标格式在/stats/暴露所有相关运行时指标,如下图所示,Kubelet内置了cadvisor服务

从 Kubernetes 1.12 版本开始,kubelet 直接从 cAdvisor 暴露了多个接口。包括以下接口:

  1. cAdvisor 的 Prometheus 指标位于 /metrics/cadvisor。
  2. cAdvisor v1 Json API 位于 /stats/、/stats/container、/stats/{podName}/{containerName} 和 /stats/{namespace}/{podName}/{uid}/{containerName}。
  3. cAdvisor 的机器信息位于 /spec。

此外,kubelet还暴露了summary API,其中cAdvisor 是该接口指标来源之一。在社区的监控架构文档中描述了“核心”指标和“监控”指标的定义。这个文档中规定了一组核心指标及其用途,并且目标是通过拆分监控架构来实现以下两个目标:

  1. 减小核心指标的统计收集性能影响,允许更频繁地收集这些指标。
  2. 使监控方案可替代且可扩展。

因此移除cadvisor的接口,成了一项长期目标,目前进度如下(进度状态的标记略为滞后):

  • [1.13] 引入 Kubelet 的 pod-resources gRPC 端点;KEP: 支持设备监控社区#2454
  • [1.14] 引入 Kubelet 资源指标 API
  • [1.15] 通过添加和弃用 --enable-cadvisor-json-endpoints 标志,废弃“直接” cAdvisor API 端点
  • [1.18] 默认将 --enable-cadvisor-json-endpoints 标志设置为禁用
  • [1.21] 移除 --enable-cadvisor-json-endpoints 标志
  • [1.21] 将监控服务器过渡到 Kubelet 资源指标 API(需要3个版本的差异)
  • [TBD] 为 kubelet 监控端点提出外部替代方案
  • [TBD] 通过添加和废弃 --enable-container-monitoring-endpoints 标志,废弃摘要 API 和 cAdvisor Prometheus 端点
  • [TBD+2] 移除“直接”的 cAdvisor API 端点
  • [TBD+2] 默认将 --enable-container-monitoring-endpoints 标志设置为禁用
  • [TBD+4] 移除摘要 API、cAdvisor Prometheus 指标和移除 --enable-container-monitoring-endpoints 标志。

当前版本的cadvisor接口已经做了部分废弃,例如/spec/stats/*等

寻根溯源

kubelet 使用 cadvisor 来获取节点级别的统计信息(无论是使用 cri 还是通过cadvisor 来统计提供程序来获取 pod 的统计信息):

代码语言:javascript
复制
kubernetes/pkg/kubelet/stats/provider.go


 // NewCRIStatsProvider returns a Provider that provides the node stats 
 // from cAdvisor and the container stats from CRI. 
 func NewCRIStatsProvider( 
   cadvisor cadvisor.Interface, 
   resourceAnalyzer stats.ResourceAnalyzer, 
   podManager PodManager, 
   runtimeCache kubecontainer.RuntimeCache, 
   runtimeService internalapi.RuntimeService, 
   imageService internalapi.ImageManagerService, 
   hostStatsProvider HostStatsProvider, 
   podAndContainerStatsFromCRI bool, 
 ) *Provider { 
   return newStatsProvider(cadvisor, podManager, runtimeCache, newCRIStatsProvider(cadvisor, resourceAnalyzer, 
     runtimeService, imageService, hostStatsProvider, podAndContainerStatsFromCRI)) 
 } 
  
 // NewCadvisorStatsProvider returns a containerStatsProvider that provides both 
 // the node and the container stats from cAdvisor. 
 func NewCadvisorStatsProvider( 
   cadvisor cadvisor.Interface, 
   resourceAnalyzer stats.ResourceAnalyzer, 
   podManager PodManager, 
   runtimeCache kubecontainer.RuntimeCache, 
   imageService kubecontainer.ImageService, 
   statusProvider status.PodStatusProvider, 
   hostStatsProvider HostStatsProvider, 
 ) *Provider { 
   return newStatsProvider(cadvisor, podManager, runtimeCache, newCadvisorStatsProvider(cadvisor, resourceAnalyzer, imageService, statusProvider, hostStatsProvider)) 
 } 

可以通过下述两种方式获取节点的内存使用情况

代码语言:javascript
复制
kubectl top node 
kubectl get --raw /api/v1/nodes/foo/proxy/stats/summary | jq -C .node.memory

结果显示cgroupv2节点的内存使用量比相同节点配置但使用 cgroupv1的高一些。kubectl top node 获取节点信息的逻辑在:https://github.com/kubernetes-sigs/metrics-server/blob/master/pkg/storage/node.go#L40

kubelet使用 cadvisor 来获取 cgroup 统计信息:

代码语言:javascript
复制
kubernetes/pkg/kubelet/server/stats/summary.go

 rootStats, err := sp.provider.GetCgroupCPUAndMemoryStats("/", false) 
 if err != nil { 
   return nil, fmt.Errorf("failed to get root cgroup stats: %v", err) 
 }

这里GetCgroupCPUAndMemoryStats调用以下cadvisor逻辑

代码语言:javascript
复制
kubernetes/pkg/kubelet/stats/helper.go

 infoMap, err := cadvisor.ContainerInfoV2(containerName, cadvisorapiv2.RequestOptions{ 
   IdType:    cadvisorapiv2.TypeName, 
   Count:     2, // 2 samples are needed to compute "instantaneous" CPU 
   Recursive: false, 
   MaxAge:    maxAge, 
 }) 
 

cadvisor 基于 cgroup v1/v2 获取不同 cgroup manager接口实现,然后调用GetStats()获取监控信息。

这些实现在计算root cgroup 的内存使用方面存在差异。

v1 使用来自 memory.usage_in_bytes 的内存使用情况:https://github.com/opencontainers/runc/blob/92c71e725fc6421b6375ff128936a23c340e2d16/libcontainer/cgroups/fs/memory.go#L204-L224

v2 使用 /proc/meminfo 并计算使用情况为总内存 - 空闲内存:https://github.com/opencontainers/runc/blob/92c71e725fc6421b6375ff128936a23c340e2d16/libcontainer/cgroups/fs2/memory.go#L217

usage_in_bytes 大致等于 RSS + Cache。workingset是 usage - 非活动文件。

在 cadvisor 中,在workingset中排除了非活动文件:https://github.com/google/cadvisor/blob/8164b38067246b36c773204f154604e2a1c962dc/container/libcontainer/handler.go#L835-L844"

因此可以判断在cgroupv2计算内存使用使用了total-free,这里面包含了inactive_anon,而内核以及cgroupv1计算内存使用量时不会计入 inactive_anon:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/mm/memcontrol.c#n3720

通过下面的测试中,inactive_anon 解释数据看到了差异。

下述分别为cgroupv1及cgroupv2的两个集群

代码语言:javascript
复制
~ # kubectl top node
NAME    CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
node1   98m          2%     1512Mi          12%
node2   99m          2%     1454Mi          11%
node3   94m          2%     1448Mi          11%

其中cgroupv1节点的root cgroup内存使用如下:

代码语言:javascript
复制
~ # cat /sys/fs/cgroup/memory/memory.usage_in_bytes
6236864512
~ # cat /sys/fs/cgroup/memory/memory.stat
cache 44662784
rss 3260416
rss_huge 2097152
shmem 65536
mapped_file 11083776
dirty 135168
writeback 0
pgpgin 114774
pgpgout 103506
pgfault 165891
pgmajfault 99
inactive_anon 135168
active_anon 3645440
inactive_file 5406720
active_file 39333888
unevictable 0
hierarchical_memory_limit 9223372036854771712
total_cache 5471584256
total_rss 767148032
total_rss_huge 559939584
total_shmem 1921024
total_mapped_file 605687808
total_dirty 270336
total_writeback 0
total_pgpgin 51679194
total_pgpgout 50291069
total_pgfault 97383769
total_pgmajfault 5610
total_inactive_anon 1081344
total_active_anon 772235264
total_inactive_file 4648124416
total_active_file 820551680
total_unevictable 0

meminfo文件如下

代码语言:javascript
复制
~ # cat /proc/meminfo
MemTotal:       16393244 kB
MemFree:         9744148 kB
MemAvailable:   15020900 kB
Buffers:          132344 kB
Cached:          5207356 kB
SwapCached:            0 kB
Active:          1557252 kB
Inactive:        4526668 kB
Active(anon):     745916 kB
Inactive(anon):      792 kB
Active(file):     811336 kB
Inactive(file):  4525876 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:               636 kB
Writeback:             0 kB
AnonPages:        618992 kB
Mapped:           624384 kB
Shmem:              2496 kB
KReclaimable:     285824 kB
Slab:             423600 kB
SReclaimable:     285824 kB
SUnreclaim:       137776 kB
KernelStack:        8400 kB
PageTables:         9060 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     8196620 kB
Committed_AS:    2800016 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       40992 kB
VmallocChunk:          0 kB
Percpu:             4432 kB
HardwareCorrupted:     0 kB
AnonHugePages:    270336 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:      302344 kB
DirectMap2M:     3891200 kB
DirectMap1G:    14680064 kB

当前的计算

memory.current - memory.stat.total_inactive_file = 6236864512 - 4648124416 = 1515 Mi -> kubelet 报告的结果

cgroupv2 集群

代码语言:javascript
复制
~ # kubectl top node
NAME    CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
node1   113m         2%     2196Mi          17%
node2   112m         2%     2171Mi          17%
node3   113m         2%     2180Mi          17%

其中一节点的meminfo文件如下:

代码语言:javascript
复制
MemTotal:       16374584 kB
MemFree:         9505980 kB
MemAvailable:   14912544 kB
Buffers:          155164 kB
Cached:          5335576 kB
SwapCached:            0 kB
Active:           872420 kB
Inactive:        5399340 kB
Active(anon):       2568 kB
Inactive(anon):   791340 kB
Active(file):     869852 kB
Inactive(file):  4608000 kB
Unevictable:       30740 kB
Mlocked:           27668 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:               148 kB
Writeback:             0 kB
AnonPages:        716552 kB
Mapped:           608424 kB
Shmem:              6320 kB
KReclaimable:     274360 kB
Slab:             355976 kB
SReclaimable:     274360 kB
SUnreclaim:        81616 kB
KernelStack:        8064 kB
PageTables:         7692 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     8187292 kB
Committed_AS:    2605012 kB
VmallocTotal:   34359738367 kB
VmallocUsed:       48092 kB
VmallocChunk:          0 kB
Percpu:             3472 kB
HardwareCorrupted:     0 kB
AnonHugePages:    409600 kB
ShmemHugePages:        0 kB
ShmemPmdMapped:        0 kB
FileHugePages:         0 kB
FilePmdMapped:         0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
Hugetlb:               0 kB
DirectMap4k:      271624 kB
DirectMap2M:     8116224 kB
DirectMap1G:    10485760 kB

usage = total - free = 16374584 - 9505980

workingset = 总内存 - 空闲内存 - 非活动文件 = 16374584 - 9505980 - 4608000 = 2207 Mi(kubelet 报告的结果)

结论

如上所述,在Linux kernel及runc cgroupv1计算内存使用为

代码语言:javascript
复制
mem_cgroup_usage =NR_FILE_PAGES + NR_ANON_MAPPED + nr_swap_pages (如果swap启用的话)

// - rss (NR_ANON_MAPPED)
// - cache (NR_FILE_PAGES)

但是runc在cgroupv2计算使用了total-free,因此在相似负载下,同一台机器上v1和v2版本的节点级别报告确实会相差约250-750Mi,为了让cgroup v2的内存使用计算更接近 cgroupv1, cgroup v2调整计算内存使用量方式为

代码语言:javascript
复制
stats.MemoryStats.Usage.Usage = stats.MemoryStats.Stats["anon"] + stats.MemoryStats.Stats["file"]

当然,我们同时还需要处理cadvisor的woringset的处理逻辑

由于笔者时间、视野、认知有限,本文难免出现错误、疏漏等问题,期待各位读者朋友、业界专家指正交流,上述排障信息已修改为社区内容。

参考文献

1.https://github.com/torvalds/linux/blob/06c2afb862f9da8dc5efa4b6076a0e48c3fbaaa5/mm/memcontrol.c#L3673-L3680

2.https://github.com/kubernetes/kubernetes/issues/68522

3.https://kubernetes.io/docs/reference/instrumentation/cri-pod-container-metrics/

真诚推荐你关注

Containerd深度剖析-CRI篇

K8s降本增效之成本优化篇

Containerd深度剖析-NRI篇

来个“分享、点赞、在看”?

本文参与?腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-11-10,如有侵权请联系?cloudcommunity@tencent.com 删除

本文分享自 DCOS 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
Prometheus 监控服务
Prometheus 监控服务(TencentCloud Managed Service for Prometheus,TMP)是基于开源 Prometheus 构建的高可用、全托管的服务,与腾讯云容器服务(TKE)高度集成,兼容开源生态丰富多样的应用组件,结合腾讯云可观测平台-告警管理和 Prometheus Alertmanager 能力,为您提供免搭建的高效运维能力,减少开发及运维成本。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
http://www.vxiaotou.com