1. 从内核视角看Cgroups不只是容器的基石如果你在Linux系统上跑过Docker容器或者管理过一台需要公平分配CPU和内存的服务器那你大概率已经间接用上了Cgroups。但很多人对它的理解可能还停留在“Docker用来限制资源的东西”这个层面。实际上Cgroups是Linux内核中一个相当古老且基础的设计它的全称是“Control Groups”直译过来就是“控制组”。我第一次在生产环境里深挖它是因为一个线上服务莫名其妙地“吃掉”了整台机器的内存导致其他应用全部瘫痪。当时用top命令只能看到是哪个进程在“作恶”却没法从系统层面给它套上一个“紧箍咒”。从那时起我才真正意识到对于系统稳定性和多租户环境来说光有进程视图是远远不够的你需要一种机制能把一组相关的进程“打包”起来作为一个整体去管理它们的资源配额。这就是Cgroups要解决的核心问题。简单来说Cgroups提供了一种将进程及其所有子进程组织成层次化分组的能力并为这些分组绑定一系列的参数或控制器。这些控制器我们称之为“子系统”它们可以是CPU、内存、磁盘I/O、网络甚至是像cpusetCPU和内存节点绑定这样的更具体的资源调度器。通过这种机制系统管理员就能像给不同部门分配预算一样给不同的进程组分配系统资源实现资源的隔离、限制、审计和优先级控制。这对于现代云计算、容器化部署以及高密度部署的服务器环境来说是至关重要的基础设施。没有它所谓的“资源隔离”就无从谈起一个失控的进程足以拖垮整台物理机。2. Cgroups核心概念与架构拆解要玩转Cgroups必须先吃透它的几个核心概念。这些概念构成了整个机制的逻辑骨架理解它们后面的操作和配置才会清晰明了。2.1 三大核心要素任务、控制组、子系统与层级任务在Cgroups的语境里任务就是一个进程或者更精确地说是内核调度的一个实体。每个任务在任意时刻都必然属于某个Cgroups层级结构中的某一个具体的控制组。控制组这是资源控制的主体单位。你可以把它想象成一个“资源容器”或者“分组标签”。一个控制组定义了一组任务以及施加于这组任务上的资源控制参数。这些参数由挂载到该控制组上的子系统来定义和管理。控制组以目录的形式存在于虚拟文件系统中其内部的文件就代表了可配置的参数和可查看的状态。子系统这才是真正干活的“资源控制器”。一个子系统代表一种单一的资源或一种特定的控制能力。例如cpu子系统使用CFS完全公平调度器来限制组的CPU使用份额。cpuacct子系统生成组内进程的CPU资源使用情况报告。cpuset子系统为组内的任务分配独立的CPU核心和内存节点。memory子系统设定组的内存使用限制并生成内存资源使用报告。blkio子系统为块设备如磁盘设定输入/输出限制。devices子系统允许或拒绝组内任务访问设备。freezer子系统挂起或恢复组内的任务。net_cls和net_prio为网络数据包打上标签以便tc等工具进行流量控制。内核开发者可以编写新的子系统来扩展控制能力。一个子系统在同一时间只能附加到一个层级上。层级这是Cgroups最具特色的设计。层级是一棵由控制组构成的树状结构。这棵树有一个根控制组下面可以创建任意多层的子控制组。每个层级在创建挂载时会关联一个或多个子系统。系统中的所有任务在这个层级中都有且仅有一个位置即属于某一个控制组。一个关键特性是你可以同时存在多个独立的层级。例如你可以用一个层级专门管理CPU份额关联cpu子系统用另一个完全独立的层级专门管理内存限制关联memory子系统。这种设计提供了极大的灵活性避免了将所有控制维度强行塞进一棵树所带来的复杂性。2.2 虚拟文件系统用户空间的操控界面Cgroups对用户空间暴露的接口是一个虚拟文件系统通常是挂载在/sys/fs/cgroup/目录下。这是你与Cgroups交互的主要方式完全遵循“一切皆文件”的哲学。当你挂载一个Cgroups层级时比如mount -t cgroup -o cpu,cpuacct cpu /sys/fs/cgroup/cpu就会在/sys/fs/cgroup/cpu目录下创建一个层级的视图。这个目录本身就是根控制组。在这个目录下你会看到两类关键文件子系统控制文件如cpu.shares,memory.limit_in_bytes等。向这些文件写入值就设置了该控制组的资源参数读取这些文件就获取了当前的状态或使用量。任务管理文件tasks这个文件列出了线程ID。向这个文件写入一个PID就会将对应的单个线程移动到当前控制组。需要注意的是一次只能写入一个PID。cgroup.procs这个文件列出了进程组ID。向这个文件写入一个PID会将对应进程的所有线程作为一个整体移动到当前控制组。写入0则表示移动当前shell进程所在进程组的所有任务。通过mkdir和rmdir命令你可以在这个虚拟文件系统中创建和删除子控制组操作起来和普通目录完全一样。这种设计使得用Shell脚本或任何支持文件操作的语言来管理Cgroups变得异常简单。2.3 关键特性原理解读notify_on_release与release_agent这是一个自动化清理机制。当一个控制组内的最后一个任务退出进程结束或被移走并且该控制组下没有任何子控制组时如果其notify_on_release标志被设置为1内核就会执行release_agent文件中指定的命令该文件只存在于层级根目录。这个机制对于动态创建和销毁的容器环境非常有用可以自动回收废弃的控制组避免“垃圾”堆积。但在生产环境中需谨慎使用因为错误的release_agent脚本可能导致意外行为。clone_children这个标志主要影响cpuset子系统。当在一个控制组中将其设置为1后在该控制组下新创建的子控制组会自动继承父控制组的cpuset.cpus和cpuset.mems配置。这简化了嵌套的cpuset配置特别是在容器启动时子容器可以自动获得与父容器相同的CPU和内存节点绑定。注意tasks和cgroup.procs文件的使用有重要区别。如果你要移动一个多线程应用比如Java、Nginx向tasks文件写入主进程PID只会移动主线程其他工作线程仍留在原控制组这会导致资源控制失效或产生不可预知的问题。正确的做法是向cgroup.procs写入PID确保整个进程组被整体迁移。3. 从零搭建与实战配置理论讲得再多不如动手操作一遍。下面我们以一个典型的服务器资源分区场景为例演示如何从零开始使用Cgroups。假设我们有一台8核CPU、16GB内存的服务器需要运行三类服务高优先级的Web服务web、常规的后端处理服务backend以及低优先级的批处理任务batch。我们的目标是限制它们的CPU和内存使用。3.1 环境准备与层级挂载现代Linux发行版如CentOS 7/Ubuntu 16.04的systemd已经默认挂载了Cgroups v2或v1的混合层级。为了清晰演示我们先手动建一个独立的层级。通常/sys/fs/cgroup是一个tmpfs用于统一管理所有Cgroups挂载点。# 首先创建一个临时挂载点如果不存在 sudo mkdir -p /sys/fs/cgroup # 挂载一个tmpfs作为cgroup的挂载根目录通常系统已做 sudo mount -t tmpfs -o size10M,mode755 cgroup_root /sys/fs/cgroup # 创建用于CPU控制的层级并关联cpu和cpuacct子系统 sudo mkdir -p /sys/fs/cgroup/cpu_and_mem sudo mount -t cgroup -o cpu,cpuacct,memory cpu_and_mem /sys/fs/cgroup/cpu_and_mem # 检查挂载结果 mount | grep cgroup # 你应该能看到类似这样的输出 # cgroup_root on /sys/fs/cgroup type tmpfs (rw,relatime,size10240k,mode755) # cpu_and_mem on /sys/fs/cgroup/cpu_and_mem type cgroup (rw,relatime,cpu,cpuacct,memory)进入/sys/fs/cgroup/cpu_and_mem目录你会看到一堆以子系统命名的文件如cpu.shares,memory.limit_in_bytes以及tasks,cgroup.procs等。这个目录就是刚创建的层级的根控制组它包含了系统上所有的进程。3.2 创建子控制组并配置资源限制现在我们在根控制组下为三类服务创建子控制组。cd /sys/fs/cgroup/cpu_and_mem sudo mkdir web backend batch每个新建的目录都是一个独立的控制组。接下来我们为它们设置资源限制。1. 设置CPU份额CPU SharesCPU子系统使用“份额”来分配CPU时间。这是一个相对权重。默认值是1024。假设我们给web组2倍于backend的权重batch组权重最低。# 查看根控制组的份额通常是1024 cat /sys/fs/cgroup/cpu_and_mem/cpu.shares # 设置web组权重为2048 echo 2048 | sudo tee /sys/fs/cgroup/cpu_and_mem/web/cpu.shares # 设置backend组权重为1024默认也可显式设置 echo 1024 | sudo tee /sys/fs/cgroup/cpu_and_mem/backend/cpu.shares # 设置batch组权重为512 echo 512 | sudo tee /sys/fs/cgroup/cpu_and_mem/batch/cpu.shares这意味着当CPU竞争激烈时web、backend、batch三个组能分到的CPU时间比例大约是 4:2:1。注意这只是比例如果CPU空闲它们都可以使用全部CPU资源。2. 设置内存限制Memory Limit内存子系统可以设置硬限制超过此限制组内的进程会被OOM Killer终止。我们为每个组设置上限。# 设置web组内存硬限制为4GB echo 4G | sudo tee /sys/fs/cgroup/cpu_and_mem/web/memory.limit_in_bytes # 设置backend组内存硬限制为2GB echo 2G | sudo tee /sys/fs/cgroup/cpu_and_mem/backend/memory.limit_in_bytes # 设置batch组内存硬限制为1GB echo 1G | sudo tee /sys/fs/cgroup/cpu_and_mem/batch/memory.limit_in_bytes # 可选设置内存Swap限制通常设置为内存限制的1.5-2倍或与内存相同以禁用Swap echo 6G | sudo tee /sys/fs/cgroup/cpu_and_mem/web/memory.memsw.limit_in_bytes重要提示memory.limit_in_bytes的值不能小于当前已使用的内存量否则设置会失败。建议先设置一个较大的值再将进程移入然后逐步调低至目标值。另外修改内存限制是一个“昂贵”的操作可能会触发内核回收内存在高负载生产环境需谨慎进行。3.3 将进程移入控制组配置好控制组后下一步就是将运行的进程放进去。假设我们Nginx主进程的PID是1234一个Java后端服务的PID是5678一个批处理脚本的PID是9012。# 将Nginx主进程及其所有线程移入web组 echo 1234 | sudo tee /sys/fs/cgroup/cpu_and_mem/web/cgroup.procs # 将Java服务移入backend组 echo 5678 | sudo tee /sys/fs/cgroup/cpu_and_mem/backend/cgroup.procs # 将批处理脚本移入batch组 echo 9012 | sudo tee /sys/fs/cgroup/cpu_and_mem/batch/cgroup.procs验证进程所属控制组# 查看进程的cgroup信息 cat /proc/1234/cgroup # 输出会包含类似行5:cpu,cpuacct,memory:/web表明它在cpu,cpuacct,memory子系统下的/web组中。你也可以直接查看控制组内的进程列表cat /sys/fs/cgroup/cpu_and_mem/web/cgroup.procs3.4 使用cpuset子系统进行CPU绑定除了按份额分配我们还可以使用cpuset子系统将进程严格绑定到特定的CPU核心上这对于减少缓存失效、提高性能确定性非常有用。这需要挂载一个独立的cpuset层级或者将其加入到之前的层级中但为了清晰我们单独挂载。# 创建并挂载cpuset层级 sudo mkdir -p /sys/fs/cgroup/cpuset sudo mount -t cgroup -o cpuset cpuset /sys/fs/cgroup/cpuset # 进入该层级并创建子控制组 cd /sys/fs/cgroup/cpuset sudo mkdir web_cpuset backend_cpuset # 必须为每个cpuset控制组分配可用的CPU和内存节点 # 假设我们将CPU 0-3分配给webCPU 4-7分配给backend echo 0-3 | sudo tee /sys/fs/cgroup/cpuset/web_cpuset/cpuset.cpus echo 0 | sudo tee /sys/fs/cgroup/cpuset/web_cpuset/cpuset.mems # 通常NUMA节点0 echo 4-7 | sudo tee /sys/fs/cgroup/cpuset/backend_cpuset/cpuset.cpus echo 0 | sudo tee /sys/fs/cgroup/cpuset/backend_cpuset/cpuset.mems # 将进程移入对应的cpuset组 echo 1234 | sudo tee /sys/fs/cgroup/cpuset/web_cpuset/tasks echo 5678 | sudo tee /sys/fs/cgroup/cpuset/backend_cpuset/tasks现在Nginx进程只会运行在CPU 0-3上Java服务只会运行在CPU 4-7上实现了物理隔离。4. 内核实现机制深度探秘理解了用户空间的接口我们再来看看内核是如何实现这一切的。这对于排查复杂问题、理解性能开销至关重要。4.1 关键数据结构css_set与cgroup_subsys_state内核为每个任务task_struct维护了一个css_set子系统状态集合的引用计数指针。css_set内部包含一组指向cgroup_subsys_state对象的指针每个指针对应一个已注册的子系统。cgroup_subsys_state是子系统状态的基类每个具体的子系统如mem_cgroup都会内嵌这个结构体并扩展自己的数据。这种设计的精妙之处在于解耦。任务并不直接知道自己属于哪个cgroup而是通过css_set间接关联到各个子系统的状态。css_set可以被多个任务共享只要它们在各层级中的位置完全相同。当移动一个任务时内核会查找或创建一个新的css_set使其指向目标控制组在各个子系统中的状态对象。这种共享机制减少了内存开销因为具有相同资源约束的任务组可以共享同一个css_set。4.2 层级与子系统的绑定关系一个层级cgroup_root在挂载时通过mount -o选项指定一组子系统。内核会检查这组子系统是否已经被其他层级占用。核心规则是一个子系统在同一时间只能附加到一个活跃的层级上。这是Cgroups v1架构的一个关键限制。如果你想用cpu子系统控制CPU份额又用memory子系统控制内存并且希望它们在不同的树状结构中进行分类比如一个按部门分一个按应用类型分在v1中这是不可能的除非你创建两个独立的层级每个层级只绑定一个子系统。这也正是前文提到的“多层级”设计的用武之地。4.3 关键钩子与生命周期回调Cgroups在内核的关键路径上插入了轻量级的钩子以实现对任务的控制fork当任务调用fork()创建子进程时子进程会继承父进程的css_set即自动加入父进程所在的所有控组。exit当任务退出时内核会将其从所属的css_set的任务列表中移除。如果该任务是其所在控制组的最后一个任务且notify_on_release被设置则会触发释放代理。attach/detach当通过向tasks或cgroup.procs文件写入PID来移动任务时内核会调用对应子系统的can_attach()、cancel_attach()和attach()回调函数。这给了子系统一个机会来检查移动是否合法例如目标cpuset是否有可用的CPU核心并在移动前后执行必要的状态迁移操作。子系统通过实现一个cgroup_subsys结构体来注册自己其中包含了一系列像css_alloc分配状态、css_free释放状态、can_attach、attach、fork、exit这样的回调函数指针。内核的Cgroups核心模块负责管理层级树和任务关联而具体的资源控制逻辑则下沉到各个子系统中。5. 生产环境常见问题与排查实录在实际使用中尤其是与容器运行时如Docker结合时会遇到一些典型问题。下面是我踩过的一些坑和总结的排查思路。5.1 问题一内存限制不生效容器仍被宿主机OOM Killer干掉现象通过docker run -m 512m限制了容器内存但在系统内存紧张时容器内的进程有时仍会被宿主机级的OOM Killer终止。根因分析Docker默认设置的是memory.limit_in_bytes内存限制和memory.memsw.limit_in_bytes内存Swap限制。如果宿主机启用了Swap且memory.memsw.limit_in_bytes设置得足够大或等于默认的-1即无限制那么当容器内存用量超过memory.limit_in_bytes时它会开始使用Swap而不会触发容器内的OOM。只有当物理内存Swap的总用量也超过memory.memsw.limit_in_bytes时才会触发容器内OOM。然而宿主机全局内存耗尽时内核的全局OOM Killer会介入它可能选择杀死一个它认为“开销大”的进程而这个进程可能在容器内。解决方案与排查步骤确认容器内存限制进入容器的Cgroups目录通常在/sys/fs/cgroup/memory/docker/容器ID/查看。# 找到容器ID对应的cgroup路径 CONTAINER_IDyour_container_id CGROUP_PATH$(find /sys/fs/cgroup/memory -name *$CONTAINER_ID* -type d) cat $CGROUP_PATH/memory.limit_in_bytes cat $CGROUP_PATH/memory.memsw.limit_in_bytes禁用或限制Swap使用对于需要严格内存隔离的场景可以考虑完全禁用Swapswapoff -a或者将memory.memsw.limit_in_bytes设置为与memory.limit_in_bytes相同的值这样一旦达到内存限制就会立即触发容器内OOM而不是使用Swap。echo 536870912 | sudo tee $CGROUP_PATH/memory.memsw.limit_in_bytes # 512MB调整宿主机OOM Killer优先级通过/proc/pid/oom_score_adj可以调整进程的OOM得分。Docker会自动为容器内的进程设置一个偏移量。在极端情况下可以调整宿主机内核参数vm.overcommit_memory和vm.panic_on_oom但这会影响整个系统需谨慎。5.2 问题二CPU份额shares在CPU空闲时为何“失效”现象为两个容器分别设置了cpu.shares为1024和512预期是2:1的CPU比例。但当系统CPU空闲时观察发现两个容器都能跑满各自的CPU核心比例并不是2:1。理解误区纠正cpu.shares不是硬性限制而是权重。它只在CPU资源发生竞争时生效。当系统有充足的CPU空闲资源时每个控制组内的进程都可以使用尽可能多的CPU不会受到份额的限制。份额的作用是定义当所有控制组都想同时使用CPU时它们能分到的时间片比例。这是一个“弹性配额”而非“上限”。如果需要设置硬性上限应该使用cpu.cfs_quota_us和cpu.cfs_period_us来定义周期内的绝对时间上限。验证竞争状态下的比例# 在容器A和容器B中同时启动高强度CPU测试如stress -c 1 # 使用top或htop观察两个stress进程的CPU占用率。 # 当系统CPU饱和时例如只有1个核心两个进程竞争它们的CPU时间比例应该趋近于2:1。5.3 问题三多线程应用移入控制组后部分线程“逃逸”现象将一个多线程应用如Java、Gunicorn的主进程PID写入控制组的tasks文件后通过cat tasks查看发现只有少数几个线程在里面。原因与解决方案正如前文强调的tasks文件操作的是线程ID。向tasks写入主进程PID只会移动该PID对应的那个特定线程通常是主线程。应用的工作线程拥有不同的线程ID它们不会被移动。正确做法始终使用cgroup.procs文件来移动进程。向cgroup.procs写入一个PID内核会找到该PID所属的整个线程组即进程并将其所有线程移动到目标控制组。# 错误做法只移动主线程 echo $JAVA_PID /sys/fs/cgroup/cpu/mygroup/tasks # 正确做法移动整个Java进程所有线程 echo $JAVA_PID /sys/fs/cgroup/cpu/mygroup/cgroup.procs移动后可以通过cat /sys/fs/cgroup/cpu/mygroup/tasks | wc -l来验证线程数量是否与进程的线程数匹配可通过ps -T -p $JAVA_PID | wc -l查看。5.4 问题四Cgroups v1与v2的混淆与兼容性现象在新版系统如Ubuntu 22.04, RHEL 8上/sys/fs/cgroup下的目录结构变了找不到cpu、memory这样的单独目录而是出现了cgroup.controllers、cgroup.subtree_control等文件。背景这是遇到了Cgroups v2。从Linux 4.5开始内核引入了完全重写的Cgroups v2旨在解决v1的一些设计缺陷如多层级绑定复杂、控制器行为不一致等。v2的核心变化是单一层级树所有控制器都挂载在同一棵树下并且具有更严格的一致性保证。如何应对识别版本检查/sys/fs/cgroup的挂载点。如果存在cgroup2的挂载且/sys/fs/cgroup本身就是一个cgroup2文件系统那么系统可能运行在纯v2或混合模式。mount -t cgroup2 cat /proc/filesystems | grep cgroup2操作差异v2中控制器需要从父组通过cgroup.subtree_control文件显式向下委派。资源限制文件路径变了例如内存限制是memory.max。CPU控制使用cpu.weight代替cpu.shares且权重值范围不同。兼容性许多发行版为了平滑过渡采用了混合模式同时挂载v1和v2。Docker等运行时通常仍默认使用v1。如果遇到问题可以检查容器运行时的配置或者通过内核引导参数systemd.unified_cgroup_hierarchy0来强制使用v1如果使用systemd。排查表Cgroups v1 vs v2 核心操作对比功能Cgroups v1Cgroups v2说明层级多棵独立的树每棵树可绑定不同控制器单一树所有控制器在同一树下v2简化了模型避免了控制器冲突CPU限制cpu.shares(权重),cpu.cfs_quota_us(配额)cpu.weight(权重),cpu.max(配额)v2的cpu.weight范围是1-10000内存限制memory.limit_in_bytesmemory.max功能类似接口简化控制器启用挂载时通过-o选项指定通过根组的cgroup.subtree_control文件写入cpu memory等来向下委派v2需要显式授权子组使用控制器进程移动向tasks或cgroup.procs写入PID向cgroup.procs写入PIDv2中tasks文件已移除统一用cgroup.procs关键文件tasks,cgroup.procs,[subsystem].*cgroup.procs,cgroup.controllers,cgroup.subtree_control,[controller].*v2引入了新的管理文件6. 高级应用与性能考量掌握了基础操作和问题排查后我们可以看看一些更深入的应用场景和性能影响。6.1 与systemd集成服务级别的资源管理现代Linux发行版广泛使用systemd作为init系统。systemd原生集成了Cgroups每个服务单元service、scope、slice都对应一个控制组。这为管理服务资源提供了极大便利。查看服务Cgroups# 查看某个服务如nginx的Cgroup路径 systemctl status nginx | grep CGroup # 输出CGroup: /system.slice/nginx.service # 直接通过systemd设置服务资源限制在服务单元文件中 [Service] CPUQuota50% # 限制最多使用50%的单个CPU核心CFS配额 MemoryMax512M # 设置内存硬限制 MemorySwapMax1G # 设置内存Swap限制 CPUShares1024 # 设置CPU权重 AllowedCPUs0-2 # 允许使用的CPU核心cpuset通过systemd-run可以临时创建一个带有资源限制的作用域来运行命令sudo systemd-run --scope -p CPUQuota20% -p MemoryMax200M stress --cpu 1 --vm 1 --vm-bytes 100M这种方式比直接操作/sys/fs/cgroup更友好也更容易集成到自动化配置管理中。6.2 性能开销分析与监控启用Cgroups本身会带来轻微的性能开销主要来自统计开销子系统如cpuacct,memory需要统计资源使用量这会增加一些计数操作。调度决策开销cpu和cpuset子系统会影响内核调度器的决策路径。内存控制开销memory子系统在分配和回收页面时需要检查限制并维护大量统计数据结构。如何监控资源使用率直接读取控制组内的统计文件如cpuacct.usage纳秒级CPU时间、memory.usage_in_bytes内存使用量。控制组压力Linux内核提供了cgroup pressure stall information (PSI)可以监控资源CPU、内存、IO短缺导致的延迟。查看文件如memory.pressure、cpu.pressure。性能剖析在极端性能敏感的场景可以使用perf工具来剖析内核中Cgroups相关函数如cgroup_attach_task的耗时。对于绝大多数应用场景Cgroups带来的开销是微不足道的其带来的资源隔离和稳定性收益远远超过开销。只有在极端的、对延迟极其敏感的实时系统中才需要仔细评估。6.3 在嵌入式平台如NXP QorIQ上的实践要点在资源受限的嵌入式环境如基于NXP QorIQ LS1046A的工控或网络设备中使用Cgroups有一些特殊考量内核配置确保内核编译时启用了所需的Cgroups子系统CONFIG_CGROUPSy以及具体的CONFIG_CGROUP_CPU,CONFIG_CGROUP_MEMORY等。在Yocto或Buildroot构建时需要仔细检查内核配置片段。内存限制与Swap嵌入式设备通常没有Swap。这意味着memory.limit_in_bytes是绝对的硬限制一旦触发OOM进程会立即被终止。需要更精确地评估应用的内存需求并设置合理的缓冲余量。cpuset的运用在异构多核处理器如ARM big.LITTLE架构或一些多核网络处理器上cpuset非常有用。可以将关键的网络处理线程绑定到性能核将后台管理任务绑定到能效核优化能效比。根文件系统嵌入式系统根文件系统可能只读。Cgroups的虚拟文件系统通常挂载在/sys/fs/cgroup这是一个tmpfs不影响根文件系统。但自定义的release_agent脚本需要确保存在于可执行路径中。启动顺序在系统启动早期需要挂载Cgroups文件系统。这通常由initramfs或早期的init脚本完成。使用systemd的系统会自动处理。在自定义的嵌入式init系统中需要确保在需要控制资源的守护进程启动之前完成Cgroups的挂载和初始控制组的配置。Cgroups作为Linux内核的基石机制之一其设计思想深刻影响了后来的容器技术。从最初简单的进程分组到如今支撑起整个云原生生态的资源模型理解它不仅能让你更好地运维容器更能让你洞悉Linux系统资源管理的底层逻辑。当你下次在Kubernetes中定义resources.requests/limits或者在Docker中设置--cpus、--memory时你会知道这一切的魔法都始于/sys/fs/cgroup目录下的那些看似普通的文件。