深入理解 Linux Namespace 与 Cgroup:容器技术的内核基石
引言
在云原生时代,容器技术已经成为软件发布的基础设施。无论是 Docker 还是 Kubernetes,其底层都依赖于 Linux 内核的两大核心机制:Namespace(命名空间)和 Cgroup(控制组)。Namespace 负责隔离——让进程看到独立的系统视图;Cgroup 负责限制——让进程使用可控的资源配额。
本文将从操作系统内核的角度,深入解析这两大机制的设计哲学、API 演进以及在生产容器运行时中的工程实践。
一、Namespace:隔离的艺术
1.1 什么是 Namespace
Namespace 是 Linux 内核用来隔离全局资源的一种机制。它可以让一组进程看到独立于其他进程的系统资源视图——包括进程 ID、网络栈、文件系统挂载点、主机名等。
在没有 Namespace 的世界中,所有进程共享同一套系统资源视图。进程 1 总是 init/systemd,所有机器上的 hostname 返回相同的根文件系统。Namespace 打破了这个假设。
1.2 Namespace 的八大类型
截至 Linux 6.x 内核,共有 8 种 Namespace 类型:
| Namespace | 系统调用 Flag | 隔离内容 | 内核版本 |
|---|---|---|---|
| Mount | CLONE_NEWNS | 文件系统挂载点 | 2.4.19 |
| UTS | CLONE_NEWUTS | 主机名与域名 | 2.6.19 |
| IPC | CLONE_NEWIPC | System V IPC、POSIX 消息队列 | 2.6.19 |
| PID | CLONE_NEWPID | 进程 ID 编号空间 | 2.6.24 |
| Network | CLONE_NEWNET | 网络协议栈、路由表、防火墙规则 | 2.6.29 |
| User | CLONE_NEWUSER | 用户和组 ID 映射 | 3.8 |
| Cgroup | CLONE_NEWCGROUP | Cgroup 根目录视图 | 4.6 |
| Time | CLONE_NEWTIME | 系统时钟(启动/单调时间) | 5.6 |
1.3 核心系统调用
Namespace 的创建涉及三个关键系统调用:
clone() — 创建新进程时指定 Namespace 标志:
pid_t pid = clone(child_func, child_stack + STACK_SIZE,
CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWNET | SIGCHLD,
NULL);
unshare() — 让当前进程脱离某个 Namespace,创建新的:
# 创建一个独立的 PID 命名空间运行 shell
sudo unshare --pid --fork --mount-proc /bin/bash
setns() — 让当前进程加入某个已存在的 Namespace:
int fd = open("/proc/1234/ns/pid", O_RDONLY);
setns(fd, CLONE_NEWPID);
Docker 的 docker exec 命令就是通过 setns() 让调试工具加入目标容器的 Namespace 实现的。
1.4 PID Namespace 深度解析
PID Namespace 是最复杂的 Namespace 之一。关键特性:
- 每个 PID Namespace 的进程编号独立,从 1 开始
- PID Namespace 是嵌套的,可以有 15 层深度
- 父 Namespace 可以看到子 Namespace 的进程,反之不行
- PID 1 有特殊职责:收养孤儿进程、转发信号
# 示例:创建嵌套的 PID Namespace
# 在 PID Namespace 中,"init" 进程就是新创建进程
sudo unshare --pid --fork mount -t proc none /proc
ps aux # 只看到一个 PID 1 的 shell
1.5 User Namespace:安全的基石
User Namespace 实现了 UID/GID 映射,这是容器安全的核心:
容器内 UID 0 (root)
↓ 映射到
容器外 UID 100000 (普通用户)
# 查看映射关系
cat /proc/self/uid_map
# 输出: 0 100000 65536
这意味着容器内的 root 进程在宿主机上只是一个普通用户进程,极大降低了安全攻击面。
1.6 Network Namespace:网络隔离的魔法
Network Namespace 复制了整个网络协议栈:
# 创建网络 Namespace
ip netns add mynet
# 在 Namespace 中回环接口默认是 down 的
ip netns exec mynet ip link show lo
# 输出: LOOPBACK, DOWN
# 启动回环接口
ip netns exec mynet ip link set lo up
Docker 每创建一个容器,就创建一个新的 Network Namespace,通过 veth pair 将容器与宿主机网桥(docker0)连接。
二、Cgroup:资源管控的引擎
2.1 Cgroup 的设计哲学
控制组(Control Groups)是 Linux 内核的一个功能,用来限制、记录和隔离进程组所使用的物理资源(CPU、内存、磁盘 I/O 等)。
Cgroup 的核心数据结构是 hierarchy(层级树)。每个 subsystem(控制器)挂载到一个 hierarchy 上,每个进程可以属于任意数量的不同 hierarchy 中的 cgroup。
2.2 Cgroup v1 vs v2
| 特性 | Cgroup v1 | Cgroup v2 |
|---|---|---|
| 发布时间 | 2008 (2.6.24) | 2016 (4.5) |
| 设计模型 | 每个控制器独立 hierarchy | 统一 hierarchy |
| 进程规则 | 同一 hierarchy 下只能属于一个 cgroup | 同样约束,但更严格 |
| 线程模式 | 不支持 | 支持 per-thread 资源控制 |
| 默认状态 | Ubuntu 20.04 默认 | Fedora/RHEL 8+ 默认 |
| 压力停滞信息 | 无 memory.pressure | PSI (Pressure Stall Information) |
Docker 20.10+ 和 Kubernetes 1.20+ 已经开始支持 Cgroup v2。Ubuntu 22.04+ 和 Debian 11+ 默认启用 v2。
2.3 核心控制器
cpu 控制器 — 控制 CPU 使用比例:
# 创建 cgroup
mkdir /sys/fs/cgroup/cpu/myapp
# 限制 CPU:每 100ms 周期内最多使用 50ms
echo 50000 > /sys/fs/cgroup/cpu/myapp/cpu.cfs_quota_us
echo 10 > /sys/fs/cgroup/cpu/myapp/cfs_period_us # 默认值(可选)
# 将进程加入 cgroup
echo $$ > /sys/fs/cgroup/cpu/myapp/cgroup.procs
memory 控制器 — 控制内存使用:
# 限制内存上限为 512MB
echo 536870912 > /sys/fs/cgroup/memory/myapp/memory.limit_in_bytes
# 限制物理内存 + Swap
echo 536870912 > /sys/fs/cgroup/memory/myapp/memory.memsw.limit_in_bytes
# 设置软限制(低优先级,尽力而为)
echo 268435456 > /sys/fs/cgroup/memory/myapp/memory.soft_limit_in_bytes
io 控制器 — 控制磁盘 I/O:
# 限制读带宽 10MB/s (device 8:0 = /dev/sda)
echo "8:0 10485760" > /sys/fs/cgroup/io/myapp/io.max
pids 控制器 — 限制进程数:
# 限制最多 100 个进程
echo 100 > /sys/fs/cgroup/pids/myapp/pids.max
2.4 Cgroup v2 的改进语法
Cgroup v2 提供了更简洁的接口:
# Cgroup v2 启用的判断
$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# CPU 限制(权重模式)
echo "max 50000 100000" > /sys/fs/cgroup/myapp/cpu.max
# 内存限制
echo "536870912" > /sys/fs/cgroup/myapp/memory.high
echo "536870912" > /sys/fs/cgroup/myapp/memory.max
# IO 限制
echo "8:0 rbps=10485760" > /sys/fs/cgroup/myapp/io.max
三、Namespace + Cgroup = 容器
3.1 容器本质公式
容器 = Namespace (隔离) + Cgroup (资源限制) + Rootfs (文件系统)
这不是一个比喻——而是对容器技术的精确描述:
- Namespace 决定了容器内进程"能看到什么"
- Cgroup 决定了容器内进程"能用多少"
- Rootfs + OverlayFS 决定了容器内进程"能访问哪些文件"
3.2 容器运行时的 Namespace 和 Cgroup 对照表
以 runc 兼容运行时为例,Docker/K8s 容器启动时:
runtime-spec.linux.namespaces:
- type: pid → CLONE_NEWPID
- type: network → CLONE_NEWNET
- type: mount → CLONE_NEWNS
- type: ipc → CLONE_NEWIPC
- type: uts → CLONE_NEWUTS
- type: cgroup → CLONE_NEWCGROUP (K8s 1.19+)
- type: user → CLONE_NEWUSER (可选)
runtime-spec.linux.resources:
cpu.shares: 1024
memory.limit: 536870912
pids.limit: 1024
3.3 容器中的 Cgroup 层级
Docker 在 Cgroup 中的目录结构:
/sys/fs/cgroup/
└── memory
└── docker
└── a1b2c3d4e5f6... (容器ID)
├── memory.limit_in_bytes
├── memory.usage_in_bytes
├── memory.stat
└── cgroup.procs
Kubernetes Pod 的 Cgroup 结构:
/sys/fs/cgroup/
└── kubepods
└── besteffort / burstable / guaranteed
└── pod<POD_UID>
├── crio-<CONTAINER_ID>.scope (容器)
└── ...
3.4 特殊的 Namespace 共享策略
Kubernetes 通过 Namespace 共享实现 Pod 内容器协作:
- Network Namespace 共享:同一 Pod 内所有容器共享网络栈,
localhost互通 - IPC Namespace 共享:同一 Pod 内容器可共享内存和信号量
- PID Namespace 共享(v1.25+ alpha):同一 Pod 内容器可看到彼此的进程,便于信号传递和调试
四、工程实践深度解析
4.1 Docker run 参数的底层映射
docker run -it \
--name=my-container \
--cpus=2 \ # cpu.max: "200000 100000"
--memory=512m \ # memory.max: 536870912
--pids-limit=100 \ # pids.max: 100
--uts=host \ # 共享 UTS Namespace
--net=bridge \ # 新建 Network Namespace
--pid=container:other \ # 共享目标容器的 PID NS
--userns=host \ # 共享 User Namespace
--cgroupns=private \ # 新建 Cgroup Namespace
ubuntu:22.04 /bin/bash
4.2 常见问题与排查
问题 1:容器内看不到自己的进程列表
# 症状:mount 了宿主机的 /proc
# 解决:确保正确挂载独立的 procfs
mount -t proc none /proc
问题 2:Cgroup 内存限制导致 OOM
# 查看 OOM 事件计数
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.oom_control
# 分析 OOM 时的内存使用
cat /sys/fs/cgroup/memory/docker/<container_id>/memory.stat
问题 3:Network Namespace 中 DNS 解析失败
# 检查 Namespace 中的 resolv.conf
ip netns exec mynet cat /etc/resolv.conf
# 手动配置
mkdir -p /etc/netns/mynet
echo "nameserver 8.8.8.8" > /etc/netns/mynet/resolv.conf
4.3 生产级最佳实践
- 永远不要共享 User Namespace:除非你完全理解 uid_map/gid_map 的含义
- Cgroup v2 优先:统一 hierarchy 简化管理,PSI 提供更精准的 OOM 预警
- 不要过度隔离:按业务场景选择合适的 Namespace 共享策略
- 资源限制是硬上限:memory.max 触发后 OOM Killer 不会留情
- 使用 cgroup namespace:防止容器内进程看到宿主机的 cgroup 拓扑信息
五、下一代容器技术展望
5.1 eBPF 的深度融合
随着 eBPF 的成熟,Namespace 和 Cgroup 与 eBPF 的结合催生了新一代可观测性和安全性方案:
- Tetragon (Cilium):基于 eBPF 的运行时安全监控,可跟踪容器内进程跨 Namespace 行为
- Falco:系统调用审计,结合 Namespace 信息过滤
- Pixie:零侵染获取容器内观测数据
5.2 WebAssembly 与 Micro-VM
Firecracker / Kata Containers / gVisor 等方案在 Namespace+Cgroup 基础上增加了用户态内核拦截层,提供更强的隔离保证。
5.3 Kubernetes Sidecar-less 革命
Istio Ambient Mesh 去掉 Sidecar,改为基于 eBPF 的 ztunnel 共享代理。这是 Namespace 与 eBPF 联动的经典案例——每个节点一个 ztunnel 实例,通过 eBPF 将流量劫持到该实例处理。
总结
Namespace 和 Cgroup 是 Linux 容器技术的两大支柱:
- Namespace 负责"让进程以为自己独占了系统"——视觉隔离
- Cgroup 负责"让进程只能用分配到的资源"——资源管控
理解了这两者,你就理解了 Docker、containerd、CRI-O 等容器运行时 90% 的内核交互逻辑。对于 SRE 和平台工程师而言,这不仅是理论知识,更是排查生产故障、优化容器性能的必备技能。
推荐资源
- 《Linux Container Internals》— Jessica McKellar
- Linux 内核文档:
Documentation/admin-guide/cgroup-v2.rst - man 手册:
man 7 namespaces,man 7 cgroup - 在线实验:Play with Docker

发表评论 取消回复