🏗️cgroup 和 namespace 深度详解
📚核心概念对比
| 特性 | cgroup (Control Groups) | namespace (命名空间) |
|---|
| 目的 | 资源限制和分配 | 隔离和虚拟化系统资源视图 |
| 主要功能 | CPU、内存、I/O 等资源限制 | PID、网络、挂载点等隔离 |
| 工作层级 | 树状层级结构 | 平级或嵌套结构 |
| 配置方式 | 通过文件系统接口 | 通过系统调用和文件系统 |
| 内核版本 | 2.6.24+ | 2.4.19+(逐步完善) |
🔧cgroup 详解
1.cgroup 架构
cgroup v1 (传统架构) ├── 多个独立的子系统 (subsystems) │ ├── blkio - 块设备I/O限制 │ ├── cpu - CPU时间分配 │ ├── cpuacct - CPU使用统计 │ ├── cpuset - CPU和内存节点绑定 │ ├── devices - 设备访问控制 │ ├── freezer - 挂起/恢复进程 │ ├── memory - 内存限制 │ ├── net_cls - 网络流量分类 │ └── net_prio - 网络优先级 └── 每个子系统独立挂载 cgroup v2 (统一层级架构) └── 所有控制器统一管理 ├── io - I/O控制 ├── memory - 内存控制 ├── pids - 进程数限制 └── cpu - CPU控制
2.cgroup v1 详细配置
安装和挂载
# 查看已安装的cgroup支持yuminstalllibcgroup libcgroup-tools# 查看可用的cgroup子系统cat/proc/cgroups# 挂载cgroup v1mount-t cgroup -o cpu,cpuacct cgroup /sys/fs/cgroup/cpu,cpuacctmount-t cgroup -o memory cgroup /sys/fs/cgroup/memory
内存子系统配置详解
# 创建cgroupcgcreate -g memory:/myapp# 查看内存控制文件ls-la /sys/fs/cgroup/memory/myapp/# 重要文件说明:# memory.limit_in_bytes - 内存硬限制(超过会触发OOM)# memory.soft_limit_in_bytes - 内存软限制(超过会被回收)# memory.usage_in_bytes - 当前内存使用# memory.max_usage_in_bytes - 历史最大使用# memory.oom_control - OOM控制参数# memory.stat - 详细内存统计# memory.swappiness - swap使用倾向# memory.use_hierarchy - 是否启用层级继承# 设置内存限制(1GB)echo1073741824>/sys/fs/cgroup/memory/myapp/memory.limit_in_bytes# 设置内存+swap限制echo2147483648>/sys/fs/cgroup/memory/myapp/memory.memsw.limit_in_bytes# 禁用swap使用echo0>/sys/fs/cgroup/memory/myapp/memory.swappiness# 查看内存统计(重要指标)cat/sys/fs/cgroup/memory/myapp/memory.stat# cache - 页面缓存# rss - 匿名页# mapped_file - 文件映射页# pgpgin - 换入页面数# pgpgout - 换出页面数# oom_kill - OOM杀死次数
CPU 子系统配置
# 创建CPU cgroupcgcreate -g cpu:/cpulimited# CPU配额配置(CFS调度器)# cpu.cfs_period_us - 周期长度(默认100ms)# cpu.cfs_quota_us - 配额(限制使用的CPU时间)# cpu.shares - CPU份额(相对权重)# 限制使用0.5个CPU核心echo100000>/sys/fs/cgroup/cpu/cpulimited/cpu.cfs_period_usecho50000>/sys/fs/cgroup/cpu/cpulimited/cpu.cfs_quota_us# 设置CPU份额(默认1024)echo512>/sys/fs/cgroup/cpu/cpulimited/cpu.shares# CPU绑定(cpuset子系统)cgcreate -g cpuset:/cpu_boundecho"0-1">/sys/fs/cgroup/cpuset/cpu_bound/cpuset.cpus# 只能使用CPU 0和1echo"0">/sys/fs/cgroup/cpuset/cpu_bound/cpuset.mems# 只能使用NUMA节点0
I/O 子系统配置
# 创建blkio cgroupcgcreate -g blkio:/iolimited# 设置读取限制(8:0是设备号,可以通过lsblk查看)echo"8:0 1048576">/sys/fs/cgroup/blkio/iolimited/blkio.throttle.read_bps_deviceecho"8:0 1048576">/sys/fs/cgroup/blkio/iolimited/blkio.throttle.write_bps_device# 设置权重(默认500)echo100>/sys/fs/cgroup/blkio/iolimited/blkio.weight
进程管理
# 将进程加入cgroupcgclassify -g cpu,memory:/myapp<PID># 在cgroup中启动新进程cgexec -g cpu,memory:/myapp /usr/bin/myapp# 查看cgroup中的进程cat/sys/fs/cgroup/memory/myapp/cgroup.procs# 冻结cgroup中的所有进程echoFROZEN>/sys/fs/cgroup/freezer/myapp/freezer.state# 解冻echoTHAWED>/sys/fs/cgroup/freezer/myapp/freezer.state
3.cgroup v2 配置
启用cgroup v2
# 在内核启动参数中添加grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=1"# 重启后验证mount|grepcgroup2# 查看cgroup v2结构ls-la /sys/fs/cgroup/
统一层级配置
# 创建子cgroupmkdir/sys/fs/cgroup/myapp# 启用控制器echo"+memory +cpu +io +pids">/sys/fs/cgroup/cgroup.subtree_control# 设置内存限制echo"1G">/sys/fs/cgroup/myapp/memory.maxecho"500M">/sys/fs/cgroup/myapp/memory.high# 软限制# 设置CPU限制echo"50000 100000">/sys/fs/cgroup/myapp/cpu.max# quota period# 设置进程数限制echo100>/sys/fs/cgroup/myapp/pids.max
🌐namespace 详解
1.namespace 类型和功能
| namespace 类型 | 系统调用 | 隔离内容 | 引入内核版本 |
|---|
| Mount (mnt) | clone(), unshare() | 文件系统挂载点 | 2.4.19 |
| UTS | clone(), unshare() | 主机名和域名 | 2.6.19 |
| IPC | clone(), unshare() | System V IPC, POSIX消息队列 | 2.6.19 |
| PID | clone(), unshare() | 进程ID空间 | 2.6.24 |
| Network (net) | clone(), unshare() | 网络设备、协议栈、端口等 | 2.6.29 |
| User | clone(), unshare() | 用户和组ID | 3.8 |
| Cgroup | clone(), unshare() | cgroup根目录 | 4.6 |
| Time | clone(), unshare() | 系统时钟 | 5.6 |
2.namespace API 操作
查看 namespace
# 查看进程的所有namespacels-la /proc/$$/ns/# 查看namespace的inode号(唯一标识)ls-la /proc/$$/ns/|awk'{print $1, $9, $11}'# 查看当前进程的namespace信息cat/proc/self/status|grep-i ns# 查看系统所有namespacesudolsnssudolsns -t pid# 按类型过滤
创建 namespace
// C语言创建namespace示例#define_GNU_SOURCE#include<sched.h>#include<stdio.h>#include<unistd.h>intmain(){// 创建新的UTS namespaceif(unshare(CLONE_NEWUTS)==-1){perror("unshare");return1;}// 在新namespace中设置主机名sethostname("newnamespace",12);// 验证charhostname[256];gethostname(hostname,sizeof(hostname));printf("New hostname: %s\n",hostname);return0;}
命令行操作 namespace
# 使用unshare创建新namespace# 创建UTS namespace并修改主机名sudounshare --utsbashhostnamenewhost# 创建PID namespace(第一个进程PID为1)sudounshare --pid --fork --mount-procbashpsaux# 只能看到namespace内的进程# 创建network namespacesudounshare --netbashiplinkshow# 只有lo接口# 创建完整的容器环境sudounshare --uts --ipc --pid --net --mount --forkbash
3.网络 namespace 深度解析
# 创建网络namespaceipnetnsaddnetns1ipnetnsaddnetns2# 查看所有网络namespaceipnetns list# 在namespace中执行命令ipnetnsexecnetns1iplinkshowipnetnsexecnetns1ipaddradd192.168.1.1/24 dev loipnetnsexecnetns1iplinksetlo up# 创建veth对连接两个namespaceiplinkaddveth1typeveth peer name veth2# 将veth接口分配到namespaceiplinksetveth1 netns netns1iplinksetveth2 netns netns2# 配置IP地址ipnetnsexecnetns1ipaddradd10.0.0.1/24 dev veth1ipnetnsexecnetns2ipaddradd10.0.0.2/24 dev veth2# 启用接口ipnetnsexecnetns1iplinksetveth1 upipnetnsexecnetns2iplinksetveth2 up# 测试连通性ipnetnsexecnetns1ping10.0.0.2
4.PID namespace 隔离机制
# 创建PID namespacesudounshare --pid --forkbash# 查看当前进程在各级namespace中的PIDecho"在PID namespace中的PID:$$"echo"在父namespace中的PID:$(cat/proc/self/status|grepNSpid|awk'{print $3}')"# 查看/proc文件系统(需要重新挂载)mount-t proc proc /procpsaux# 现在只能看到namespace内的进程# 查看进程在不同namespace中的映射cat/proc/<PID>/status|grepNSpid# NSpid: 在各级namespace中的PID列表
5.User namespace 权限隔离
# 创建user namespaceunshare --user --map-root-userbash# 查看用户映射cat/proc/self/uid_mapcat/proc/self/gid_map# 从外部设置用户映射# 准备映射文件echo"0 1000 1">/tmp/uid_mapecho"0 1000 1">/tmp/gid_map# 创建带用户映射的namespaceunshare --user --map-root-user --map-auto --map-users=1000,1000,1bash
6.Mount namespace 文件系统隔离
# 创建mount namespaceunshare --mountbash# 查看当前挂载点mount|wc-l# 创建私有挂载(不影响其他namespace)mount--make-private /tmp# 绑定挂载mount--bind /source /destination# 创建共享挂载(在多个namespace间共享)mount--make-shared /tmp# 查看挂载传播类型findmnt -o PROPAGATION /mountpoint
🎯cgroup + namespace 实战:创建简单容器
1.手动创建容器环境
#!/bin/bash# create-mini-container.sh# 创建唯一标识CONTAINER_ID="container_$(date+%s)"CGROUP_PATH="/sys/fs/cgroup/memory/$CONTAINER_ID"MOUNT_PATH="/tmp/$CONTAINER_ID"# 1. 创建cgroupmkdir-p$CGROUP_PATHecho100000000>$CGROUP_PATH/memory.limit_in_bytesecho0>$CGROUP_PATH/memory.swappiness# 2. 创建目录结构mkdir-p$MOUNT_PATH/{bin,lib,lib64,proc,dev,etc}# 3. 复制必要的二进制文件和库cp/bin/bash$MOUNT_PATH/bin/cp/bin/ls$MOUNT_PATH/bin/ ldd /bin/bash|awk'{print $3}'|xargs-I{}cp{}$MOUNT_PATH/lib/2>/dev/null# 4. 使用unshare创建容器unshare --uts --ipc --pid --net --mount --fork\cgexec -g memory:$CONTAINER_ID\bash-c" # 设置主机名 hostname$CONTAINER_ID# 挂载proc mount -t proc proc /proc # 切换到容器根目录 cd$MOUNT_PATHpivot_root . . # 启动shell exec /bin/bash "
2.使用nsenter进入容器namespace
# 查找容器的PIDCONTAINER_PID=$(psaux|grep"hostname$CONTAINER_ID"|grep-vgrep|awk'{print $2}')# 进入容器的所有namespacensenter -t$CONTAINER_PID-m -u -i -n -pbash# 或只进入特定namespacensenter -t$CONTAINER_PID-nbash# 只进入网络namespace
🔍高级调试和监控
1.namespace 调试工具
# 使用strace跟踪namespace操作strace-etrace=clone,unshare,setns unshare --utsbash# 查看namespace的变化sudoipnetns monitorsudonsmonitor# 需要自己编译的工具
2.cgroup 事件监控
# 监控cgroup内存事件inotifywait -m /sys/fs/cgroup/memory/myapp/memory.oom_control# 使用cgroup事件通知API# 在cgroup.event_control中设置监控echo"memory.usage_in_bytes memory.oom_control">\/sys/fs/cgroup/memory/myapp/cgroup.event_control
3.性能分析工具
# 使用systemd-cgtop监控cgroup资源使用systemd-cgtop# 使用bpftrace分析cgroup操作bpftrace -e'tracepoint:cgroup:cgroup_attach_task { printf("%s -> %s\n", comm, args->dst_path); }'# 使用perf分析namespace开销perf record -e namespaces:* -asleep10
⚙️最佳实践和优化
1.cgroup 最佳实践
# 1. 合理的内存限制# 设置软限制(memory.high)早于硬限制(memory.max)# 预留10-20%的缓冲空间# 2. CPU分配策略# 对有波动的服务使用cpu.weight# 对需要保证的服务使用cpu.max# 3. I/O优先级# 数据库服务设置更高的I/O权重# 批处理任务可以设置较低的权重# 4. 层级设计# 按业务划分顶层cgroup# 按服务类型划分子cgroup
2.namespace 最佳实践
# 1. 最小权限原则# 只创建必要的namespace# 避免给予不必要的权限# 2. 用户映射安全# 使用非root用户映射# 限制映射范围# 3. 网络隔离# 为每个容器使用独立的网络namespace# 使用桥接或macvlan进行网络连接# 4. 挂载传播# 对敏感目录使用私有挂载# 对共享数据使用共享挂载
3.生产环境配置示例
# /etc/cgconfig.conf group database { memory { memory.limit_in_bytes = 4G; memory.memsw.limit_in_bytes = 8G; memory.swappiness = 10; } cpu { cpu.shares = 1024; } blkio { blkio.weight = 800; } } group webserver { memory { memory.limit_in_bytes = 2G; memory.swappiness = 30; } cpu { cpu.shares = 512; } }
📚学习资源和扩展
1.内核文档
# 查看内核cgroup文档cat/usr/share/doc/kernel-doc-*/Documentation/cgroup-v1/*.txtcat/usr/share/doc/kernel-doc-*/Documentation/cgroup-v2.txt# 查看namespace文档find/usr/share/doc/kernel-doc-*/Documentation -name"*namespace*"
2.相关工具
# 容器运行时dockerruntiem - 实现OCI标准 crun - 用C写的轻量级运行时 youki - Rust写的运行时# 监控工具cgmon - cgroup监控 nsenter - namespace进入工具 cntr - 容器调试工具