Linux 命名空间

最后更新: 2021-11-21 04:00:04 +0800 +0800
原文以英文撰写。您正在阅读AI翻译版本。

命名空间将原本全局共享的名称和资源视图进行分区。 每个任务在每种受支持的类型中都有一个命名空间实例;处于同一实例中的任务会观察到该实例 特有的相同资源。命名空间本身不会限制 CPU、内存或 I/O 消耗;这些资源控制由 cgroup 提供。

Linux 提供八种命名空间类型。Time 命名空间是在 Linux 5.6 中加入的;其他类型早于该版本 已经存在。命名空间标志的版本特定事实来源是 include/uapi/linux/sched.h。

TypeCreation flagIsolated resource
CgroupCLONE_NEWCGROUPcgroup 根视图
IPCCLONE_NEWIPCSystem V IPC 对象和 POSIX 消息队列
NetworkCLONE_NEWNET网络设备、协议栈、路由表、防火墙状态和端口空间
MountCLONE_NEWNS挂载表和挂载传播关系
PIDCLONE_NEWPID进程 ID 编号空间和 PID 1 语义
TimeCLONE_NEWTIMEmonotonic clock 和 boot-time clock 的偏移量
UserCLONE_NEWUSER用户和组 ID 映射、凭据以及作用域限定的 capability
UTSCLONE_NEWUTS主机名和 NIS 域名

初始系统从每种类型各有一个实例开始。命名空间标识不是可移植且具有全局意义的 32 位 ID。 相反,/proc/PID/ns 以符号链接的形式公开命名空间句柄,例如 net:[4026531840]; 其中的数字标识该内核对象在命名空间文件系统中的 inode。

sh
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) 是适合实验和管理的便捷前端工具。

sh
# 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 也会保留引用,因此监督程序可以在 最后一个进程退出后继续保留命名空间:

sh
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 策略仍然构成 安全边界。

用于容器的组合 #

容器是多种隔离机制的组合,而不是单个命名空间。一个实际的设置顺序如下:

  1. 创建 User 命名空间并安装明确的 UID/GID 映射,因为它会为后续设置提供命名空间作用域内的 capability。
  2. 创建挂载命名空间,明确调整传播属性,并构建文件系统视图。
  3. 创建 PID 命名空间,fork 预期的 init 进程,并从挂载命名空间挂载新的 /proc,使进程 报告与 PID 视图保持一致。
  4. 按需创建或加入 Network、IPC、UTS、Time 和 cgroup 命名空间;从具有特权的主机侧管理器 配置网络链路。
  5. 在执行前将工作负载置于主机管理的 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 上阅读或评论(发表评论需要登录)