linux memory cgroup子系统

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
cd /sys/fs/cgroup/memory

mkdir foo
cd foo && ls

cgroup.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
cgroup.event_control   memory.kmem.max_usage_in_bytes      memory.kmem.usage_in_bytes      memory.memsw.usage_in_bytes      memory.stat
cgroup.procs           memory.kmem.slabinfo                memory.limit_in_bytes           memory.move_charge_at_immigrate  memory.swappiness
memory.failcnt         memory.kmem.tcp.failcnt             memory.max_usage_in_bytes       memory.numa_stat                 memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.limit_in_bytes      memory.memsw.failcnt            memory.oom_control               memory.use_hierarchy
memory.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的。

1
2
3
cat memory.oom_control
oom_kill_disable 0
under_oom 0

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

1
2
3
yum install libcgroup-tools

cgdelete memory:foo

限制containerd容器内存

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

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

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

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

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

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

注意/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容器的资源限制情况如下:

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

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

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

cat memory.limit_in_bytes
209715200

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