加上限制,再和观测值对照
本实验在真正的 VM 中运行
这个环境不是 Pod,而是由 KubeVirt 启动的虚拟机。Linux 内核独立运行,systemd 真正管理服务,docker 也不是模拟工具,而是真正的 Docker 引擎。通过 docker run 启动的容器会成为实际进程,docker exec 和 docker logs 也都能照常工作。
过去,这个实验在 Pod 中运行。由于该环境放弃了所有内核权限,启动容器的步骤无法进行,因此只能学习直接解开镜像归档的变通方法。现在不再需要绕路了。
有两点需要了解。
- **首次启动大约需要 1 分钟。**因为 VM 需要启动并安装 Docker,比 Pod 实验(通常 40 秒)更慢。
- **没有浏览器预览。**进入 VM 的连接只开放评分端口。如果启动了 Web 服务器,请在 VM 内使用
curl检查。
目标
亲手设置内存、CPU、PID 限制,并将请求的值(docker inspect 的 HostConfig)与容器实际看到的值(/sys/fs/cgroup/*)并排比较。亲自制造退出码 137,并整理如何将其与 OOM kill 区分。
为什么重要
运维中遇到的问题几乎总是“明明设了限制,为什么没有生效”,或者“CPU 明明很空闲,为什么还是很慢”。前者源于不了解请求值可能与强制执行值不同(在 rootless 环境中,如果没有 systemd delegation,可能只记录请求);后者则源于不了解 CPU cgroup 不会杀死进程,而是让它休眠。而且,这两个问题都不能只靠 docker stats 或仪表盘确认,必须直接读取 cgroup 文件。因此,本实验旨在养成从两个位置读取值并进行对照的习惯。只读其中一处,必然会得出错误结论。
步骤
- 创建
/root/int2目录,将stat -fc %T /sys/fs/cgroup的输出原样保存到/root/int2/version.txt(cgroup2fs表示 v2,tmpfs表示 v1)。 - 将
/sys/fs/cgroup/cgroup.controllers文件的内容原样保存到/root/int2/controllers.txt。 - 使用
alpine:3.20镜像,在后台运行dk-mem容器,并设置**64MiB(67108864 字节)**内存限制。评分时它必须仍处于 running 状态。 - 在
dk-mem内部读取/sys/fs/cgroup/memory.max,将该值原样保存到/root/int2/memmax.txt。 - 使用
alpine:3.20镜像,在后台运行dk-cpu容器,并设置 CPU 0.5 核限制。 - 启动
dk-killed容器后,亲自发送 SIGKILL,使其以退出码 137 结束(不能是 OOM)。然后在/root/int2/exit137.txt中同时写明:137 由128+9组成,以及OOMkill 也会产生相同的 137。 - 使用
alpine:3.20镜像运行dk-pids容器,并设置 PID 限制 64。 - 在
/root/int2/limits.md中写三行。dk-mem行包含实际字节值67108864,dk-cpu行包含0.5(或500m),dk-pids行包含64。
参考
- 内存限制使用
--memory=64m,CPU 使用--cpus=0.5,PID 使用--pids-limit=64。 - 查看请求值时,可以像
docker inspect <이름> | jq -r '.[0].HostConfig.Memory'这样读取。 - 直接发送信号的命令是
docker kill,其默认信号为 SIGKILL。 - 可以使用
docker inspect <이름> | jq -r '.[0].State.ExitCode, .[0].State.OOMKilled'同时查看退出码和 OOM 状态。 - 常见错误 1:如果在第 3、5、7 步添加
--rm,容器会消失,没有可供评分的对象。 - 常见错误 2:如果在第 6 步通过耗尽内存使容器死亡,
OOMKilled会变为 true,导致失败。此步骤必须由人为发送的 SIGKILL 触发。 - 常见错误 3:即使第 4 步的值与第 3 步请求的数字不同,也不要尝试修正。将看到的值原样写下才是正确答案。
判断 cgroup 版本
创建 /root/int2 目录,将 stat -fc %T /sys/fs/cgroup 的输出原样保存到 /root/int2/version.txt(cgroup2fs 表示 v2,tmpfs 表示 v1)。
stat 有一个选项可以查询已挂载文件系统的类型。不要解释结果后写成 v1/v2,而要将得到的字符串原样保存。
列出可用的控制器
将 /sys/fs/cgroup/cgroup.controllers 文件的内容原样保存到 /root/int2/controllers.txt。
cgroup v2 会在根层级放置一个文件,用一行列出可用控制器的名称。不要手动抄写,请直接复制该文件内容。空格差异可以接受,但不能缺少项目。
请求 64MiB 内存限制
使用 alpine:3.20 镜像,在后台运行 dk-mem 容器,并设置**64MiB(67108864 字节)**内存限制。评分时它必须仍处于 running 状态。
内存限制标志接受 m、g 等后缀。64MiB 正好是 67108864 字节,评分会检查这个数字。评分时容器必须仍在运行,因此不能使用很快结束的命令启动。
查看容器实际看到的 memory.max
在 dk-mem 内部读取 /sys/fs/cgroup/memory.max,将该值原样保存到 /root/int2/memmax.txt。
请在容器内部读取 /sys/fs/cgroup/memory.max。其值可能不是 67108864,而是 max;这并非错误,正是本步骤的重点——请求值和强制执行值可能不同。请原样保存看到的内容。
限制 CPU 为 0.5 核
使用 alpine:3.20 镜像,在后台运行 dk-cpu 容器,并设置 CPU 0.5 核限制。
有一个标志可以用小数指定核心数。在内部,quota 会是 period 的一半;这不是“把核心切成两半”,而是“每个周期只运行一半时间”。
制造并解释退出码 137
启动 dk-killed 容器后,亲自发送 SIGKILL,使其以退出码 137 结束(不能是 OOM)。然后在 /root/int2/exit137.txt 中同时写明:137 由 128 + 9 组成,以及 OOM kill 也会产生相同的 137。
不能通过耗尽内存杀死容器,而必须亲自发送 SIGKILL(如果 OOMKilled 为 true,本步骤会失败)。此外,还要在备忘文件中说明 137 可拆分为 128 与 9,并写明 OOM kill 也同样会产生 137。
使用 PID 限制阻止 fork 炸弹
使用 alpine:3.20 镜像运行 dk-pids 容器,并设置 PID 限制 64。
有一个专门限制进程数量上限的标志。内存或 CPU 限制无法阻止 fork 炸弹——虽然每个进程都很小,但数量会耗尽内核资源。
编写限制汇总表
在 /root/int2/limits.md 中写三行。dk-mem 行包含实际字节值 67108864,dk-cpu 行包含 0.5(或 500m),dk-pids 行包含 64。
三行都必须填写通过 docker inspect 确认的值,而不是猜测值。内存保留原始字节数字,CPU 要能识别为 0.5,PID 保留原始数字。