u1timate
发布于 2025-12-14 / 4 阅读
0
0

1、Cgroups与Namespaces

0x01 Namespaces:隔离的魔法

Linux Namespaces 提供了一种将系统资源(如进程ID、网络接口、挂载点等)进行抽象和隔离的机制。这意味着在不同的命名空间中,进程会看到不同的系统资源视图,从而实现了隔离。

常见的Namespaces类型及其隔离内容:

  • PID Namespace (进程ID命名空间): 隔离进程ID。在新的PID Namespace中,进程会从PID 1开始编号,并且只能看到该Namespace内的进程。外部Namespace的进程看不到内部进程的PID,反之亦然。这是容器内进程拥有独立进程树的关键。

  • Mount Namespace (挂载命名空间): 隔离文件系统挂载点。每个Mount Namespace都有一套独立的挂载点列表。这意味着在一个Namespace内挂载或卸载文件系统,不会影响到其他Namespace。这是容器拥有独立根文件系统的基础。

  • Network Namespace (网络命名空间): 隔离网络资源,包括网络接口、IP地址、路由表、防火墙规则等。每个Network Namespace都有自己独立的网络栈。这是容器拥有独立IP地址和端口空间的基石。

  • UTS Namespace (UNIX Timesharing System命名空间): 隔离主机名和域名。允许每个Namespace拥有独立的主机名和域名。

  • IPC Namespace (Interprocess Communication命名空间): 隔离进程间通信(System V IPC和POSIX消息队列)。

  • User Namespace (用户命名空间): 隔离用户和组ID。允许一个Namespace内的用户ID映射到其外部Namespace的不同用户ID,增强了安全性,例如,容器内的root用户在宿主机上可以是普通用户。

Namespaces的工作原理:

当一个进程在新的Namespace中启动时,它会获得该Namespace的资源视图。例如,当使用unshare命令创建一个新的PID Namespace时,新的shell进程及其子进程将拥有独立的PID空间。

# 查看当前shell pid
echo $$

# 创建一个新的PID Namespace并启动一个子shell
unshare --pid --fork --mount-proc bash

# 查看隔离后的pid 和进程树
root@dev-1-35:/home/u1timate# cho $$
1
root@dev-1-35:/home/u1timate# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   7636  4284 pts/1    S    10:39   0:00 bash
root           7  0.0  0.0  10072  1644 pts/1    R+   10:40   0:00 ps aux

# 退出新的子shell后,宿主机环境的ps aux又会恢复正常
exit

unshare 命令解释

  • --fork:在新的 Namespace 中启动一个子进程。

  • --mount-proc:挂载一个新的 /proc,否则你看到的还是宿主机的进程表。

0x02 Cgroups:资源的管家

Linux Cgroups (Control Groups) 提供了一种机制来组织进程,并对其资源使用进行限制、审计和优先级管理。简单来说,Namespaces负责“隔离”,而Cgroups则负责“限制”。

Cgroups的主要功能:

  • 资源限制 (Resource Limiting): 限制进程组可以使用的资源量,例如CPU、内存、I/O带宽等。

  • 优先级 (Prioritization): 调整进程组对资源的访问优先级。

  • 审计 (Accounting): 监控进程组的资源使用情况。

  • 控制 (Control): 挂起或恢复进程组。

常见的Cgroup子系统(Subsystems/Controllers):

  • cpu: 控制CPU资源访问,例如CPU时间片的分配 (cpu.cfs_quota_us, cpu.cfs_period_us) 和CPU亲和性。

  • memory: 限制内存使用,包括物理内存和交换空间 (memory.limit_in_bytes, memory.memsw.limit_in_bytes)。

  • blkio: 限制块设备(如硬盘)的I/O访问。

  • pids: 限制一个Cgroup内可以创建的进程数量。

  • net_cls / net_prio: 标记网络数据包,以便通过流量控制器(Traffic Controller, TC)进行优先级和带宽管理。

