1.Namespaces
概述
参考:
Linux Namespaces(Linux 名称空间) 是 Linux 内核的一个特性,Namespaces 可以对内核资源进行划分,使得一组进程看到一组资源,而另一组进程看到一组不同的资源。
这里的资源包括 进程 ID、主机名、用户 ID、网络 等等。
如果把 Linux 操作系统比作一个大房子,那名称空间指的就是这个房子中的一个个房间,住在每个房间里的人都自以为独享了整个房子的资源,但其实大家仅仅只是在共享的基础之上互相隔离,共享指的是共享全局的资源,而隔离指的是局部上彼此保持隔离,因而名称空间的本质就是指:一种在空间上隔离的概念,当下盛行的许多容器虚拟化技术(典型代表如 LXC、Docker)就是基于 Linux 名称空间的概念而来的。
很早以前的 Unix 有一个叫 Chroot 的系统调用(通过修改根目录把用户 jail(监狱) 到一个特定目录下),Chroot 提供了一种简单的隔离模式(隔离目录):Chroot 内部的文件系统无法访问外部的内容。Linux Namespace 就是基于 Chroot 的概念扩展而来,提供了对系统下更多资源的隔离机制。
操作系统通过虚拟内存技术,使得每个用户进程都认为自己拥有所有的物理内存,这是操作系统对内存的虚拟化。操作系统通过分时调度系统,每个进程都能被【公平地】调度执行,即每个进程都能获取到 CPU,使得每个进程都认为自己在进程活动期间拥有所有的 CPU 时间,这是操作系统对 CPU 的虚拟化。
从这两种虚拟化方式可推知,当使用某种虚拟化技术去管理进程时,进程会认为自己拥有某种物理资源的全部。
虚拟内存和分时系统均是对物理资源进行虚拟化,其实操作系统中还有很多非物理资源,比如用户权限系统资源、网络协议栈资源、文件系统挂载路径资源等。通过 Linux 的 namespace 功能,可以对这些非物理全局资源进行虚拟化。
Linux namespace 是在当前运行的系统环境中创建(隔离)另一个进程的运行环境出来,并在此运行环境中将一些必要的系统全局资源进行【虚拟化】。进程可以运行在指定的 namespace 中,因此,namespace 中的每个进程都认为自己拥有所有这些虚拟化的全局资源。
背景
Linux Namespaces 的灵感来自 Plan 9 from Bell Labs 中大量使用的名称空间功能。Plan 9 from Bell Labs 是贝尔实验室弄出来的分布式操作系统。
Linux Namespace 类型
Note:随着技术的发展,Linux 内核支持的的 Namespace 类型在逐步增加
目前,Linux 已经支持 8 种全局资源的虚拟化(每种资源都是随着 Linux 内核版本的迭代而逐渐加入的,因此有些内核版本可能不具备某种 namespace):
- cgroup namespace:该 namespace 可单独管理自己的 cgroup
- ipc namespace:该 namespace 有自己的 IPC,比如共享内存、信号量等
- network namespace:该 namespace 有自己的网络资源,包括网络协议栈、网络设备、路由表、防火墙、端口等
- mount namespace:该 namespace 有自己的挂载信息,即拥有独立的目录层次
- pid namespace:该 namespace 有自己的进程号,使得 namespace 中的进程 PID 单独编号,比如可以 PID=1
- time namespace:该 namespace 有自己的启动时间点信息和单调时间,比如可设置某个 namespace 的开机时间点为 1 年前启动,再比如不同的 namespace 创建后可能流逝的时间不一样
- user namespace:该 namespace 有自己的用户权限管理机制(比如独立的 UID/GID),使得 namespace 更安全
- uts namespace:该 namepsace 有自己的主机信息,包括主机名(hostname)、NIS domain name
用户可以同时创建具有多种资源类型的 namespace,比如创建一个同时具有 uts、pid 和 user 的 namespace。
| 类型 | 功能说明 | 系统调用参数 | 内核版本 |
|---|---|---|---|
| MNT Namespace | 提供磁盘挂载点和文件系统的隔离能力 | CLONE_NEWNS | 2.4.19 |
| IPC Namespace | 提供进程间通信的隔离能力 | CLONE_NEWIPC | 2.6.19 |
| Net Namespace | 提供网络隔离能力 | CLONE_NEWNET | 2.6.29 |
| UTS Namespace | 提供主机名隔离能力 | CLONE_NEWUTS | 2.6.19 |
| PID Namespace | 提供进程隔离能力 | CLONE_NEWPID | 2.6.24 |
| User Namespace | 提供用户隔离能力 | CLONE_NEWUSER | 3.8 |
| CGroup Namespace | Cgroup root directory | 4.6 |
理解 Linux namespace
用户可以创建指定类型的 namespace 并将进程放入该 namespace 中运行,这表示从当前的系统运行环境中隔离一个进程的运行环境,在此 namespace 中运行的进程将认为自己享有该 namespace 中的独立资源。
实际上,即使用户没有手动创建 Linux namespace,Linux 系统开机后也会创建一个默认的 namespace,称为 root namespace,所有进程默认都运行在 root namespace 中,每个进程都认为自己拥有该 namespace 中的所有系统全局资源。
回顾一下 Linux 的开机启动流程,内核加载成功后将初始化系统运行环境,这个运行环境就是 root namespace 环境,系统运行环境初始化完成后,便可以认为操作系统已经开始工作了。
每一个 namespace 都基于当前内核,无论是默认的 root namespace 还是用户创建的每一个 namespace,都基于当前内核工作。所以可以认为 namespace 是内核加载后启动的一个特殊系统环境,用户进程可以在此环境中独立享用资源。更严格地说,root namespace 直接基于内核,而用户创建的 namespace 运行环境基于当前所在的 namespace。之所以用户创建的 namespace 不直接基于内核环境,是因为每一个 namespace 可能都会修改某些运行时内核参数。
比如,用户创建的 uts namespace1 中修改了主机名为 ns1,然后在 namespace1 中创建 uts namespace2 时,namespace2 默认将共享 namespace1 的其他资源并拷贝 namespace1 的主机名资源,因此 namespace2 的主机名初始时也是 ns1。当然,namespace2 是隔离的,可以修改其主机名为 ns2,这不会影响其他 namespace,修改后,将只有 namespace2 中的进程能看到其主机名为 ns2。

