Container Cpu and Memory Cgroupfs

时间:2022-12-17 18:00:46

每个容器,pod在启动之后都会有着属于自己的cgroup路径,在该路径下有着这个容器cpu,memory限制参数,能够控制资源使用的上限,而在我们日常集群运营维护中,这些原理能够帮助我们快速解决问题,在此做一些总结


容器CPU

cpu容器限制原理
docker容器限制参数
--cpu 4:表示使用4个核心
--cpuset-cpus:指定cpu id核心绑定
Kubernetes容器限制参数
--requests-cpu:最低限制使用多少核心
--limits-cpu:最高能使用到多少核心

每个容器的创建都在 CPUCgroup 的子系统中建立一个控制组,然后把容器中进程写入到这个控制组里,这时候"Request CPU"就需要为容器设置最低可用CPU,"Limit CPU"就需要为容器设置可用 CPU 的上限,这里就得说到控制容器cpu的cgroup的三个参数,参数如下

cpu.cfs_period_us:是CFS 算法的一个调度周期,一般它的值是 100000,以 microseconds 为单位,也就 100ms,基本在docker/kubernetes使用下都不会变,可以直接用100ms计算。
cpu.cfs_quota_us:是CFS 算法中,一个调度周期里这个控制组被允许的运行时间,比如这个值为 50000 时,就是 50ms。
cpu.shares:可以控制CPU Cgroup子系统下控制组可用CPU的相对比例,在整体系统资源达到上限是可以控制容器能够分配的cpu比例
参数查看路径
/sys/fs/cgroup/cpu/
指定cpu id进行资源绑定,需要查看
/sys/fs/cgroup/cpuset/cpuset.cpus

limit cpu上限:容器 CPU 的上限由 cpu.cfs_quota_us 除以 cpu.cfs_period_us 得出的值来决定的。而且,在操作系统里,cpu.cfs_period_us 的值一般是个固定值(100ms),不会去修改它,所以我们就是只修改 cpu.cfs_quota_us

request cpu:当容器整体limit可能超过主机本身资源限制时,通过cpu.shares进行控制,保证最低资源使用

主机cpu共4核

容器A cpu.cfs_quota_us为200ms,200/100=2核,容器A最高使用资源上限为2 core,cpu.shares为1024

容器B cpu.cfs_quota_us为300ms,300/100=3核,容器B最高使用资源上限为3 core,cpu.shares为3072

当容器A,B都跑满时主机资源明显不足,这个时候主机就通过cpu.shares比例A:B=1024:3072,分配容器A 1核使用,分配容器B 3核使用

Container Cpu and Memory Cgroupfs

总结:limit CPU就是容器所在Cgroup控制组中的CPU上限值,request CPU的值就是控制组中的cpu.shares的值。


容器Memory

容器在运行一段时间之后,为什么会被kill,内存在监控上显示没有到上限,为什么会OOM了?

OOM的原因就是容器使用的内存超过cgroup限制,无法正常释放使用的内存,内存在容器层面是属于硬限制,如果超过使用,就会引发oom killMemory Cgroup也是Linux Cgroups子系统之一,它的作用是对一组进程的Memory使用做限制。Memory Cgroup的虚拟文件系统的挂载点一般在"/sys/fs/cgroup/memory"这个目录下,这个和CPU Cgroup类似。




同样memory cgroup强相关的三个参数

memory.limit_in_bytes:控制组内所有进程可以使用的内存limit限制,容器最高能使用的内存,超过则会被oom kill。
memory.oom_control:当控制组中的进程内存使用达到上限值时,这个参数能够决定会不会触发oom kill。如果没有人为设置的话,memory.oom_control 的缺省值就会触发 OOM Killer。如果我们不希望触发oom kill,只要执行 echo 1 > memory.oom_control 就行了,这时候即使控制组里所有进程使用的内存达到 memory.limit_in_bytes 设置的上限值,控制组也不会杀掉里面的进程。
memory.usage_in_bytes:当前控制组使用的内存资源,不包括page_cache。
memory.stat:可以查看到内存开销


查看容器退出原因

裸docker运行

通过docker inspect查看,就会看到容器处于"exited"状态,并且"OOMKilled"是 true。


kubernetes pod

kubectl describe pod $podname可以在事件中查看到oom信息

再通过主机上/var/log/message日志可以查看到类似OOM推出的堆栈,可以查看到退出时容器的内存达到了limit上限。


RSS:RSS是指进程真正能申请到物理页面的内存大小。

Page Cache每个进程除了各自独立分配到的RSS内存外,如果进程对磁盘上的文件做了读写操作,Linux还会分配内存,把磁盘上读写到的页面存放在内存中,这部分的内存就是Page Cache。

备注:而我们在容器中经常遇到的RSS内存使用量并不高但是却触发了limit的OOM,原因就在于进程读写文件产生的page_Cahce没有及时的释放给RSS申请,导致OOM


代码程序去读取 100MB 的文件,在读取文件前,系统中 Page Cache 的大小是 388MB,读取后 Page Cache 的大小是 506MB,增长了大约 100MB 左右,多出来的这 100MB,正是我们读取文件的大小。

Container Cpu and Memory Cgroupfs

在 Linux 系统里只要有空闲的内存,系统就会自动地把读写过的磁盘文件页面放入到 Page Cache 里。那么这些内存都被 Page Cache 占用了,一旦进程需要用到更多的物理内存,执行 malloc() 调用做申请时,就会发现剩余的物理内存不够了,那该怎么办呢?这就要提到 Linux 的内存管理机制了。

Linux 的内存管理有一种内存页面回收机制(page frame reclaim),会根据系统里空闲物理内存是否低于某个阈值(wartermark),来决定是否启动内存的回收。内存回收的算法会根据不同类型的内存以及内存的最近最少用原则,就是 LRU(Least Recently Used)算法决定哪些内存页面先被释放。因为 Page Cache 的内存页面只是起到 Cache 作用,自然是会被优先释放的。

所以,Page Cache 是一种为了提高磁盘文件读写性能而利用空闲物理内存的机制。同时,内存管理中的页面回收机制,又能保证 Cache 所占用的页面可以及时释放,这样一来就不会影响程序对内存的真正需求了。

了解到了memory内存统计的方法,当看到容器长期处于limit的临界值,但依然能够不断申请到内存的原因,其实就是page_cache一直存在可以申请到的内存,可以通过memory.stats查看到当前的内存开销

启动容器后可以看到memory.stats参数中的rss跟cache,而cache代表的就是page_Cache,而这部分的内存是可以回收的,如果回收不掉造成容器的oom就需要进一步排查一下是否是因为脏页太多导致的无法回收

#cat /sys/fs/cgroup/memory/memory.stat
cache 9864474624
rss 3018174464
rss_huge 0
shmem 0
mapped_file 3313664
dirty 20480
writeback 0
swap 0