Linux 3.8 合并窗口接受了 Eric Biederman 的大量用户命名空间及相关的补丁。尽管仍有一些细节待完成,例如,许多 Linux 文件系统还不知道用户命名空间,但用户命名空间的实现已经在功能上完成了。
用户命名空间的完成是一个里程碑。首先,这项工作代表了迄今为止命名空间中最复杂实现之一的完成,因为自从用户命名空间实现首次实现(在Linux2.6.23中)以来,已经有五年左右的时间了。其次,命名空间工作目前处于“稳定点”的状态,大多数现有命名空间的实现基本是完整的。但这并不代表命名空间的工作已经完成了:将来可能会添加其他命名空间,并且可能会进一步扩展现有的命名空间,例如为内核日志添加命名空间隔离。最后,就命名空间的使用方式而言,最近在用户命名空间实现方面的变化是一个规则的改变:从 Linux3.8 开始,没有特权的进程可以创建具有全部特权的用户名称空间,从而允许在用户名称空间内创建任何其他类型的命名空间。
因此,现在比较适合对名称空间进行概述,并查看命名空间 API。这是本系列文章的第一篇:在本文中,我们概述了当前可用的命名空间;在后续文章中,我们将展示如何在程序中使用命名空间 API。
命名空间
目前,Linux 实现了六种命名空间。每种命名空间的目的是将特定的全局系统资源包装在一个抽象中,使命名空间中的进程认为它们拥有全局资源的独立实例。命名空间的目标之一是支持容器的实现,容器是一种用于轻量级虚拟化(以及其他目的)的工具,它为一组进程提供了一种错觉,即它们是系统上唯一的进程。
接下来,我们按照命名空间实现的顺序(或者至少按照实现完成的顺序)来呈现命名空间。括号中列出的 CLONE_NEW* 标识符用于识别命名空间类型的常量的名称,在使用与命名空间相关的API(clone(),unshare() 和 setns())时用到,我们将在后续文章中进行描述。
Mount namespaces(CLONE_NEWNS, Linux 2.4.19)隔离一组进程看到的文件系统挂载点集。因此,不同挂载命名空间中的进程可具有不同的文件系统层次结构视图。随着挂载命名空间的添加,mount() 和 umount() 系统调用不再对系统上所有进程可见的全局挂载点集的进行操作,而仅操作与调用进程相关的挂载命名空间。
挂载命名空间的用途之一是创建类似于 chroot 的限制环境。然而,与使用 chroot() 系统调用相比,挂载命名空间更安全、灵活。挂载命名空间还可用于更复杂的用途。例如,可以在主从关系中单独设置一个挂载命名空间,以便挂载事件自动从一个命名空间传播到另一个命名空间;例如,允许挂载在一个命名空间中的光盘设备自动出现在其它命名空间中。
挂载命名空间是第一种在 Linux 上实现的命名空间,出现在 2002 年。这解释了相当通用的“NEWNS”名(简称“new namespace”):好像当时人们认为将来不会需要其他类型的命名空间了。
UTS namespaces(CLONE_NEWUTS, Linux 2.6.19)隔离两种系统标识符 —nodename 和 domainname — 由 uname() 返回;使用 sethostname() 和 setdomainname() 设置。在容器的上下文中,UTS 命名空间允许每个容器有自己的主机名和 NIS 域名。这有助于基于这些名称进行初始化操作和配置脚本。术语“UTS”来源于传递给 uname() 的结构体的名称:struct utsname。该结构体的名称来源于“UNIX 时分系统”。
IPC namespaces (CLONE_NEWIPC, Linux 2.6.19) 隔离特定的进程间通信(IPC)资源,即 System V IPC 对象和(从 Linux 2.6.30 开始)POSIX 消息队列。这些 IPC 机制的共同特点是 IPC 对象并不通过文件系统路径名标识。每个 IPC 命名空间都有自己的 System V IPC 标识符集和 POSIX 消息队列文件系统。
PID namespaces (CLONE_NEWPID, Linux 2.6.24) 隔离进程的 ID 号空间。换句话说,不同 PID 命名空间中的进程可以具有相同的 PID。PID 命名空间的主要好处之一是,可以在主机之间迁移容器,同时保持容器内进程的进程 ID 不变。PID 命名空间还允许每个容器有自己的 init(PID 1),它是“所有进程的祖先”,管理各种系统初始化任务,并在孤儿进程终止时获取它们。
站在每个 PID 命名空间实例的角度来看,进程有两个 PID:命名空间内的 PID 和主机系统上命名空间外的 PID。可以嵌套 PID 命名空间:一个进程可从其所在的PID 命名空间开始,一直到根 PID 命名空间,每层命名空间都有一个 PID。一个进程只能看到(例如,通过 /proc/pid 查看并使用 kill() 发送信号)它自己的 PID 命名空间中包含的进程以及该 PID 命名空间下面嵌套的命名空间。
Network namespaces (CLONE_NEWNET,始于 Linux 2.6.24,大部分由Linux 2.6.29 完成)隔离网络相关的系统资源。因此,每个网络命名空间都有自己的网络设备、IP地址、IP路由表、/proc/net 目录、端口号等。
从网络的角度,网络命名空间使得容器很有用:每个容器可以有自己的(虚拟)网络设备和绑定到命名空间中的端口号的应用程序;主机系统中合适的路由规则可以将网络数据包定向到与特定容器关联的网络设备。因此,例如,可以在同一主机系统上有多个容器化 web 服务器,每个服务器可绑定到其(每个容器)网络命名空间中的端口 80。
User namespaces (CLONE_NEWUSER, 始于 Linux 2.6.23,完成于 Linux 3.8) 隔离用户和组 ID 数字空间。换句话说,进程的用户和组 ID 在用户命名空间内外可以不同。最有趣的是,一个进程可以在用户命名空间外有一个普通的无特权用户 ID,同时在命名空间内有一个值 为 0 的用户 ID。这意味着进程对用户命名空间内的操作具有完全的 root 权限,但对命名空间外的操作没有权限。
从 Linux 3.8 开始,无特权的进程可以创建用户命名空间,这为应用程序添加了许多有趣的可能性:因为在其他情况下,无特权的进程可以在用户命名空间中拥有 root 权限,所以无特权的应用程序现在可以访问之前仅限于 root 的功能。Eric Biederman 在保证用户命名空间实现的安全性和正确性方面做了大量的工作。然而,这项工作所带来的变化是微妙和广泛的。因此,可能在用户命名空间会有一些尚未发现的安全问题,这些问题有待发现和修复。
结束语
自从第一个 Linux 命名空间的实现到现在已经有十年了。从那时起,命名空间的概念扩展到了一个更通用的框架中,用于隔离一系列系统全局资源。因此,命名空间现在以容器的形式为一个完整的轻量级虚拟化系统奠定了基础。随着名称空间概念的扩展,相关 API 已经从一个系统调用(clone())和一个或两个 /proc 文件发展到更多系统调用和 /proc 下的更多文件。这些 API 的细节将构成本文后续内容的主题。
原文:https://lwn.net/Articles/531114/
公众号:Geek乐园
云+社区:https://cloud.tencent.com/developer/column/4124