前言

最近解决生产问题,生产环境是K8S容器部署,Java应用,通过promtheus和grafana监控容器内存,发现JVM的内存正常,包括堆 栈 Meta direct的内存监控都正常,但是监控发现workingset和RSS持续增加,约等于线性增涨,后面发现是Native memory持续增长,本章内容简单说明容器下的内存监控指标WSS和RSS。

K8S下的容器隔离-cgroup

要解决问题,需要知道K8S容器下的内存限制怎么样的,是如何监控到限制的资源的资源使用率的,根据docker官方文档,容器是通过Linux操作系统的cgroup来进行资源限制的,其实Linux的cgroup常用版本已经有v1 和 v2版本,实现的逻辑不同,但是用途没变化。

v1版本

cgroup v1的资源限制比较复杂,对于CPU 内存是单独子系统分配限制,每个子系统下划分层级结构树来管理,但是这些层级结构树又被多个CSS_SET链表关联,而实际上容器的运行态在node节点上就是进程,所以一个进程可能对应一个CSS_SET,多个进程就是多个CSS_SET。

https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html

https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/memory.html

The name of the subsystem appears as part of the hierarchy description in /proc/mounts and /proc/<pid>/cgroups.所以promtheus的采集指标就是读取这些数据来判断容器内存的,如果在容器下直接top或者free,得到的是宿主机的内存信息。同时文档说明了如何使用cgroup进行资源隔离,当然如果使用容器,这个已经默认封装进docker-engine。

v2版本

相对于v1版本,v2版本在资源限制方面进行了大幅度简化,使用根root层级管理

cgroup2 filesystem has the magic number 0x63677270 (“cgrp”). All controllers which support v2 and are not bound to a v1 hierarchy are automatically bound to the v2 hierarchy and show up at the root. Controllers which are not in active use in the v2 hierarchy can be bound to other hierarchies. This allows mixing v2 hierarchy with the legacy v1 multiple hierarchies in a fully backward compatible way.

https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html

复杂性大大简化,使用一个层级管理所有子系统(比如cpu mem gpu etg)

WSS和RSS

其实WSS和RSS是内存资源,相对于cpu,内存资源的使用相对难判断问题,cpu使用高只要找到执行线程占用即可,内存往往会划分区域,预分配,真实使用,缓存等。往往不可能每个区域都监控到,所以才会很难定位问题。

其中WSS和RSS是cadvisor的监控指标的https://github.com/google/cadvisor/blob/master/info/v1/container.go

RSS: The amount of anonymous and swap cache memory (includes transparent hugepages)

匿名内存(包括堆栈)+swap缓存内存+透明大页

WorkingSet:The amount of working set memory, this includes recently accessed memory, dirty memory, and kernel memory. Working set is <= "usage"

工作集内存的总量,包含近期访问的内存、脏内存以及内核内存。工作集内存量 ≤ “已使用内存量”

其实计算公式代码也有说明:https://github.com/google/cadvisor/blob/master/container/libcontainer/handler.go

根据代码得出结论:

从系统读取WSS和RSS

容器使用Linux的namespace和cgroup特性,那么其他非Linux环境实际上是虚拟化技术支持的,比如mac,win

根据Linux cgroup的特性(上面的官方文档),以MySQL的容器为例,在/sys/fs/cgroup/memory/memory.stat下

The memory.stat file contains the metric values required for cAdvisor formulations.

看看wss和rss是多少,笔者进入容器bash,发现/sys/fs/cgroup/memory目录不存在,笔者的MySQL是最新版,估计使用的是cgroup v2版本

还真是,各个信息平铺了,/sys/fs/cgroup/cgroup.controllers文件存在,就没有rss和wss的直接展示了,不过有使用情况

根据代码计算cgroup v2的内存情况

func setMemoryStats(s *cgroups.Stats, ret *info.ContainerStats) {
	ret.Memory.Usage = s.MemoryStats.Usage.Usage
	ret.Memory.MaxUsage = s.MemoryStats.Usage.MaxUsage
	ret.Memory.Failcnt = s.MemoryStats.Usage.Failcnt
	ret.Memory.KernelUsage = s.MemoryStats.KernelUsage.Usage
	setPSIStats(s.MemoryStats.PSI, &ret.Memory.PSI)

	if cgroups.IsCgroup2UnifiedMode() {
		ret.Memory.Cache = s.MemoryStats.Stats["file"]
		ret.Memory.RSS = s.MemoryStats.Stats["anon"]
		ret.Memory.Swap = s.MemoryStats.SwapUsage.Usage - s.MemoryStats.Usage.Usage
		ret.Memory.MappedFile = s.MemoryStats.Stats["file_mapped"]
	} else if s.MemoryStats.UseHierarchy {
		ret.Memory.Cache = s.MemoryStats.Stats["total_cache"]
		ret.Memory.RSS = s.MemoryStats.Stats["total_rss"]
		ret.Memory.Swap = s.MemoryStats.Stats["total_swap"]
		ret.Memory.MappedFile = s.MemoryStats.Stats["total_mapped_file"]
	} else {
		ret.Memory.Cache = s.MemoryStats.Stats["cache"]
		ret.Memory.RSS = s.MemoryStats.Stats["rss"]
		ret.Memory.Swap = s.MemoryStats.Stats["swap"]
		ret.Memory.MappedFile = s.MemoryStats.Stats["mapped_file"]
	}
	if v, ok := s.MemoryStats.Stats["pgfault"]; ok {
		ret.Memory.ContainerData.Pgfault = v
		ret.Memory.HierarchicalData.Pgfault = v
	}
	if v, ok := s.MemoryStats.Stats["pgmajfault"]; ok {
		ret.Memory.ContainerData.Pgmajfault = v
		ret.Memory.HierarchicalData.Pgmajfault = v
	}

	inactiveFileKeyName := "total_inactive_file"
	if cgroups.IsCgroup2UnifiedMode() {
		inactiveFileKeyName = "inactive_file"
	}

	activeFileKeyName := "total_active_file"
	if cgroups.IsCgroup2UnifiedMode() {
		activeFileKeyName = "active_file"
	}

	if v, ok := s.MemoryStats.Stats[activeFileKeyName]; ok {
		ret.Memory.TotalActiveFile = v
	}

	workingSet := ret.Memory.Usage
	if v, ok := s.MemoryStats.Stats[inactiveFileKeyName]; ok {
		ret.Memory.TotalInactiveFile = v
		if workingSet < v {
			workingSet = 0
		} else {
			workingSet -= v
		}
	}
	ret.Memory.WorkingSet = workingSet
}

anon就是rss,这里差不多470M

ret.Memory.Cache = s.MemoryStats.Stats["file"]
ret.Memory.RSS = s.MemoryStats.Stats["anon"]

通过rss+cache-inactive_file=wss计算为505MB左右

    inactiveFileKeyName := "total_inactive_file"
	if cgroups.IsCgroup2UnifiedMode() {
		inactiveFileKeyName = "inactive_file"
	}

如下:

总结

容器下使用namespace和cgroup技术,其中cgroup限制资源,有v1和v2版本,v2的层级管理更简单,从技术原理看,容器的隔离很弱,在容器中可以直接通过free top拿到宿主机的内存信息,而容器自己的使用内存是通过cgroup的memory特性监控的,可以直接获取到,一般是看WSS和RSS指标。

更多推荐