本文演示 CentOS 8.0:Linux VM-8-17-centos 4.18.0-348.7.1.el8_5.x86_64
前言
Cgroup namespace 是进程的 cgroups 的虚拟化视图,通过 /proc/[pid]/cgroup 和 /proc/[pid]/mountinfo 展示。使用 cgroup namespace 需要内核开启CONFIG_CGROUPS 选项来确认系统是否开启。
[root@VM-8-17-centos ~]# grep CONFIG_CGROUPS /boot/config-$(uname -r)
CONFIG_CGROUPS=y
cgroup namespace的作用
- 防止信息泄漏,在容器内的进程不应该能看到容器外进程的cgroup信息。
- 简化了容器迁移工作。虚拟化的容器视图使容器内的cgroup看起来和其他的cgroup 层级结构没有关系。
比如容器上进程在init cgroup namespace显示的cgroup路径为/sys/fs/cgroup/memory/docker/[contaienr_id],但在容器内却看起来是/sys/fs/cgroup/memory。
- 限制容器进程资源,因为它会把 cgroup 文件系统进行挂载,使得容器进程无法获取上层的访问权限。
也就是说在容器内只能访问/sys/fs/cgroup/memory下的资源(相对于是主机的[contaienr_id]目录),不能访问/sys/fs/cgroup/memory/docker这个父级目录。
cgroup namespace演示
每个 cgroup namespace 都有自己的一组 cgroup 根目录。这些 cgroup 的根目录是在 /proc/[pid]/cgroup 文件中对应记录的相对位置的基点。当一个进程用 CLONE_NEWCGROUP(clone(2) 或者 unshare(2)) 创建一个新的 cgroup namespace时,它当前的 cgroups 的目录就变成了新 namespace 的 cgroup 根目录。
[root@VM-8-17-centos ~]# cat /proc/self/cgroup
12:blkio:/user.slice
11:rdma:/
10:freezer:/
9:hugetlb:/
8:perf_event:/
7:pids:/user.slice/user-0.slice/session-4151.scope
6:devices:/user.slice
5:cpu,cpuacct:/user.slice
4:cpuset:/
3:net_cls,net_prio:/
2:memory:/user.slice/user-0.slice/session-4151.scope
1:name=systemd:/user.slice/user-0.slice/session-4151.scope
上面是我们查看到当前进程的cgroup信息显示在第三列,此处显示的cgroup信息与root cgroup是相对路径,如2:memory:/user.slice/user-0.slice/session-4151.scope
说明进程所在的memory hierarchy的cgroup路径为/sys/fs/cgroup/memory/user.slice/user-0.slice/session-4151.scope
。
如果目标进程的 cgroup 目录位于正在读取的进程的 cgroup namespace 根目录之外时,那么,路径名称将会对每个 cgroup 层次中的上层节点显示 …/ 。
- 在初始cgroup namespace中我们使用 root 用户,在 freezer 层下创建一个子 cgroup 名为 sub1,并且后台执行一个sleep进程并将进程放入该 cgroup 进行限制。
[root@VM-8-17-centos ~]# readlink /proc/self/ns/cgroup
cgroup:[4026531835]
[root@VM-8-17-centos ~]# mkdir /sys/fs/cgroup/freezer/sub1
[root@VM-8-17-centos ~]# sleep 100000000000 &
[1] 1662846
[root@VM-8-17-centos ~]# echo 1662846 > /sys/fs/cgroup/freezer/sub1/cgroup.procs
- 我们在 freezer 层下创建另外一个子 cgroup,名为 sub2, 并且将当前进程号。可以看到当前的进程已经纳入到sub2的 cgroup 下管理了。
[root@VM-8-17-centos ~]# mkdir /sys/fs/cgroup/freezer/sub2
[root@VM-8-17-centos ~]# echo $$
1647306
[root@VM-8-17-centos ~]# echo 1647306 > /sys/fs/cgroup/freezer/sub2/cgroup.procs
[root@VM-8-17-centos ~]# cat /proc/self/cgroup | grep freezer
10:freezer:/sub2
- 使用 unshare(1) 创建一个进程,这里使用了 -C参数表示是新的 cgroup namespace, 使用了 -m参数表示是新的 mount namespace。
[root@VM-8-17-centos ~]# unshare -Cm /bin/bash
# cgroup namespace和初始cgroup:[4026531835]命名空间不同
[root@VM-8-17-centos ~]# readlink /proc/self/ns/cgroup
cgroup:[4026532313]
# 新cgroup namespace中的freezer hierarchy显示为/,说明在新明空空间内他可见的路径是/sys/fs/cgroup/freezer。在初始namespace中他应该是/sys/fs/cgroup/freezer/sub2。
[root@VM-8-17-centos ~]# cat /proc/self/cgroup | grep freezer
10:freezer:/
# 我们查看init 进程1和sleep进程1662846 freezer层级显示为../,因为当前进程处于另外一个命名空间。
[root@VM-8-17-centos ~]# cat /proc/1/cgroup | grep freezer
10:freezer:/..
[root@VM-8-17-centos ~]# cat /proc/1662846/cgroup | grep freezer
10:freezer:/../sub1
- 接下来我们看一下
/proc/self/mountinfo
信息。
[root@VM-8-17-centos ~]# cat /proc/self/mountinfo | grep freezer
383 373 0:36 /.. /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
我们在第四列看到信息是/…,这里面我们看到的挂载点信息依然是init namespace中的挂载点信息,因为我们使用unshare -m新建命名空间的时候不添加其他参数情况下默认会拷贝父命名空间的挂载点信息。但实际我们在新的cgroup namespace中希望看到的是"/"。我们可以重新挂载解决这个问题。
# 设定不传播挂载事件,避免影响其他挂载点
[root@VM-8-17-centos ~]# mount --make-rslave /
# 取消挂载并重新挂载
[root@VM-8-17-centos ~]# umount /sys/fs/cgroup/freezer
[root@VM-8-17-centos ~]# mount -t cgroup -o freezer freezer /sys/fs/cgroup/freezer
# 查看挂载信息 第四列由 /..变成了/
[root@VM-8-17-centos ~]# cat /proc/self/mountinfo | grep freezer
383 373 0:36 / /sys/fs/cgroup/freezer rw,relatime - cgroup freezer rw,freezer
[root@VM-8-17-centos ~]# mount | grep freezer
freezer on /sys/fs/cgroup/freezer type cgroup (rw,relatime,freezer)
cgroup namespace在docker中的使用
在docker官方文档Runtime metrics和docker create
均介绍了docker启动时候对cgroupns的设置。
Cgroup namespace to use (host|private)
'host': Run the container in the Docker host's cgroup namespace
'private': Run the container in its own private cgroup namespace
Use the cgroup namespace as configured by the default-cgroupns-mode option on the daemon (default)
docker启动容器的时候可以通过传入 --cgroupns 'host’或者--cgroupns 'private’来区分docker使用cgroupns的方式。
- 指定 host 为使用初始cgroup namespace。
- 指定 private 为使用新的cgroup namespace。
在docker 20.10.17中演示发现,默认情况下docker使用cgroups v1版本且启动容器默认使用初始的cgroup namespace。
--cgroupns 参数演示
- 我们使用docker pull redis 拉取redis最新版本镜像,不设置 --cgroupns参数启动容器,进入容器后查看容器内进程的cgroup信息。
从上图我们发现在容器内和容器外的cgroup信息和cgroup namespace node id是一致的,也就是默认情况下docker启动容器并未创建新的cgroup namespace。
-
使用--cgroupns 'host’和默认不指定参数一致,不再演示。
-
接下来使用private参数启动一个redis容器,进入容器后查看容器内进程的cgroup信息。
从上图可以看出,当我们指定--cgroupns private参数后,docker创建容器的时候会创建一个新的cgroup namespace,实现了cgroup namespace的隔离。
总结
本章我们主要介绍了cgroup的作用并使用unshare来演示了如何生成一个新的cgroup空间来实现cgroup虚拟视图的隔离。并且介绍了docker 启动容器的时候 --cgroupns 'host|private’参数的区别。
评论区