linux memory cgroup子系统

限制容器的内存使用需要借助memory cgroup子系统。 在使用cgroups时需要先挂载,例如在centos下memory cgroup子系统被挂载到了/sys/fs/cgroup/memory下,,在这个目录下是各个memory控制组目录,每个控制组目录下还可以有子目录,各个控制组形成了一个树状的层级关系。

例如在/sys/fs/cgroup/memory下创建一个名为foo的pids控制组目录,控制组目录中有如下内容:

 1cd /sys/fs/cgroup/memory
 2
 3mkdir foo
 4cd foo && ls
 5
 6cgroup.clone_children  memory.kmem.limit_in_bytes          memory.kmem.tcp.usage_in_bytes  memory.memsw.max_usage_in_bytes  memory.soft_limit_in_bytes  tasks
 7cgroup.event_control   memory.kmem.max_usage_in_bytes      memory.kmem.usage_in_bytes      memory.memsw.usage_in_bytes      memory.stat
 8cgroup.procs           memory.kmem.slabinfo                memory.limit_in_bytes           memory.move_charge_at_immigrate  memory.swappiness
 9memory.failcnt         memory.kmem.tcp.failcnt             memory.max_usage_in_bytes       memory.numa_stat                 memory.usage_in_bytes
10memory.force_empty     memory.kmem.tcp.limit_in_bytes      memory.memsw.failcnt            memory.oom_control               memory.use_hierarchy
11memory.kmem.failcnt    memory.kmem.tcp.max_usage_in_bytes  memory.memsw.limit_in_bytes     memory.pressure_level            notify_on_release

task文件中是控制组中的进程id,可以把某个进程添加到这个控制组中,另外memory.usage_in_bytes是只读的,它的值是当前控制组中所有进程使用的内存总和。

重点关注memory.limit_in_bytes, memory.oom_control文件的值:

memory.limit_in_bytes: 用来设置控制组中所有进程可以使用内存的最大值

memory.oom_control用来设置当控制组中所有进程达到可以使用内存的最大值时,也就是发生OOM(Out of Memory)时是否触发linux的OOM killer杀死控制组内的进程。默认的配置是开启OOM killer的。

1cat memory.oom_control
2oom_kill_disable 0
3under_oom 0

下面演示如何删除上面创建的控制组foo目录,可以使用libcgroup-tools工具:

1yum install libcgroup-tools
2
3cgdelete memory:foo

限制containerd容器内存

nerdctl run启动一个containerd容器时,可以使用-m选项指定容器的最大内存

1nerdctl help run
2......
3--memory value, -m value      Memory limit
4......

下面使用nerdctl启动一个containerd容器,限制其内存使用为100Mb,并在服务器上查找一下它的memory cgroup目录。

1nerdctl run -m 100m -d redis:alpine3.14
298321686255a1a219651ee5c454fc2cb85f2b96aab9bda51c97a1f843c018689

可以在/sys/fs/cgroup/memory/default目录下下出现了一个与容器ID同名的目录,目录中的memory.limit_in_bytes文件的值为104857600 bytes即100Mb:

1cd /sys/fs/cgroup/memory/default/98321686255a1a219651ee5c454fc2cb85f2b96aab9bda51c97a1f843c018689
2cat memory.limit_in_bytes
3104857600

注意/sys/fs/cgroup/memorydefault目录是containerd中的namespace概念,默认是default,如果启动容器时指定了namespace,则就是对应的namespace目录。如nerdctl -n mynamespace run -m 100m -d redis:alpine3.14,则控制组就在/sys/fs/cgroup/memory/mynamespace目录的以容器id为名称的子目录里。

k8s如何使用memory cgroup限制容器cpu

最后根据结合Kubernetes中对容器的资源限制requests.memorylimits.memory,到k8s容器的memory控制组目录中看一下是如何设置的。 这个k8s集群的版本是1.21,容器运行时使用的是containerd。 在这个集群中部署了Kubernetes的Dashboard,设置了dashboard容器的资源限制情况如下:

1resources:
2  limits:
3    cpu: '2'
4    memory: 200Mi
5  requests:
6    cpu: 100m
7    memory: 100Mi

即limits.memory为200Mi,requests.memory为200Mi。memory控制组目录的具体位置可以参考前面第29节中查找cpu控制组目录的方法,这里略过:

1cd /sys/fs/cgroup/memory/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podaaf5f103_b1ee_43b7_a59e_7ff9bf9e377c.slice/cri-containerd-9788e7eefd4a3857e594680987cefc31f82915f2f8edce0ac1bad1bf7b4427cf.scope
2
3cat memory.limit_in_bytes
4209715200

可以看到k8s里pod容器的requests.memory不会修改memory cgroup里的memory.limit_in_bytes,只是在k8s调度时用来计算使用的,看某个k8s节点上是否有可用内存分配给这个pod的容器。而k8s里pod容器的limits.memory被设置到memory cgroup里的memory.limit_in_bytes