可以通过如下方式查看某个进程运行在哪一个 namespace 中,即该进程享有的独立资源来自于哪一个 namespace。
# ls -l /proc/<PID>/ns
$ ls -l /proc/$$/ns | awk '{print $1,$(NF-2),$(NF-1),$NF}'
lrwxrwxrwx cgroup -> cgroup:[4026531835]
lrwxrwxrwx ipc -> ipc:[4026531839]
lrwxrwxrwx mnt -> mnt:[4026531840]
lrwxrwxrwx net -> net:[4026531992]
lrwxrwxrwx pid -> pid:[4026531836]
lrwxrwxrwx pid_for_children -> pid:[4026531836]
lrwxrwxrwx user -> user:[4026531837]
lrwxrwxrwx uts -> uts:[4026531838]
$ sudo ls -l /proc/1/ns | awk '{print $1,$(NF-2),$(NF-1),$NF}'
lrwxrwxrwx cgroup -> cgroup:[4026531835]
lrwxrwxrwx ipc -> ipc:[4026531839]
lrwxrwxrwx mnt -> mnt:[4026531840]
lrwxrwxrwx net -> net:[4026531992]
lrwxrwxrwx pid -> pid:[4026531836]
lrwxrwxrwx pid_for_children -> pid:[4026531836]
lrwxrwxrwx user -> user:[4026531837]
lrwxrwxrwx uts -> uts:[4026531838]
这些文件表示当前进程打开的 namespace 资源,每一个文件都是一个软链接,所指向的文件是一串格式特殊的名称。冒号后面中括号内的数值表示该 namespace 的 inode。如果不同进程的 namespace inode 相同,说明这些进程属于同一个 namespace。
从结果上来看,每个进程都运行在多个 namespace 中,且 pid=1 和 pid=$$(当前 Shell 进程)两个进程的 namespace 完全一样,说明它们运行在相同的环境下(root namespace)。
# namespace 概念和细节相关 man 文档。这些 man 手册在 3.10 内核及之前版本是没有的
man namespaces
man uts_namespaces
man network_namespaces
man ipc_namespaces
man pid_namespaces
man mount_namespaces
man user_namespaces
man time_namespaces
man cgroup_namespaces
# namespace 管理工具
man unshare # 创建namespace
man nscreate # 创建namespace,老版本的内核没有该工具
man nsenter # 切换namespace
man lsns # 查看当前已创建的namespace
Namespace 的具体实现
对于 Linux 系统来说,自己本身就是一个 Namespace。系统启动的第一个进程 systemd 自己就有对应的 6 个名称空间,可以通过 lsns 命令看到 pid 为 1 的进程所使用的 Namespace,我们平时操作的地方就是 systemd 所在的 jail,所以能看到的 / 就是 systemd 所在 jail 规定出来的 /
Linux Namespace 主要使用三个系统调用来实现
- clone() # 实现线程的系统调用,用来创建一个新的进程 。
- unshare() # 使某进程脱离某个 Namespace
- setns() # 把某进程加入到某个 Namespace
每个 NameSpace 的说明:
- 当调用 clone 时,设定了 CLONE_NEWPID,就会创建一个新的 PID Namespace,clone 出来的新进程将成为 Namespace 里的第一个进程。一个 PID Namespace 为进程提供了一个独立的 PID 环境,PID Namespace 内的 PID 将从 1 开始,在 Namespace 内调用 fork,vfork 或 clone 都将产生一个在该 Namespace 内独立的 PID。新创建的 Namespace 里的第一个进程在该 Namespace 内的 PID 将为 1,就像一个独立的系统里的 init 进程一样。该 Namespace 内的孤儿进程都将以该进程为父进程,当该进程被结束时,该 Namespace 内所有的进程都会被结束。PID Namespace 是层次性,新创建的 Namespace 将会是创建该 Namespace 的进程属于的 Namespace 的子 Namespace。子 Namespace 中的进程对于父 Namespace 是可见的,一个进程将拥有不止一个 PID,而是在所在的 Namespace 以及所有直系祖先 Namespace 中都将有一个 PID。系统启动时,内核将创建一个默认的 PID Namespace,该 Namespace 是所有以后创建的 Namespace 的祖先,因此系统所有的进程在该 Namespace 都是可见的。
- 当调用 clone 时,设定了 CLONE_NEWIPC,就会创建一个新的 IPC Namespace,clone 出来的进程将成为 Namespace 里的第一个进程。一个 IPC Namespace 有一组 System V IPC objects 标识符构成,这标识符有 IPC 相关的系统调用创建。在一个 IPC Namespace 里面创建的 IPC object 对该 Namespace 内的所有进程可见,但是对其他 Namespace 不可见,这样就使得不同 Namespace 之间的进程不能直接通信,就像是在不同的系统里一样。当一个 IPC Namespace 被销毁,该 Namespace 内的所有 IPC object 会被内核自动销毁。
- PID Namespace 和 IPC Namespace 可以组合起来一起使用,只需在调用 clone 时,同时指定 CLONE_NEWPID 和 CLONE_NEWIPC,这样新创建的 Namespace 既是一个独立的 PID 空间又是一个独立的 IPC 空间。不同 Namespace 的进程彼此不可见,也不能互相通信,这样就实现了进程间的隔离
- 当调用 clone 时,设定了 CLONE_NEWNS,就会创建一个新的 mount Namespace。每个进程都存在于一个 mount Namespace 里面,mount Namespace 为进程提供了一个文件层次视图。如果不设定这个 flag,子进程和父进程将共享一个 mount Namespace,其后子进程调用 mount 或 umount 将会影响到所有该 Namespace 内的进程。如果子进程在一个独立的 mount Namespace 里面,就可以调用 mount 或 umount 建立一份新的文件层次视图。该 flag 配合 pivot_root 系统调用,可以为进程创建一个独立的目录空间。
- 当调用 clone 时,设定了 CLONE_NEWNET,就会创建一个新的 Network Namespace。一个 Network Namespace 为进程提供了一个完全独立的网络协议栈的视图。包括网络设备接口,IPv4 和 IPv6 协议栈,IP 路由表,防火墙规则,sockets 等等。一个 Network Namespace 提供了一份独立的网络环境,就跟一个独立的系统一样。一个物理设备只能存在于一个 Network Namespace 中,可以从一个 Namespace 移动另一个 Namespace 中。虚拟网络设备(virtual network device)提供了一种类似管道的抽象,可以在不同的 Namespace 之间建立隧道。利用虚拟化网络设备,可以建立到其他 Namespace 中的物理设备的桥接。当一个 Network Namespace 被销毁时,物理设备会被自动移回 init Network Namespace,即系统最开始的 Namespace
- 当调用 clone 时,设定了 CLONE_NEWUTS,就会创建一个新的 UTS Namespace。一个 UTS Namespace 就是一组被 uname 返回的标识符。新的 UTS Namespace 中的标识符通过复制调用进程所属的 Namespace 的标识符来初始化。Clone 出来的进程可以通过相关系统调用改变这些标识符,比如调用 sethostname 来改变该 Namespace 的 hostname。这一改变对该 Namespace 内的所有进程可见。CLONE_NEWUTS 和 CLONE_NEWNET 一起使用,可以虚拟出一个有独立主机名和网络空间的环境,就跟网络上一台独立的主机一样。
以上所有 clone flag 都可以一起使用,为进程提供了一个独立的运行环境。LXC 正是通过在 clone 时设定这些 flag,为进程创建一个有独立 PID,IPC,FS,Network,UTS 空间的 container。一个 container 就是一个虚拟的运行环境,对 container 里的进程是透明的,它会以为自己是直接在一个系统上运行的。
Namespace 关联文件
主信息:
- /proc/PID/ns/ # 由于 namespace 都是与进程相关联,那么可以通过从每个进程的 ns 目录查看相关进程的 namespace 使用情况
Network Namespace:
- /var/run/netns/NAME # 该目录为
ip netns命令所能调取查看的目录- 如果想让
ip netns命令查看到网络名称空间的信息,则需要把 /proc/PID/ns/net 文件链接到该目录即可
- 如果想让
反馈
此页是否对你有帮助?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.