命名空间将原本全局共享的名称和资源视图进行分区。 每个任务在每种受支持的类型中都有一个命名空间实例;处于同一实例中的任务会观察到该实例 特有的相同资源。命名空间本身不会限制 CPU、内存或 I/O 消耗;这些资源控制由 cgroup 提供。
Linux 提供八种命名空间类型。Time 命名空间是在 Linux 5.6 中加入的;其他类型早于该版本
已经存在。命名空间标志的版本特定事实来源是
include/uapi/linux/sched.h。
| Type | Creation flag | Isolated resource |
|---|---|---|
| Cgroup | CLONE_NEWCGROUP | cgroup 根视图 |
| IPC | CLONE_NEWIPC | System V IPC 对象和 POSIX 消息队列 |
| Network | CLONE_NEWNET | 网络设备、协议栈、路由表、防火墙状态和端口空间 |
| Mount | CLONE_NEWNS | 挂载表和挂载传播关系 |
| PID | CLONE_NEWPID | 进程 ID 编号空间和 PID 1 语义 |
| Time | CLONE_NEWTIME | monotonic clock 和 boot-time clock 的偏移量 |
| User | CLONE_NEWUSER | 用户和组 ID 映射、凭据以及作用域限定的 capability |
| UTS | CLONE_NEWUTS | 主机名和 NIS 域名 |
初始系统从每种类型各有一个实例开始。命名空间标识不是可移植且具有全局意义的 32 位 ID。
相反,/proc/PID/ns 以符号链接的形式公开命名空间句柄,例如 net:[4026531840];
其中的数字标识该内核对象在命名空间文件系统中的 inode。
ls -l /proc/self/ns
lsns
readlink /proc/self/ns/net创建和加入命名空间 #
向 clone(2) 和 clone3(2) 传入一个或多个 CLONE_NEW* 标志时,它们会在新的命名空间中
创建子任务。unshare(2) 会让调用任务使用新创建的命名空间,但具体行为受每种命名空间类型
的规则约束。PID 命名空间和 Time 命名空间的 unshare 只会影响之后创建的子任务;调用者仍
留在当前的 PID 命名空间或 Time 命名空间中。setns(2) 会加入由已打开文件描述符引用的
命名空间。unshare(1) 和 nsenter(1) 是适合实验和管理的便捷前端工具。
# New user, mount, and PID namespaces; the child becomes PID 1 in its PID namespace.
unshare --user --map-root-user --mount --pid --fork --mount-proc sh
# Join the mount namespace of an existing process.
sudo nsenter --target "$PID" --mount sh命名空间操作不能相互替代:
- 新的 PID 命名空间会影响之后创建的子任务。任务不能将自身移动到另一个 PID 命名空间;
对其调用
setns()后,之后创建的子任务会在该命名空间中可见。 - 新的挂载命名空间最初会复制调用者的挂载表。共享挂载传播仍然可能在命名空间之间传递 挂载,因此容器设置通常会在挂载文件系统之前,将相关树设置为 private 或 slave。
- 网络命名空间启动时包含一个必须启用的 loopback 设备。将 veth 端点移动到该命名空间中, 是连接它的常见方式。
- User 命名空间的创建规则涉及凭据和映射,这些规则与其他命名空间类型不同。不要假设
setns()成功就会授予主机特权。
内核会在相关 User 命名空间中检查 capability,并拒绝不安全的转换。由于确切的错误条件 会因命名空间类型和内核版本而异,调用者应结合系统调用手册页和目标内核源码进行判断。
句柄和生命周期 #
/proc/PID/ns/<type> 可以作为命名空间文件描述符打开。只要仍有任务或命名空间文件描述符
引用它,命名空间就会保持存在。对该条目执行 bind mount 也会保留引用,因此监督程序可以在
最后一个进程退出后继续保留命名空间:
sudo mkdir -p /run/netns/demo
sudo touch /run/netns/demo
sudo mount --bind /proc/"$PID"/ns/net /run/netns/demo
ip netns exec demo ip link不再需要 bind mount 时必须卸载它。应将命名空间句柄视为携带权限的对象:对 /proc/PID/ns
的访问受 /proc 可见性和 ptrace 风格访问检查的约束;继承的文件描述符可能允许接收方调用
setns()。
User 命名空间和 ID 映射 #
User 命名空间会将命名空间内部的连续 ID 范围映射到其父 User 命名空间中的 ID。创建者通常
可以通过 --map-root-user 获得命名空间本地的有效 UID 0,但该身份不会自动获得对未映射
主机 ID 或全局主机资源的特权。
在非特权设置中,监督程序通常先写入 uid_map,然后向 setgroups 写入 deny,最后写入
gid_map。这种顺序可以防止进程通过未映射的组转换获得基于组的访问权限。映射规则经过
有意限制;委托的范围以及 newuidmap 和 newgidmap 辅助程序负责处理调用者无法直接写入
的映射。
Capability 是相对于 User 命名空间进行评估的。在子 User 命名空间中拥有 CAP_SYS_ADMIN
的进程可以执行由该命名空间管理的操作,例如配置其挂载命名空间,但它在初始 User 命名空间
中并不具备该 capability。由祖先命名空间拥有的内核对象、设备访问权限和 LSM 策略仍然构成
安全边界。
用于容器的组合 #
容器是多种隔离机制的组合,而不是单个命名空间。一个实际的设置顺序如下:
- 创建 User 命名空间并安装明确的 UID/GID 映射,因为它会为后续设置提供命名空间作用域内的 capability。
- 创建挂载命名空间,明确调整传播属性,并构建文件系统视图。
- 创建 PID 命名空间,fork 预期的 init 进程,并从挂载命名空间挂载新的
/proc,使进程 报告与 PID 视图保持一致。 - 按需创建或加入 Network、IPC、UTS、Time 和 cgroup 命名空间;从具有特权的主机侧管理器 配置网络链路。
- 在执行前将工作负载置于主机管理的 cgroup 中,并保留命名空间文件描述符,以便检查和拆除。
必须明确处理 PID 1,因为它负责回收孤儿子进程,并具有特殊的信号行为。只创建 PID 命名空间 的最小容器命令可能留下僵尸进程或忽略终止信号;运行时需要 init/reaper 或等效的监督机制。
挂载命名空间控制文件系统挂载的可见性,而 chroot(2) 只改变路径名解析。为避免暴露或
接收非预期的挂载,必须将两者与谨慎设置的挂载传播结合使用。
Cgroup 和 Cgroup 命名空间 #
Cgroup 命名空间会虚拟化任务的 cgroup 根路径。它会改变任务从 /proc/PID/cgroup 以及
挂载在该命名空间中的 cgroup 文件系统中读取的内容,从而阻止内部工作负载发现祖先路径。
它不会创建独立的 cgroup 层次结构,也不会削弱主机的 CPU、内存、I/O 或 PID 限制。
如需完整的命名空间 ABI 说明,请将相关的
namespaces(7) 手册页
与目标版本的
内核源码
结合查阅。
评论
在 GitHub 上阅读或评论(发表评论需要登录)