Cgroups的工作原理:

  • cgroup v1 的管理方式

    • /sys/fs/cgroup/ 下,每个子系统(CPU、内存、blkio 等)都有独立的层级结构。

    • 你可以通过在对应子系统目录下创建子目录来建立新的 cgroup。

    • 配置资源限制时,向该目录下的控制文件写入值,例如 memory.limit_in_bytes。

    • 将进程加入 cgroup 时,把 PID 写入 tasks 文件。

  • cgroup v2 的变化

    • v2 统一了层级结构:所有控制器共享一个单一的层级,而不是每个子系统独立。

    • 进程管理文件也有所不同:使用 cgroup.procs 文件而不是 tasks 文件来添加进程。

    • 控制文件的命名和语义也发生了变化,例如 CPU 使用 cpu.weight 而不是 v1 的 cpu.shares。

    • 在大多数现代发行版(如 RHEL 9、Ubuntu 22.04),默认启用的是 cgroup v2。

检查cgroups版本

root@dev-1-35:/home/u1timate# cat /proc/filesystems | grep cgroup
nodev   cgroup
nodev   cgroup2

# 查看实际正在使用的版本
root@dev-1-35:/home/u1timate# mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)  
  • 输出包含 cgroup → v1 支持。

  • 输出包含 cgroup2 → v2 支持。

