在第 3 章:进程管理中,理解了 task_struct 和进程的完整生命周期;在第 14 章:内核同步机制中,掌握了内核如何保护共享数据。现在,走进 Linux 内核中最深刻的一次抽象跃迁——如何让一组进程以为自己独占了整台机器。
这就是容器技术的内核根基:Namespace 提供资源视图隔离,Cgroup 提供资源使用限制。两者组合,构成了 Docker、Kubernetes 等容器生态的底层基石。理解它们,你才能真正理解”容器到底是什么”——它不是虚拟机,不是沙箱,而是内核对进程施加的一组视角限制和配额约束。
本章将从容器与虚拟机的本质区别出发,逐一剖析 7 种 Namespace 的隔离能力与内核实现,深入 Cgroup v1/v2 的架构演进与控制器机制,最终揭示容器运行时如何将两者编织为现代云原生的基石。
一、容器 vs 虚拟机:两种隔离哲学
1.1 虚拟机:硬件级隔离
虚拟机(Virtual Machine)通过 Hypervisor 在物理硬件之上模拟出完整的硬件环境,每个虚拟机运行独立的操作系统内核。这种隔离是硬件级别的——不同的内核实例、不同的系统调用表、不同的设备驱动。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐│ App A │ │ App B │ │ App C ││ Guest OS │ │ Guest OS │ │ Guest OS │├─────────────┤ ├─────────────┤ ├─────────────┤│ vCPU │ │ vCPU │ │ vCPU ││ vMem │ │ vMem │ │ vMem │└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └───────────────┼───────────────┘ Hypervisor ┌─────────┴─────────┐ │ Host Kernel │ └───────────────────┘ ┌───────────────────┐ │ Hardware │ └───────────────────┘1.2 容器:内核级隔离
容器则完全不同——所有容器共享同一个内核。容器本质上只是一组进程,内核通过 Namespace 让它们”看”到不同的资源视图,通过 Cgroup 限制它们能使用的资源量。没有硬件模拟,没有额外的内核实例,只有同一个内核中的不同视角和约束。
┌──────────┐ ┌──────────┐ ┌──────────┐│ App A │ │ App B │ │ App C ││ NS+Cgrp │ │ NS+Cgrp │ │ NS+Cgrp │└────┬─────┘ └────┬─────┘ └────┬─────┘ └─────────────┼─────────────┘ Shared Linux Kernel ┌─────────────┴─────────────┐ │ Namespaces + Cgroups │ └───────────────────────────┘ ┌───────────────────────────┐ │ Hardware │ └───────────────────────────┘1.3 核心差异对比
| 维度 | 虚拟机 | 容器 |
|---|---|---|
| 隔离级别 | 硬件级(独立内核) | 内核级(共享内核) |
| 启动速度 | 分钟级(需启动完整 OS) | 毫秒级(仅创建进程) |
| 资源开销 | GB 级(完整 OS 镜像) | MB 级(仅应用+依赖) |
| 隔离强度 | 强(硬件强制) | 弱(内核机制,共享内核漏洞影响全局) |
| 密度 | 低(通常数十个) | 高(可达数千个) |
| 技术基础 | VT-x/AMD-V 硬件虚拟化 | Namespace + Cgroup |
容器的”弱隔离”并非缺陷,而是设计权衡。共享内核意味着零虚拟化开销,但也意味着内核漏洞(如 Dirty COW)可以突破容器边界。这就是为什么安全敏感场景仍需虚拟机,而高密度部署场景偏好容器。
二、Linux Namespace:资源视图隔离
2.1 Namespace 的核心思想
Namespace 的设计哲学极其优雅:不创建新的资源,只改变进程能看到什么。它为进程提供了一种”视障”——每个 Namespace 中的进程都以为自己拥有系统的全部资源,而实际上它们只是看到了内核为它们定制的一份”视图”。
内核中每个 Namespace 类型对应 task_struct 中的一个指针:
// include/linux/sched.h 中的 nsproxy 结构struct task_struct { // ... struct nsproxy *nsproxy; // 指向进程的命名空间代理 // ...};
// kernel/nsproxy.cstruct nsproxy { atomic_t count; struct uts_namespace *uts_ns; // UTS: 主机名与域名 struct ipc_namespace *ipc_ns; // IPC: System V IPC 与 POSIX MQ struct mnt_namespace *mnt_ns; // Mount: 文件系统挂载点 struct pid_namespace *pid_ns_for_children; // PID: 进程号 struct net *net_ns; // Network: 网络栈 struct cgroup_namespace *cgroup_ns; // Cgroup: cgroup 根目录视图 struct user_namespace *user_ns; // User: 用户与组 ID};2.2 七种 Namespace 详解
Linux 内核目前实现了 7 种 Namespace,每种隔离一类系统资源:
Mount Namespace(文件系统隔离)
Mount Namespace 是 Linux 历史上第一个 Namespace(2002 年,Linux 2.4.19),它隔离的是进程看到的文件系统挂载点视图。不同 Mount Namespace 中的进程可以拥有完全不同的目录结构。
关键机制:Mount Namespace 使用传播类型(Shared Slaves)控制挂载事件如何在 Namespace 之间传播:
| 传播类型 | 行为 |
|---|---|
shared | 挂载/卸载事件传播到对等组中的其他 Namespace |
slave | 只接收来自主 Namespace 的事件,不反向传播 |
private | 不传播也不接收任何挂载事件 |
unbindable | 与 private 相同,且禁止被 bind mount |
PID Namespace(进程号隔离)
PID Namespace 让不同 Namespace 中的进程拥有独立的进程号空间。在子 PID Namespace 中,第一个进程的 PID 为 1,扮演 init 进程的角色。当 PID 1 退出时,该 Namespace 中的所有进程都被杀死。
// kernel/pid_namespace.c 中的核心结构struct pid_namespace { struct idr idr; // PID 分配器 unsigned int pid_allocated; struct task_struct *child_reaper; // PID 1 进程 struct kmem_cache *pid_cachep; unsigned int level; // 嵌套深度 struct pid_namespace *parent; // 父 Namespace // ...};PID Namespace 支持嵌套——子 Namespace 中的进程在父 Namespace 中可见,但 PID 号不同。一个进程在不同层级的 Namespace 中拥有不同的 PID:
全局 PID Namespace (level 0) └── PID 1 (systemd) └── 容器 PID Namespace (level 1) └── PID 1 (容器 init) └── PID 47 (容器内进程) → 在全局 Namespace 中 PID 为 12345Network Namespace(网络栈隔离)
Network Namespace 隔离了完整的网络协议栈——包括网络设备、IP 地址、路由表、端口号、iptables 规则、/proc/net 和 /sys/class/net 目录。每个 Network Namespace 拥有独立的 lo(loopback)设备,可以拥有独立的虚拟以太网设备(veth pair)。
这是容器网络实现的基石:Docker 的 bridge 网络模式、Kubernetes 的 Pod 网络、CNI 插件——它们都基于 Network Namespace 构建。
User Namespace(用户身份隔离)
User Namespace 是最强大的 Namespace 类型——它允许普通用户在子 User Namespace 中”成为 root”。具体来说,User Namespace 建立了 UID/GID 映射:子 Namespace 中的 UID 0(root)映射到父 Namespace 中的普通 UID。
struct user_namespace { struct uid_gid_map uid_map; // UID 映射表 struct uid_gid_map gid_map; // GID 映射表 struct uid_gid_map projid_map; // 项目配额映射 struct user_namespace *parent; // 父 Namespace int level; // 嵌套深度 kuid_t owner; // 创建者 UID // ...};User Namespace 的嵌套深度限制为 32 层(USER_NS_HIERARCHY_MAX)。正是 User Namespace 使得非特权用户也能创建容器——这是 rootless container 的技术基础。
UTS Namespace(主机名隔离)
UTS(Unix Time-sharing System)Namespace 隔离的是 hostname 和 domainname 两个系统标识符,对应 sethostname() 和 setdomainname() 系统调用。每个容器可以有自己的主机名,而不影响宿主机。
IPC Namespace(进程间通信隔离)
IPC Namespace 隔离了 System V IPC 对象(信号量、消息队列、共享内存段)和 POSIX 消息队列。不同 IPC Namespace 中的进程无法通过这些机制通信,确保了容器间 IPC 资源的隔离。
Cgroup Namespace(cgroup 视图隔离)
Cgroup Namespace(Linux 4.6)是最晚加入的 Namespace,它隔离的是进程看到的 cgroup 层级视图。在 Cgroup Namespace 中,进程的 cgroup 根目录被映射为 /,使得容器内的进程无法看到宿主机的完整 cgroup 层次结构。
2.3 /proc/[pid]/ns/:Namespace 的文件表示
每个进程的 Namespace 都在 /proc/[pid]/ns/ 目录下以符号链接的形式暴露:
$ ls -la /proc/self/ns/total 0lrwxrwxrwx 1 root root 0 cgroup -> 'cgroup:[4026531835]'lrwxrwxrwx 1 root root 0 ipc -> 'ipc:[4026531839]'lrwxrwxrwx 1 root root 0 mnt -> 'mnt:[4026531840]'lrwxrwxrwx 1 root root 0 net -> 'net:[4026531992]'lrwxrwxrwx 1 root root 0 pid -> 'pid:[4026531836]'lrwxrwxrwx 1 root root 0 user -> 'user:[4026531837]'lrwxrwxrwx 1 root root 0 uts -> 'uts:[4026531838]'方括号中的数字是 Namespace 的唯一标识符(inode 号)。两个进程如果指向同一个 inode,说明它们在同一个 Namespace 中。这就是判断两个进程是否共享某个 Namespace 的方法。
2.4 unshare 与 setns:操作 Namespace 的系统调用
内核提供了两个关键系统调用来操作 Namespace:
unshare(flags)——将调用进程从当前 Namespace 中移出,创建并加入新的 Namespace:
SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags){ // 根据 flags 创建新的 Namespace // CLONE_NEWNS → 新 Mount Namespace // CLONE_NEWUTS → 新 UTS Namespace // CLONE_NEWIPC → 新 IPC Namespace // CLONE_NEWPID → 新 PID Namespace(仅影响子进程) // CLONE_NEWNET → 新 Network Namespace // CLONE_NEWUSER → 新 User Namespace // ...}setns(fd, flags)——将调用进程加入由文件描述符 fd 指定的已有 Namespace:
SYSCALL_DEFINE2(setns, int, fd, int, nstype){ // fd 指向 /proc/[pid]/ns/ 下的某个符号链接 // nstype 用于验证 Namespace 类型是否匹配 // 将当前进程切换到目标 Namespace}unshare(CLONE_NEWPID) 有一个微妙之处:它只为后续创建的子进程建立新的 PID Namespace,调用进程自身的 PID 不会改变。这是因为当前进程已经在父 Namespace 中注册了 PID,无法撤销。
三、Cgroup:资源使用限制
如果说 Namespace 是”你能看到什么”,那么 Cgroup 就是”你能用多少”。Cgroup(Control Group)将进程组织成层次化的组,对每组施加资源限制、优先级分配和审计统计。
3.1 Cgroup v1 vs v2:架构演进
Cgroup 经历了从 v1 到 v2 的重大架构变革:
| 维度 | Cgroup v1 | Cgroup v2 |
|---|---|---|
| 层级结构 | 每个控制器独立层级 | 统一单层级(unified hierarchy) |
| 挂载方式 | 每个控制器单独挂载 | 一次挂载,所有控制器共享 |
| 进程归属 | 同一进程可在不同层级的不同组 | 一个进程只能在一个 cgroup 中 |
| 控制器启用 | 挂载即启用 | 通过 cgroup.subtree_control 按需启用 |
| 线程粒度 | 部分控制器支持线程模式 | 原生支持线程模式(cgroup.type) |
| 压力通知 | 无 | memory.pressure_level + PSI |
| 默认行为 | 无默认限制 | memory.swap.max 可限制 swap |
Cgroup v2 的统一层级是最核心的设计改进。在 v1 中,cpu 控制器和 memory 控制器有各自独立的树形结构,一个进程可以位于 cpu 层级的 A 组、memory 层级的 B 组——这导致资源所有权模糊,管理复杂。v2 强制所有控制器共享同一棵树,一个进程只能属于一个 cgroup,资源所有权清晰明确。
3.2 Cgroup v2 核心控制器
cpu 控制器
cpu 控制器限制 cgroup 的 CPU 使用时间。核心接口文件:
cpu.max:格式为"max 100000"或"50000 100000",第一个值是配额(微秒),第二个值是周期(微秒)。max表示无限制。50000 100000表示每个 100ms 周期内最多使用 50ms CPU 时间(即 50% CPU)。cpu.weight:1-10000 的权重值,用于在兄弟 cgroup 之间按比例分配 CPU 时间。这是 CFS 调度器在 cgroup 粒度的扩展。
# 限制容器最多使用 1.5 个 CPU 核心echo "150000 100000" > /sys/fs/cgroup/mypod/cpu.max
# 设置相对权重(默认 100)echo 200 > /sys/fs/cgroup/mypod/cpu.weightmemory 控制器
memory 控制器限制 cgroup 的内存使用。核心接口文件:
memory.max:内存使用硬限制(字节)。超过此限制会触发 OOM killer。memory.high:内存使用软限制。超过此限制后内核会积极回收内存,但不会杀进程。memory.swap.max:swap 使用限制。设为0可完全禁止 swap 使用。memory.current:当前内存使用量(只读)。memory.oom.group:设为 1 时,OOM killer 会杀死该 cgroup 中的所有进程。
# 限制最多使用 512MB 内存echo 536870912 > /sys/fs/cgroup/mypod/memory.max
# 禁止使用 swapecho 0 > /sys/fs/cgroup/mypod/memory.swap.max
# 设置软限制,超过时触发回收echo 4294967296 > /sys/fs/cgroup/mypod/memory.highio 控制器
io 控制器限制 cgroup 的块设备 I/O 带宽和 IOPS。核心接口文件:
io.max:格式为"8:0 rbps=max wbps=10485760 riops=max wiops=1000",按设备号设置读写带宽和 IOPS 限制。io.weight:1-10000 的权重值,类似 cpu.weight。
# 限制 /dev/sda (8:0) 的写带宽为 10MB/secho "8:0 wbps=10485760" > /sys/fs/cgroup/mypod/io.maxpids 控制器
pids 控制器限制 cgroup 中可以创建的进程数量,防止 fork 炸弹:
# 限制最多 100 个进程echo 100 > /sys/fs/cgroup/mypod/pids.max
# 查看当前进程数cat /sys/fs/cgroup/mypod/pids.currentcpuset 控制器
cpuset 控制器限制 cgroup 可以使用的 CPU 核心和内存节点(NUMA):
# 只允许使用 CPU 0-1echo "0-1" > /sys/fs/cgroup/mypod/cpuset.cpus
# 只允许使用 NUMA 节点 0 的内存echo "0" > /sys/fs/cgroup/mypod/cpuset.mems3.3 内核实现:css_set 与 cgroup_subsys
Cgroup 的内核实现围绕两个核心数据结构展开:
// css_set:连接进程与 cgroup 的桥梁struct css_set { struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; // 每个控制器对应一个 css 指针 struct list_head tasks; // 属于此 css_set 的进程列表 struct list_head mg_tasks; // 正在迁移的进程 // ...};
// include/linux/cgroup-defs.h// cgroup_subsys_state:控制器在 cgroup 中的实例struct cgroup_subsys_state { struct cgroup *cgroup; // 所属 cgroup struct cgroup_subsys *ss; // 所属控制器 struct percpu_ref refcnt; // 引用计数 // ...};
// cgroup_subsys:控制器的操作接口struct cgroup_subsys { const char *name; // 控制器名称 struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css); int (*css_online)(struct cgroup_subsys_state *css); void (*css_offline)(struct cgroup_subsys_state *css); void (*css_free)(struct cgroup_subsys_state *css); // 控制器特有的方法...};进程与 cgroup 的关系通过 css_set 间接建立:task_struct → css_set → cgroup_subsys_state[] → cgroup。这种间接设计使得多个进程可以共享同一组 cgroup 归属,避免为每个进程维护独立的关联关系。
当进程被移入新的 cgroup 时,内核会查找或创建匹配的 css_set,更新进程的 cgroups 指针,并调用各控制器的 css_online() 回调完成初始化。
3.4 PSI:资源压力感知
Cgroup v2 引入了 PSI(Pressure Stall Information)机制,让用户空间可以感知资源竞争程度:
# 查看 CPU 压力cat /sys/fs/cgroup/mypod/cpu.pressure# some avg10=0.00 avg60=0.12 avg300=0.05 total=123456# full avg10=0.00 avg60=0.00 avg300=0.00 total=0
# 查看内存压力cat /sys/fs/cgroup/mypod/memory.pressure# some avg10=2.34 avg60=1.56 avg300=0.89 total=9876543
# 查看 IO 压力cat /sys/fs/cgroup/mypod/io.pressure- some:至少有一个任务等待资源的时间占比
- full:所有任务都在等待资源的时间占比(完全停滞)
PSI 是 Kubernetes 基于资源压力进行调度决策的基础设施。
四、容器运行时:Namespace + Cgroup 的编织者
4.1 runc:从 OCI 规范到运行容器
容器运行时(如 runc)是 Namespace 和 Cgroup 的”编织者”——它按照 OCI(Open Container Initiative)运行时规范,依次创建各种 Namespace、配置 Cgroup 限制、设置 rootfs,最终启动容器进程。
runc 创建容器的大致流程:
- 创建 User Namespace(如果配置了非 root 容器)
- 创建 PID Namespace(
unshare(CLONE_NEWPID)) - 创建 Mount Namespace(
unshare(CLONE_NEWNS)),挂载 rootfs - 创建 Network Namespace(
unshare(CLONE_NEWNET)),配置 veth pair - 创建 UTS/IPC/Cgroup Namespace
- 配置 Cgroup 限制(写入
cpu.max、memory.max等) - 执行容器入口进程(
execve容器镜像中的 CMD/ENTRYPOINT)
// runc/libcontainer/process_linux.go(简化)func (p *initProcess) start() error { // 1. 创建 namespace cmd.SysProcAttr.Cloneflags = p.config.Namespaces.CloneFlags() // CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET | CLONE_NEWNS
// 2. 启动子进程(此时已在新 namespace 中) if err := cmd.Start(); err != nil { return err }
// 3. 配置 cgroup if err := p.manager.Apply(p.pid()); err != nil { return err }
// 4. 设置网络(veth pair, bridge, iptables) if err := p.network.Setup(p.config.Networks); err != nil { return err }
// 5. 发送 execv 指令,执行容器入口 return p.sendConfig()}4.2 容器 = Namespace + Cgroup + rootfs
一个完整的容器由三部分组成:
| 组件 | 作用 | 内核机制 |
|---|---|---|
| Namespace | 资源视图隔离 | unshare/setns 系统调用 |
| Cgroup | 资源使用限制 | cgroup 文件系统接口 |
| rootfs | 文件系统隔离 | Mount Namespace + pivot_root |
rootfs 是容器的文件系统视图——通过 Mount Namespace + pivot_root()(或 chroot()),将容器的根目录切换为镜像解压后的目录。容器内进程看到的 / 是镜像中的文件系统,而非宿主机的根文件系统。
五、eBPF 在 Cgroup 中的应用
eBPF(extended Berkeley Packet Filter)与 Cgroup 的结合是现代容器网络和安全的重要基础设施。内核提供了 cgroup-bpf 机制,允许 eBPF 程序附加到 cgroup 上,对该 cgroup 中所有进程的网络行为进行过滤或修改。
5.1 cgroup-bpf 附加类型
enum cgroup_bpf_attach_type { CGROUP_INET_INGRESS, // 入站网络包过滤 CGROUP_INET_EGRESS, // 出站网络包过滤 CGROUP_INET_SOCK_CREATE, // socket 创建拦截 CGROUP_SOCK_OPS, // socket 操作回调 CGROUP_DEVICE, // 设备访问控制(替代 cgroup v1 devices 控制器) CGROUP_INET4_BIND, // IPv4 bind 拦截 CGROUP_INET6_BIND, // IPv6 bind 拦截 CGROUP_INET4_CONNECT, // IPv4 connect 拦截 CGROUP_INET6_CONNECT, // IPv6 connect 拦截 // ...};5.2 典型应用场景
- Cilium:使用 cgroup-bpf 实现 socket-level 的网络策略,绕过 iptables,性能提升显著
- Facebook Katran:基于
CGROUP_INET_SOCK_CREATE实现 L4 负载均衡 - 设备控制:
CGROUP_DEVICE替代了 cgroup v1 的 devices 控制器,用 eBPF 程序决定容器可以访问哪些设备节点
# 将 eBPF 程序附加到 cgroupbpftool cgroup attach /sys/fs/cgroup/mypod/ ingress pinned /sys/fs/bpf/my_filter
# 查看附加到 cgroup 的 eBPF 程序bpftool cgroup show /sys/fs/cgroup/mypod/六、从 Namespace + Cgroup 到 Kubernetes Pod
6.1 Pod 的内核本质
Kubernetes 的 Pod 是一组共享某些 Namespace 的容器。从内核视角看,Pod 的实现非常直接:
- 同一个 Pod 内的所有容器共享 Network Namespace、IPC Namespace 和 UTS Namespace
- 每个容器有独立的 Mount Namespace 和 PID Namespace
- 整个 Pod 对应一个 cgroup(Kubernetes 通常使用 cgroup v2 的层级结构管理)
6.2 Pause 容器:Pod 的 Namespace 锚点
Kubernetes 使用 Pause 容器(也叫 sandbox 容器)作为 Pod 中共享 Namespace 的锚点。Pause 容器是一个极简进程(只执行 pause() 系统调用,永久睡眠),它的作用是:
- 创建 Network Namespace、IPC Namespace、UTS Namespace
- 作为这些 Namespace 的”持有者”——即使 Pod 中的业务容器崩溃重启,Namespace 也不会被销毁
- 为后续加入的容器提供
setns()的目标
当业务容器启动时,它通过 setns() 加入 Pause 容器已创建的共享 Namespace,然后创建自己的 Mount Namespace 和 PID Namespace。
6.3 资源限制的层级传递
Kubernetes 的 resources.limits 和 resources.requests 最终映射为 cgroup 参数:
| Kubernetes 字段 | cgroup v2 接口 | 含义 |
|---|---|---|
resources.limits.cpu: "2" | cpu.max = "200000 100000" | 2 个 CPU 核心的硬限制 |
resources.limits.memory: "512Mi" | memory.max = 536870912 | 512MB 内存硬限制 |
resources.requests.cpu: "1" | cpu.weight = 100(按比例) | CPU 调度权重 |
resources.requests.memory: "256Mi" | 用于调度决策 | 不直接映射为 cgroup 参数 |
Pod 级别的 cgroup 是所有容器 cgroup 的父节点,Pod 的资源限制是容器资源限制的上限。这种层级传递确保了 Pod 不会超过其总配额,即使单个容器的限制尚未触达。
七、动手实践
实践 1:用 unshare 创建隔离环境
# 创建新的 UTS、IPC、PID、Mount Namespace# --fork: 在子进程中运行(PID NS 需要)# --mount-proc: 挂载新的 /proc(否则看到宿主机的进程)unshare --uts --ipc --pid --mount --fork --mount-proc /bin/bash
# 在新 Namespace 中验证隔离效果hostname isolated-hosthostname # 输出: isolated-host
# 另一个终端中检查宿主机 hostname 未改变hostname # 输出: 原始主机名
# 在新 PID Namespace 中ps aux # 只能看到 bash 和 ps 两个进程exit # 退出后 Namespace 自动销毁实践 2:查看进程的 Namespace
# 查看当前进程的所有 Namespacels -la /proc/self/ns/
# 比较两个进程是否在同一个 Network Namespacereadlink /proc/1/ns/netreadlink /proc/$$/ns/net# 如果 inode 号相同,则在同一个 Network Namespace
# 查看系统中所有不同的 Namespacels -la /proc/*/ns/* 2>/dev/null | awk '{print $NF}' | sort -u实践 3:用 cgcreate 和 cgset 管理 Cgroup v1
# 安装 cgroup 工具sudo apt install cgroup-tools
# 创建一个新的 cgroup(v1)sudo cgcreate -g cpu,memory:/mygroup
# 设置 CPU 限制(50% CPU)sudo cgset -r cpu.cfs_quota_us=50000 mygroupsudo cgset -r cpu.cfs_period_us=100000 mygroup
# 设置内存限制(256MB)sudo cgset -r memory.limit_in_bytes=268435456 mygroup
# 将进程加入 cgroupsudo cgclassify -g cpu,memory:mygroup $$
# 验证cat /proc/$$/cgroup实践 4:直接操作 Cgroup v2 文件
# 创建子 cgroupsudo mkdir /sys/fs/cgroup/mycontainer
# 将当前进程加入 cgroupecho $$ | sudo tee /sys/fs/cgroup/mycontainer/cgroup.procs
# 设置 CPU 限制(1 核心)echo "100000 100000" | sudo tee /sys/fs/cgroup/mycontainer/cpu.max
# 设置内存限制(128MB)echo 134217728 | sudo tee /sys/fs/cgroup/mycontainer/memory.max
# 禁止 swapecho 0 | sudo tee /sys/fs/cgroup/mycontainer/memory.swap.max
# 限制进程数echo 50 | sudo tee /sys/fs/cgroup/mycontainer/pids.max
# 查看当前资源使用cat /sys/fs/cgroup/mycontainer/memory.currentcat /sys/fs/cgroup/mycontainer/pids.current
# 查看 PSI 压力指标cat /sys/fs/cgroup/mycontainer/cpu.pressurecat /sys/fs/cgroup/mycontainer/memory.pressurecat /sys/fs/cgroup/mycontainer/io.pressure实践 5:用 setns 加入已有 Namespace
# 终端 1:创建一个新的 Network Namespace 并运行一个进程unshare --net /bin/bash# 在新 Network Namespace 中启动一个简单的服务nc -l 8080 &
# 终端 2:找到该 Namespace 的文件描述符NS_INODE=$(readlink /proc/$(pgrep -f "unshare --net")/ns/net)echo "Namespace: $NS_INODE"
# 使用 nsenter 加入该 Namespacesudo nsenter --net --target $(pgrep -f "unshare --net") /bin/baship link # 只看到 lo 设备参考资料
内核源码
| 路径 | 内容 |
|---|---|
kernel/cgroup/ | Cgroup 核心实现(cgroup.c、rstat.c、legacy_freezer.c) |
kernel/cgroup/cgroup-v1.c | Cgroup v1 兼容层 |
kernel/cgroup/rstat.c | Cgroup 递归统计 |
kernel/pid_namespace.c | PID Namespace 实现 |
kernel/user_namespace.c | User Namespace 实现 |
kernel/nsproxy.c | Namespace 代理管理 |
net/core/net_namespace.c | Network Namespace 实现 |
fs/namespace.c | Mount Namespace 实现 |
include/linux/cgroup-defs.h | Cgroup 核心数据结构定义 |
include/linux/nsproxy.h | nsproxy 结构定义 |
手册页
man 7 namespaces——Linux Namespace 概述,7 种 Namespace 的详细说明man 7 cgroups——Cgroup 概述,v1/v2 接口说明man 2 unshare——unshare 系统调用man 2 setns——setns 系统调用man 1 nsenter——nsenter 工具,加入已有 Namespace
官方文档
- Cgroup v2 Documentation——内核官方 Cgroup v2 文档,所有接口文件的权威说明
- OCI Runtime Spec——容器运行时规范,定义了 Namespace/Cgroup 的配置格式
- Kubernetes Cgroup v2 Support——Kubernetes 对 Cgroup v2 的支持说明
深入阅读
- 《Container Security》——Liz Rice,从安全视角剖析容器技术的内核基础
- 《Linux Containers and Virtualization》——Shashank Mohan Jain,容器技术的系统级解析
- Cilium eBPF Documentation——eBPF 在容器网络中的实践
- PSI — Pressure Stall Information——PSI 机制文档
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






