部落格好读版
如果有看我之前文章的朋友会知道,我对于 Docker 实战六堂课 这本书赞誉有加,因为它不仅理论基础扎实,还提供了非常实用的实作范例,帮助读者快速掌握 Docker 和 Linux 的核心概念与应用。受到启发,我写了 从 Linux 基础实现 Docker Bridge 网路:一步步理解容器通讯 (1) 系列文章。
上周小赖老师举办了一场直播,探讨该书中未提及的容器资源限制,并进行了相关实验。内容依然简洁、精彩且实用。因此,我打算以该直播内容为基础,撰写我的理解、延伸内容与实作过程。
如果懒得阅读文字,可以直接观看小赖老师的直播回放 - 探索容器资源限制
Namespaces - Linux 的系统资源隔离
命名空间(Namespaces)是 Linux 核心提供的一种机制,用于隔离不同进程之间的全局系统资源。Docker 利用以下几种命名空间来实现容器的隔离:
- PID 命名空间:隔离进程 ID,容器内的进程无法看到容器外的进程。
- NET 命名空间:隔离网路介面、IP 位址、路由表等,使每个容器拥有独立的网路堆叠。
- MOUNT 命名空间:隔离档案系统的挂载点,容器可以有自己的档案系统视图。
- IPC 命名空间:隔离进程间通信资源,如信号量和共享记忆体。
- UTS 命名空间:隔离主机名和域名,容器可以设定自己的主机名。
- USER 命名空间:隔离使用者和群组 ID,增强安全性。
简单来说,Namespace 就像团体分组,各组(Namespace)彼此隔离,无法互相看到。Namespace 可以嵌套,类似层级分明的组织图。不过,PID 命名空间比较特殊,因为它可以看到自己底下的子命名空间,而其他类型则无法。
透过这些命名空间,Docker 得以确保容器在进程、网路、档案系统等方面的隔离,防止容器之间的相互干扰。目前只需要了解 Docker 和 Kubernetes 的 Namespace 都是基于这个机制来实现的。未来有机会会单独写一篇文章深入探讨。详情可参考 namespaces(7) — Linux manual page。
网路方面的 Namespace 实作,可参考系列文章:从 Linux 基础实现 Docker Bridge 网路:一步步理解容器通讯 (1)。
cgroups - Linux 的计算资源限制
控制群组(cgroups)是 Linux 核心提供的另一种机制,用于限制、监控和隔离进程群组对计算资源的使用。Docker 利用 cgroups 来限制容器的资源消耗,包括:
- CPU 限制:限制容器可使用的 CPU 时间或核心数量。
- 记忆体限制:设定容器可使用的最大记忆体,包括物理记忆体和交换空间。
- 块设备 I/O 限制:限制容器对磁盘的读写速率。
- 网路频宽限制:限制容器的网路吞吐量。
透过配置 cgroups,Docker 能防止单个容器过度消耗系统资源,确保系统的稳定性与公平性。
cgroups 版本的差异
自 cgroups 引入以来,已经发展出两个主要版本:cgroups v1 和 cgroups v2。cgroups v2 针对 v1 的复杂性和一致性问题进行了重大改进,主要透过统一层级结构和标準化介面。虽然需要一定的迁移工作,但 v2 提供更强大的功能、更一致的行为和更简化的管理方式,有助于提升系统资源管理的效率与可靠性。
目前一些主流的 Linux 发行版,已经陆续预设启用 cgroup v2,例如:
- Fedora (since 31)
- Arch Linux (since April 2021)
- openSUSE Tumbleweed (since c. 2021)
- Debian GNU/Linux (since 11)
- Ubuntu (since 21.10)
- RHEL 和 RHEL-like 发行版 (since 9)
要查看 Linux 使用的 cgroups 版本,可以使用以下指令:
stat -fc %T /sys/fs/cgroup/
cgroup v2 的输出为 cgroup2fs;cgroup v1 的输出为 tmpfs。
更多资讯可参考:Kubernetes - About cgroup v2。
以下是补充、修正与延伸后的版本,保持内容通顺、精简且正确:
CPU 和 Memory 资源的特性
要了解 CPU 的运作方式,我们需要引入作业系统的资源分配与调度概念,特别是 CPU 的时间分配与权重调整机制。而 Memory 资源则相对直观,但其容量限制与管理机制也需要作业系统进行控制。
CPU
特性与运作
- 现代 Linux 系统预设使用 CFS(Completely Fair Scheduler) 来调度 CPU 时间,保证进程获得「公平」的执行机会。
- CFS 将 CPU 核心视为工人,每个任务按权重分配工作时间。权重高的任务获得更多资源,而权重低的任务仍有机会执行。
- 比喻:
- 工人每天有固定的工作时间,若专注于单一任务(权重高),可快速完成工作。
- 若多任务同时进行,工人将根据任务的重要性(权重)分配时间,每个任务都会被处理,但完成速度取决于其权重比例。
CPU 调度
- CFS 将 CPU 时间划分为周期(如 100,000 微秒),进程按权重分配这段时间:
- 进程 A:权重为 1024,获得 50% CPU 时间。
- 进程 B 和 C:权重各为 512,各获得 25% CPU 时间。
- 当进程的 CPU 时间用尽,需等待下一周期分配资源。
- 优势:
- 任务只会变慢,不会因资源不足而被终止,CPU 调度具有弹性(软性限制)。
简而言之
- CPU 调度机制确保所有进程能公平共享资源,根据权重比例分配时间,避免任务饿死。
- 即使资源紧张,低优先级任务也可在后续周期继续执行。
Memory
特性与运作
- Memory 是用于暂存程式与资料的有限资源,可比喻为 出租仓库:
- 仓库空间有限,每个顾客(进程)需租用空间存放物品(资料)。
- 当仓库已满,新顾客需等现有顾客清理空间或离开,才能获得存放空间。
记忆体管理
- 作业系统可透过 memory.limit_in_bytes 或 memory.max 设定记忆体上限:
- 当进程超出限制时,可能触发 OOM(Out of Memory)Killer,强制终止记忆体超量使用的进程。
- 系统可能释放部分缓存或非关键资源,但若空间不足,仍需终止某些进程以恢复稳定。
简而言之
- Memory 限制是刚性的(硬性限制),进程无法超出上限使用记忆体。
- 当记忆体不足时,作业系统无法像 CPU 调度那样「延后执行」,需直接终止部分进程以腾出资源。
表格对比
用表格简单比对:
资源特性 | 弹性资源,可共享 | 固定资源,不可超量使用 |
限制机制 | 调度和优先级 | 硬性上限,触发限制可能导致进程终止 |
不足时的行为 | 进程速度变慢,但可继续执行 | 超量分配会导致应用崩溃或触发 OOM Killer |
限制工具 | - cpu.max- cpu.shares | - memory.limit_in_bytes- memory.max |
适用场景 | 确保公平分配,防止 CPU 过载 | 防止记忆体洩漏,确保系统稳定性 |
实验:使用 cgroups 限制进程 CPU 资源
实验环境如下:
- Windows host: Windows 11 Pro 23H2
- WSL Version: 2.3.26.0
- WSL Distribution: Ubuntu 22.04.5 LTS
设置 cgroups v2
虽然我的 WSL 使用的是 Ubuntu 22.04 发行版,但预设却是 cgroup v1:
stat -fc %T /sys/fs/cgroup/
#
tmpfs
这点在 KinD 的 Issues 中有提到。在 wsl-cgroupsv2 专案的说明文件中提到,WSL 2 默认运行在一种混合模式下,支持 cgroup v1 和 v2。这可能会导致在使用某些容器技术(如 Docker 或 Kubernetes)时出现问题。
自 Linux v5.0 起,Linux kernel boot option cgroup_no_v1=<list_of_controllers_to_disable> 可用于停用 cgroup v1 层级架构。根据 WSL 文件,我们必须在 %UserProfile%\\.wslconfig 档案中新增:
[wsl2]
kernelCommandLine = cgroup_no_v1=all
储存后,使用以下指令重启 WSL:
wsl --shutdown
wsl -d Ubuntu-22.04
再次查询 cgroup 使用版本:
stat -fc %T /sys/fs/cgroup/
#
cgroup2fs
查看 cgroup 的位置
切换到 root 使用者,来到 /sys/fs/cgroup 资料夹,查看内容:
$ sudo su
#
root@vince987:/#cd /sys/fs/cgroup
#
root@vince987:/sys/fs/cgroup# ls
#
cgroup.controllers cgroup.threads init.scope sys-kernel-debug.mount
cgroup.max.depth cpu.stat io.stat sys-kernel-tracing.mount
cgroup.max.descendants cpuset.cpus.effective memory.reclaim system.slice
cgroup.procs cpuset.mems.effective memory.stat user.slice
cgroup.stat dev-hugepages.mount misc.capacity
cgroup.subtree_control dev-mqueue.mount sys-fs-fuse-connections.mount
从档名可以看出,它可以定义诸如 cpu、memory、io 等计算资源。
我们在这个目录下建立新资料夹 test_group,等于建立一个新的 group 来控制资源:
root@vince987:/sys/fs/cgroup# mkdir test_group
#
root@vince987:/sys/fs/cgroup# cd test_group/
#
root@vince987:/sys/fs/cgroup/test_group# ls
cgroup.controllers cpu.stat hugetlb.2MB.current memory.oom.group
cgroup.events cpu.weight hugetlb.2MB.events memory.reclaim
cgroup.freeze cpu.weight.nice hugetlb.2MB.events.local memory.stat
cgroup.kill cpuset.cpus hugetlb.2MB.max memory.swap.current
cgroup.max.depth cpuset.cpus.effective hugetlb.2MB.rsvd.current memory.swap.events
cgroup.max.descendants cpuset.cpus.partition hugetlb.2MB.rsvd.max memory.swap.high
cgroup.procs cpuset.mems io.stat memory.swap.max
cgroup.stat cpuset.mems.effective memory.current misc.current
cgroup.subtree_control hugetlb.1GB.current memory.events misc.max
cgroup.threads hugetlb.1GB.events memory.events.local pids.current
cgroup.type hugetlb.1GB.events.local memory.high pids.events
cpu.idle hugetlb.1GB.max memory.low pids.max
cpu.max hugetlb.1GB.rsvd.current memory.max rdma.current
cpu.max.burst hugetlb.1GB.rsvd.max memory.min rdma.max
仅仅是在这建立资料夹,cgroup 就会自动帮我们做初始化。
打印 cpu.max 内容:
cat cpu.max
#
max 100000
这表示一个 CPU 周期是 100,000 微秒,而该 group 可以无限制地使用。
将 max 设定为 10,000,表示在 100,000 微秒的周期内,最多允许使用 10,000 微秒的 CPU 时间,即 10% 的 CPU 使用率:
root@vince987:/sys/fs/cgroup/test_group# echo "10000 100000" | tee cpu.max
#
10000 100000
实验步骤:
结果如下:
清理
在删除 cgroup 时,必须确保其中没有任何进程或子 cgroup,否则删除操作会失败。
# 检查是否有进程
cat /sys/fs/cgroup/test_group/cgroup.procs
# 如果有进程,移动进程到父 cgroup
for pid in $(cat /sys/fs/cgroup/test_group/cgroup.procs); do
echo $pid > /sys/fs/cgroup/cgroup.procs
done
# 删除 cgroup
cd /sys/fs/cgroup/
rmdir /sys/fs/cgroup/test_group
建议使用现成工具(如 cgdelete)来移除 cgroup,等效于上述操作:
# 安装工具
sudo apt install cgroup-tools
# 移除
sudo cgdelete -g cpu:/test_group
Memory 的限制也是差不多的做法,就不另外展示。
小结
Linux 系统中,Namespaces 提供了进程、网路和档案系统等多层面的隔离能力,使每个容器都像一个独立的操作环境。cgroups 则在资源分配和限制上发挥了关键作用,避免单一容器过度消耗系统资源。
在实验中,我们实现了使用 cgroups v2 来限制进程的 CPU 资源,并成功观察到其效果,验证了理论与实践的一致性。
后续还有透过 Docker,来实践和验证资源分配和限制的特性,由于篇幅关系,就留到下一个章节继续。