创建一个cgroup(以 v2 版本为例

# 安装stress工具
sudo apt-get install stress # Debian/Ubuntu

# 创建一个cgroup
mkdir /sys/fs/cgroup/test

# 设置限制
# 设置CPU配额:限制该组进程在100ms内最多使用50ms的CPU时间(即50% CPU)
echo "50000 100000" > /sys/fs/cgroup/test/cpu.max

cpu.max 文件定义了进程在该 cgroup 内可用的 CPU 时间配额。

  • 格式:quota period

    • quota:允许使用的时间(微秒)。

    • period:调度周期(微秒)。

test文件夹下结构:

结构解释:

通用 cgroup 文件

  • cgroup.controllers:当前层级可用的控制器列表(如 cpu、memory、io 等)。

  • cgroup.subtree_control:启用/禁用子 cgroup 的控制器。

  • cgroup.events:记录 cgroup 的事件(如是否被冻结)。

  • cgroup.freeze:冻结/解冻 cgroup 内的所有进程。

  • cgroup.kill:终止 cgroup 内的所有进程。

  • cgroup.max.depth:子 cgroup 最大嵌套深度。

  • cgroup.max.descendants:子 cgroup 最大数量。

  • cgroup.procs:该 cgroup 内所有进程的 PID。

  • cgroup.threads:该 cgroup 内所有线程的 TID。

  • cgroup.type:cgroup 类型(如 domain、thread)。

  • cgroup.stat:统计信息(如 nr_descendants、nr_dying_descendants)。

CPU 控制器

  • cpu.max:CPU 使用配额(quota/period),用于限制 CPU 百分比。

  • cpu.max.burst:允许的额外突发 CPU 时间。

  • cpu.weight:CPU 调度权重(相对值,默认 100)。

  • cpu.weight.nice:与 nice 值对应的权重。

  • cpu.idle:控制 CPU 空闲时的行为。

  • cpu.pressure:CPU 压力信息(延迟统计)。

  • cpu.stat:CPU 使用统计(如 user/system 时间)。

  • cpu.uclamp.min / cpu.uclamp.max:统一 CPU clamp,限制最小/最大 CPU 性能水平。

CPUSET 控制器

  • cpuset.cpus:允许使用的 CPU 集合。

  • cpuset.cpus.effective:实际可用的 CPU 集合(考虑父 cgroup 限制)。

  • cpuset.cpus.partition:CPU 分区设置。

  • cpuset.mems:允许使用的内存节点集合。

  • cpuset.mems.effective:实际可用的内存节点集合。

内存控制器

  • memory.current:当前内存使用量。

  • memory.max:最大内存限制。

  • memory.high:高水位限制,超过时触发回收。

  • memory.low:低水位保证,尽量保留。

  • memory.min:最小保证内存。

  • memory.swap.current:当前 swap 使用量。

  • memory.swap.max:最大 swap 限制。

  • memory.swap.high:swap 高水位限制。

  • memory.swap.events:swap 相关事件统计。

  • memory.events / memory.events.local:内存事件(如 OOM、high/low 触发)。

  • memory.pressure:内存压力信息。

  • memory.oom.group:是否作为 OOM 杀进程的整体。

  • memory.stat:详细内存统计(anon、file、slab 等)。

  • memory.numa_stat:按 NUMA 节点的内存使用统计。

IO 控制器

  • io.max:IO 限制(带宽/IOPS)。

  • io.weight:IO 权重(相对值)。

  • io.prio.class:IO 优先级类别。

  • io.stat:IO 使用统计。

  • io.pressure:IO 压力信息。

PIDs 控制器

  • pids.current:当前进程数。

  • pids.max:最大允许进程数。

  • pids.events:进程数相关事件(如达到上限)

运行进程测试

限制前:

# 使用cgexec将stress命令运行在test中
sudo cgexec -g cpu:test stress -c 8 -t 10s

限制后:

0x03 容器如何利用Cgroups和Namespaces

容器运行时(例如Docker、Containerd)在创建容器时,会执行以下核心步骤:

  1. 创建Namespaces: 为新容器进程创建一组新的Namespaces(通常是PID、Mount、Network、UTS、IPC等),使其与宿主机和其他容器隔离。

  2. 配置Cgroups: 为容器进程创建一个或多个Cgroups,并根据容器的资源配置(如CPU限制、内存限制)向这些Cgroup的控制文件写入相应的值。

  3. 启动进程: 在新创建的Namespaces和Cgroups环境中启动容器的第一个进程。

这样,容器内的进程就拥有了独立的运行环境,并且其资源使用也受到了严格的控制,从而实现了轻量级的、高性能的隔离。

0x04 cgroup v1

文件夹结构

/sys/fs/cgroup/
├── cpu/
│   ├── tasks
│   ├── cgroup.procs
│   ├── cpu.shares
│   ├── cpu.cfs_quota_us
│   └── cpu.cfs_period_us
├── memory/
│   ├── tasks
│   ├── memory.limit_in_bytes
│   ├── memory.usage_in_bytes
│   ├── memory.stat
│   └── memory.failcnt
├── blkio/
│   ├── tasks
│   ├── blkio.weight
│   ├── blkio.throttle.read_bps_device
│   └── blkio.throttle.write_bps_device
├── cpuset/
│   ├── tasks
│   ├── cpuset.cpus
│   ├── cpuset.mems
│   └── cpuset.memory_migrate
├── freezer/
│   ├── tasks
│   └── freezer.state
└── net_cls/
    ├── tasks
    └── net_cls.classid
  • 每个控制器(如 cpu, memory, blkio)是一个单独的目录

  • 每个目录下可以创建子 cgroup(子目录),用于隔离不同进程组

  • 每个子目录中包含:

    • tasks:用于写入进程 PID,把它加入该 cgroup

    • 控制器特有的配置文件(如 cpu.shares, memory.limit_in_bytes)。

  • 你可以通过 mkdir 创建子 cgroup,然后写入配置和 PID 来控制资源。

# 以CPU子系统为例,创建并配置一个Cgroup

# 1. 创建一个名为my_limited_cpu_group的Cgroup
sudo mkdir /sys/fs/cgroup/cpu/my_limited_cpu_group

# 2. 设置CPU配额:限制该组进程在100ms内最多使用50ms的CPU时间(即50% CPU)
sudo sh -c "echo 50000 > /sys/fs/cgroup/cpu/my_limited_cpu_group/cpu.cfs_quota_us"
sudo sh -c "echo 100000 > /sys/fs/cgroup/cpu/my_limited_cpu_group/cpu.cfs_period_us"

# 3. 将当前shell的PID添加到该Cgroup中 (或者使用cgexec)
# 获取当前shell的PID
# echo $$ 
# 将PID添加到Cgroup的tasks文件中
sudo sh -c "echo $(echo $$) > /sys/fs/cgroup/cpu/my_limited_cpu_group/tasks"

# 4. 运行一个CPU密集型任务(如stress工具),观察其CPU使用率
# 注意:直接运行stress可能需要将其PID移入Cgroup,或者使用cgexec

# 安装stress工具
sudo apt-get install stress # Debian/Ubuntu


# 使用cgexec将stress命令运行在my_limited_cpu_group中
sudo cgexec -g cpu:my_limited_cpu_group stress -c 1 -t 10s

# 观察CPU使用率,会发现stress进程的CPU使用被限制在50%左右
# top # 在另一个终端观察

# 5. 清理Cgroup
sudo rm -rf /sys/fs/cgroup/cpu/my_limited_cpu_group


评论