一、linux namespace 介绍
1.1、概念
Linux Namespace是Linux内核提供的一种机制,它用于隔离不同进程的资源视图,使得每个进程都拥有独立的资源空间,从而实现进程之间的隔离和资源管理。
Linux Namespace的设计目标是为了解决多个进程之间资源冲突的问题,提供一种轻量级的虚拟化技术。通过使用Namespace,可以在一个物理主机上创建多个独立的虚拟环境,每个环境都有自己的进程、文件系统、网络和用户视图。其提供了一种抽象机制,将原本全局共享的资源隔离成不同的集合,集合中的成员独享其原本全局共享的资源。如下图:
进程 A 和进程 B 分别属于两个不同的 Namespace,那么进程 A 将可以使用 Linux 内核提供的所有 Namespace 资源:如独立的主机名,独立的文件系统,独立的进程编号等等。同样地,进程 B 也可以使用同类资源,但其资源与进程 A 使用的资源相互隔离,彼此无法感知。
从用户的角度来看,每一个命名空间应该像一台单独的 Linux计算机一样,有自己的 init进程 (PID为 1),其他进程的PID依次递增, A和B空间都有PID为l的init进程, 子命名空间的进程映 射到父命名空间的进程上,父命名空间可以知道每一个子命名空间的运行状态,而子命名空间与子命名空间之间是隔离的。
1.2、虚拟化相关概念
1.2.1、常见的进程级虚拟化技术:
-
chroot(Change Root)
:chroot
是一种将进程的根目录修改为指定目录的技术。它通过限制进程的文件系统访问范围,使得进程只能在指定的目录树中运行。这样可以实现一定程度的进程隔离。 -
Linux容器(Linux Containers,LXC)
:LXC
是一种操作系统级虚拟化技术,它使用Linux内核中的命名空间(namespaces)
和控制组(cgroups)
等特性,实现了对进程的隔离。通过创建和管理多个容器实例,每个容器都拥有自己的文件系统、网络和进程空间,可以实现进程级别的隔离和资源控制。 -
Docker
:Docker是基于LXC的一层封装,提供了更高级别的容器管理和部署工具。它通过使用镜像(Images)和容器(Containers)的概念,使得应用程序的打包、分发和部署更加方便。Docker在LXC的基础上添加了一些额外的功能和工具,使得容器的使用更加简单和高效。 -
systemd-nspawn
:systemd-nspawn
是Systemd
项目中的一个工具,它基于Linux命名空间和chroot,提供了一个简单的容器环境。它可以启动一个进程并将其隔离在一个独立的文件系统环境中,实现了进程的隔离和资源控制。
1.2.2、常见linux 虚拟化技术
完全虚拟化(Full Virtualization):
完全虚拟化技术通过在物理硬件上运行一个虚拟机监视器(Hypervisor)
,来模拟一个完整的虚拟硬件环境。在完全虚拟化中,虚拟机操作系统不需要进行修改,可以运行未经修改的操作系统。代表产品有:
KVM(Kernel-based Virtual Machine)
:KVM是一个开源的完全虚拟化解决方案,它基于Linux内核提供了虚拟化的能力。KVM使用QEMU(Quick Emulator)
作为虚拟机监视器,并通过硬件虚拟化扩展(如Intel的VT-x
和AMD的AMD-V
)提供硬件加速。
半虚拟化(Para-virtualization):
半虚拟化技术在虚拟机内部对操作系统进行修改,使其能够与虚拟化层进行通信和协作,从而提高性能和效率。代表产品有:
Xen
:Xen
是一个开源的半虚拟化解决方案,它可以在不修改操作系统的情况下运行虚拟机。Xen使用一种称为"Xen插入式内核"的方法,通过修改操作系统内核,使其与Hypervisor
进行通信,实现半虚拟化。
容器虚拟化(Containerization):
容器虚拟化技术通过在操作系统级别创建隔离的容器实例,实现应用程序和依赖的隔离。容器共享操作系统内核,因此比虚拟机更轻量级和高效。代表产品有:
- Docker:Docker是一种流行的容器化平台,它使用容器镜像(Images)来打包应用程序及其依赖,并通过Docker引擎在宿主机上运行容器实例。Docker提供了方便的构建、分发和部署工具,使得容器的使用变得简单和高效。
- LXC(Linux Containers):LXC是一种轻量级的容器化解决方案,它利用Linux内核中的命名空间(namespaces)和控制组(cgroups)等特性,实现了对进程的隔离。LXC提供了一个容器运行时环境,可以在其中运行独立的用户空间实例。
硬件辅助虚拟化(Hardware-assisted Virtualization):
硬件辅助虚拟化技术利用物理处理器中的虚拟化扩展,如Intel的VT-x和AMD的AMD-V,提供对虚拟化的硬件支持,提高虚拟机的性能和效率。上述的KVM和Xen也是硬件辅助虚拟化的解决方案。
轻量级虚拟化(Lightweight Virtualization):
轻量级虚拟化技术是一种特殊形式的虚拟化,它通过在操作系统级别利用命名空间(namespaces)和控制组(cgroups)等特性,实现对进程的隔离和资源控制。代表产品有:
- Docker:除了作为容器化平台,Docker也提供了一种轻量级的虚拟化方式。Docker容器可以在宿主机上以独立的进程运行,具有隔离的文件系统、网络和进程空间。
1.3、linux namespace 发展历史
Linux Namespace的发展历史可以追溯到2002年,最早是由Eric W. Biederman提出并实现。
以下是Linux Namespace的主要发展历程:
- 2002年:最早的Linux Namespace实现由Eric W. Biederman在2.4内核版本中引入,包括
Mount Namespace
和UTS Namespace
。 - 2006年:Eric W. Biederman和Serge E. Hallyn在2.6.24版本中引入
PID Namespace
,允许每个Namespace拥有独立的进程ID空间。 - 2008年:Eric W. Biederman和Serge E. Hallyn在2.6.29版本中引入
Network Namespace
,实现了独立的网络隔离。 - 2013年:Docker公司推出了Docker容器平台,基于Linux Namespace和Cgroups实现了轻量级的容器虚拟化技术,引发了容器技术的热潮。
- 2016年:Linux Kernel 4.6版本中引入了
IPC Namespace
和User Namespace
,分别实现了进程间通信和用户隔离。 - 2017年:Linux Kernel 4.11版本中引入了
CGROUP Namespace
,允许每个Namespace拥有独立的资源限制。
随着时间的推移,Linux Namespace逐渐成为Linux内核中的重要特性,为容器化技术的发展提供了基础。它提供了一种灵活且轻量级的隔离机制,使得可以在单个主机上创建多个独立的虚拟环境,实现了资源的隔离和管理。
1.4、linux namespace 作用
Linux Namespace的作用包括:
- 进程隔离:Linux Namespace可以将不同进程隔离开,每个进程在自己的Namespace中运行,不受其他进程的影响,具有独立的进程视图。
- 文件系统隔离:通过Mount Namespace,每个进程可以拥有自己的文件系统挂载点,实现文件系统的隔离。
- 网络隔离:通过Network Namespace,每个进程可以拥有独立的网络设备、IP地址、路由表和防火墙规则,实现网络的隔离。
- 进程间通信隔离:通过IPC Namespace,实现进程间通信(IPC)的隔离,使得不同的进程在不同的Namespace中无法直接通信。
- 用户和用户组隔离:通过User Namespace,每个进程可以拥有独立的用户和用户组视图,从而实现用户和用户组的隔离。
- 进程资源限制:通过PID Namespace和CGROUP Namespace,可以限制进程的资源使用,如CPU、内存、磁盘IO等,实现资源的管理和隔离。
通过使用Linux Namespace,可以在一个物理主机上创建多个独立的虚拟环境,每个环境都有自己的进程、文件系统、网络和资源限制,从而实现进程间的隔离和资源管理,提高系统的安全性和性能。
二、namespace 的主要函数
主要函数列表如下:
名称 | 作用 |
---|---|
clone() | 用于创建新进程并指定新的命名空间。可以使用clone() 函数的CLONE_NEWPID 、CLONE_NEWNS 、CLONE_NEWNET 、CLONE_NEWIPC 、CLONE_NEWUSER 等标志来创建新的PID、Mount、Network、IPC 或User Namespace 。 |
unshare() | 用于在运行中创建新的命名空间。可以使用unshare() 函数的CLONE_NEWPID 、CLONE_NEWNS 、CLONE_NEWNET 、CLONE_NEWIPC 、CLONE_NEWUSER 等标志来创建新的PID、Mount、Network、IPC 或User Namespace 。不同于clone() 函数,unshare() 函数不会创建新的进程,而是将当前进程切换到新的命名空间。 |
setns() | 用于将进程加入到已存在的命名空间中,实现命名空间的共享。可以通过指定命名空间的文件描述符和命名空间类型来将进程加入到相应的命名空间中。 |
mount() | 用于在指定的Mount Namespace 中挂载文件系统。可以指定挂载点、文件系统类型和挂载选项等参数来进行挂载操作。 |
umount() | 用于在指定的Mount Namespace中 卸载文件系统。可以指定要卸载的挂载点来完成卸载操作。 |
unshare(CLONE_NEWCGROUP) | 用于切换进程到一个新的CGROUP Namespace ,实现进程的资源限制和控制。 |
netns() | 用于创建或管理Network Namespace 。通过指定相应的命令行参数,可以创建、删除、查看或切换到不同的Network Namespace 。 |
ip netns命令 | 命令行工具用于创建、删除、查看或切换Network Namespace 。 |
ipc_namespace() | 用于创建或管理IPC Namespace 。 |
user_namespace() | 用于创建或管理User Namespace 。 |
2.1、clone()
clone()
函数是Linux中用于创建新进程的系统调用之一,它具有创建新的命名空间的能力。clone()
是在C
语言库中定义的一个封装(wrapper)
函数,它负责创建新进程的堆栈而且调用对编程者隐藏的clone()
系统调用。clone()
实际上是 linux 系统调用fork()
的一种更通用的实现方式,它能够经过flags
来控制使用多少功能。一共有 20 多种CLONE_
开头的falg(标志位)
参数用来控制 clone 进程的方方面面(好比是否与父进程共享虚拟内存等)。以下是clone()
函数的详细介绍:
#include <sched.h>int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...);
clone()
函数的第一个参数fn是一个函数指针,它指向新进程将要执行的函数。这个函数具有如下原型:int fn(void *arg)
。新进程将从指定函数的入口点开始执行。当这个函数返回时,子进程终止。该函数返回一个整数,表示子进程的退出代码。
child_stack
参数是新进程的栈,可以是已分配的内存空间,也可以是NULL
。如果child_stack
为NULL
,则新进程使用与父进程相同的栈。
flags
参数是一个位掩码,用于设置新进程的行为和属性。常用的标志包括:
CLONE_NEWPID
:创建新的PID Namespace
,使得新进程在单独的进程视图中运行,独立于父进程和其他进程。CLONE_NEWNS
:创建新的Mount Namespace
,使得新进程在单独的文件系统视图中运行,独立于父进程和其他进程。CLONE_NEWNET
:创建新的Network Namespace
,使得新进程在单独的网络视图中运行,独立于父进程和其他进程。CLONE_NEWIPC
:创建新的IPC Namespace
,使得新进程在单独的IPC资源视图中运行,独立于父进程和其他进程。CLONE_NEWUSER
:创建新的User Namespace
,使得新进程在单独的用户和用户组视图中运行,独立于父进程和其他进程。
arg
参数是传递给新进程的参数。
clone()
函数的返回值是新进程的ID(PID)
,如果出现错误,则返回-1
。
通过指定不同的标志位,可以在clone()函数中创建不同类型的命名空间,实现进程的隔离和资源管理。这为容器化技术的实现提供了基础。
2.2、setns()
函数
setns()
函数是Linux中用于将进程加入到已存在的命名空间中的系统调用之一。
#define _GNU_SOURCE
#include <sched.h>int setns(int fd, int nstype);
setns()
函数用于将进程加入到已存在的命名空间中。它接受两个参数:
fd
:一个打开的文件描述符,指向已存在的命名空间。这个文件描述符可以通过打开/proc/[pid]/ns/[namespace_type]
文件获取。namespace_type
表示命名空间的类型,如pid、mnt、net、ipc、uts
等。nstype
:指定要加入的命名空间的类型。这个值应与namespace_type
对应,例如CLONE_NEWPID
对应pid
,CLONE_NEWNS
对应mnt
,CLONE_NEWNET
对应net
等。
setns()
函数的返回值是一个整数,表示操作的成功与否。如果成功,则返回0
;否则返回-1
,并设置相应的错误码。
使用setns()
函数可以将当前进程加入到已存在的命名空间中,从而共享命名空间中的资源和上下文。这在某些场景中非常有用,特别是在容器化技术中,可以使多个容器共享相同的命名空间。在 docker 中,使用 docker exec
命令在已经运行着的容器中执行新的命令就须要用到 setns()
函数。
2.3、unshare()
函数
unshare()
函数可以用于使进程脱离指定的命名空间。通过调用unshare()
函数并指定相应的命名空间选项,可以将当前进程从指定的命名空间中分离出来,使其成为新命名空间的首个成员。
#define _GNU_SOURCE
#include <sched.h>int unshare(int flags);
flags
:指定要创建的命名空间的类型和相关选项。可以通过按位或运算符将多个选项组合在一起。常见的选项有:
CLONE_NEWPID
:创建新的PID命名空间。CLONE_NEWNET
:创建新的网络命名空间。CLONE_NEWNS
:创建新的挂载命名空间。CLONE_NEWIPC
:创建新的IPC命名空间。CLONE_NEWUTS
:创建新的UTS命名空间(用于主机名和域名)。
unshare()
函数的返回值是一个整数,表示操作的成功与否。如果成功,则返回0;否则返回-1,并设置相应的错误码。
使用unshare()
函数可以在当前进程中创建新的命名空间,并将其作为首个成员。这使得进程可以在新的命名空间中拥有独立的资源和上下文。这在容器化和隔离技术中非常有用,可以实现进程的隔离和资源隔离。
需要注意的是,unshare()
函数只能创建新的命名空间,而不能将进程加入到已存在的命名空间中。要将进程加入到已存在的命名空间中,可以使用 setns()
函数。
unshare
命令
unshare命令用于创建新的命名空间,并使当前进程成为新命名空间的首个成员。
unshare [options] [command [arguments...]]
unshare
命令可以带有一些选项和参数:
参数 | 解释 |
---|---|
-m 或 --mount | 创建新的挂载命名空间。 |
-u 或 --uts | 创建新的UTS命名空间。 |
-i 或 --ipc | 创建新的IPC命名空间。 |
-n 或 --net | 创建新的网络命名空间。 |
-p 或 --pid | 创建新的PID命名空间。 |
-U 或 --user | 创建新的用户命名空间。 |
-C 或 --cgroup | 创建新的控制组命名空间。 |
-f 或 --fork | 在创建新命名空间后,立即执行一个子命令(command)。 |
-r 或 --map-root-user | 将用户命名空间中的root用户映射到当前用户。 |
-s 或 --setgroups | 设置用户命名空间中的附加组。 |
-h 或 --help | 显示帮助信息。 |
unshare
命令用于创建新的命名空间,并使当前进程成为新命名空间的首个成员。可以通过选项来指定要创建的命名空间类型。在创建新命名空间后,还可以通过指定一个子命令来在新命名空间中执行特定的操作。
例如,执行以下命令将创建一个新的网络命名空间,并在该命名空间中执行bash命令:
$ unshare -n bash# 查找新建命名空间
$ ps -ef | grep bash
root 1 0 0 15:47 pts/0 00:00:00 /bin/bash
root 38 0 0 15:47 pts/1 00:00:00 /bin/bash
root 84 38 0 15:48 pts/1 00:00:00 bash
root 139 84 0 15:49 pts/1 00:00:00 grep --color=auto bash$ ls -l /proc/84/ns
total 0
lrwxrwxrwx 1 root root 0 Sep 4 15:49 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 ipc -> ipc:[4026533180]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 mnt -> mnt:[4026533178]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 net -> net:[4026534452]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 pid -> pid:[4026533181]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 pid_for_children -> pid:[4026533181]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 time -> time:[4026531834]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 time_for_children -> time:[4026531834]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Sep 4 15:49 uts -> uts:[4026533179]
执行该命令后,系统将创建一个新的网络命名空间,并将当前进程(包括子进程)置于该命名空间中。然后,将启动一个新的bash shell,并在新网络命名空间中执行。
通过使用unshare
命令,可以方便地创建新的命名空间,并在新命名空间中执行特定的操作。这对于进行进程隔离、资源隔离以及容器化等任务非常有用。
三、namespace 分类
Namespace 类型 | 系统调用参数 | Page | Isolates | 内核版本 |
---|---|---|---|---|
Mount | CLONE_NEWNS | mount_namespaces | Mount points 隔离文件系统挂载点 | 2.4.19 |
UTS | CLONE_NEWUTS | uts_namespaces | Hostname and NIS domain name 隔离主机名和域名信息 | 2.6.19 |
IPC | CLONE_NEWIPC | ipc_namespaces | System V IPC,POSIX message queues 隔离进程间通信 | 2.6.19 |
PID | CLONE_NEWPID | pid_namespaces | Process IDs 隔离进程的ID | 2.6.24 |
Network | CLONE_NEWNET | network_namespaces | Network devices,stacks, ports, etc. 隔离网络资源 | 2.6.29 |
User | CLONE_NEWUSER | user_namespaces | User and group IDs 隔离用户和用户组的ID | 3.8 |
Cgroup | CLONE_NEWCGROUP | cgroup_namespaces | Cgroup root directory | 4.6 |
Time | CLONE_NEWTIME | time_namespaces | Boot and monotonic clocks | 5.6 |
3.1、Mount Namespace
Mount Namespace
用来隔离文件系统的挂载点,不同Mount Namesace
的进程拥有不同的挂载点,同时也拥有了不同的文件系统视图。Mount Namespace
是历史上第一个支持的Namespace
。mount Namespace
为进程提供了一个文件层次视图,在Mount Namespace
中调用mount()
和umount()
仅仅只会影响当前Namespace内的文件系统,而对全局的文件系统是没有影响的。实际上,Mount Namespace
是基于chroot
的不断改良才被发明出来的,chroot
可以算是 Linux 中第一个 Namespace。那么上面被挂载在容器根目录上、用来为容器镜像提供隔离后执行环境的文件系统,就是所谓的容器镜像,也被叫做 rootfs(根文件系统)
。
3.1.1、chroot
介绍
chroot(Change Root)
通过修改当前进程的根目录,来创建一个新的文件系统环境。下面是chroot的原理介绍:
chroot系统调用:chroot命令是通过chroot系统调用来实现的。chroot系统调用的原型如下:
int chroot(const char *path);
该系统调用将进程的根目录更改为指定的目录。注意,只有具有足够特权的用户可以使用chroot命令,通常需要使用root用户或者具有sudo权限的用户。
修改根目录:chroot命令在执行时,会将指定的目录通过chroot系统调用传递给内核。内核会将当前进程的根目录更改为指定的目录,进程以这个目录作为新的根目录。
文件和目录访问:一旦chroot命令执行成功,进程以新的根目录作为基准进行文件和目录的访问。进程只能访问新的根目录及其子目录下的文件和目录,对于根目录以外的文件和目录则无法访问。
隔离环境:使用chroot命令创建的新的文件系统环境是隔离的,进程在这个环境中运行时无法访问或修改主机系统的文件和目录。这样可以提供一定程度的安全性和隔离性,特别是在系统修复、软件开发和测试等场景中。
需要注意的是,chroot只是修改了进程的根目录,并不能完全隔离进程。进程仍然可以通过其他方式访问和修改主机系统的资源。
3.1.2、rootfs
介绍
rootfs(根文件系统)
是Linux文件系统的最顶层,包含了操作系统的基本文件和目录结构。在引导过程中,rootfs是最早被挂载的文件系统,它是系统启动的基础。
rootfs通常以一个镜像文件或者一个设备文件的形式存在,它包含了操作系统所需的核心文件和目录,如/bin、/sbin、/etc
等。这些文件和目录是构成系统的基础,包括系统初始化脚本、核心命令等。
在Linux系统中,rootfs是只读的,它由操作系统提供并在系统启动时挂载到根目录("/")
。一旦系统启动,rootfs会被切换为可读写模式,此后可以通过写入其他文件系统来改变系统的状态。这种只读的rootfs设计可以保护系统的核心文件和目录,避免意外的修改和损坏。
在容器化技术中,每个容器都有自己的rootfs,容器的应用程序和依赖都存在于该文件系统中。容器可以通过挂载其他文件系统或目录来扩展其功能,但它们的根文件系统仍然是只读的,保证了容器的隔离性和安全性。
3.1.3、Mount Propagation
挂载传播
挂载传播决定了这个挂载操作对于其他进程的可见性和影响。挂载传播定义了挂载对象(mount object)之间的关系,系统利用这些关系决定任何挂载对象中的挂载事件传播到其它挂载对象。所谓传播事件,就是一个挂载对象状态变化导致的其它挂载对象的挂载与解除挂载动作的事件。
- 如果两个挂载对象具有共享关系(share relationship),那么一个挂载对象的挂载事件会传播到另一个挂载对象,反之亦然。
- 如果两个挂载对象形成从属关系(master slave),那么一个挂载对象的挂载事件会传播到另一个挂载对象,但反之不行。在这种关系中,从属对象是事件的接受者。
一个挂载状态可以为如下的其中一种:
- 共享状态(shared)
- 从属状态(slave)
- 共享/从属状态(shared and slave)
- 私有挂载(private)
- 不可绑定挂载(unbindable)
传播事件的挂载对象称为共享挂载(shared mount);接收传播事件的挂载对象称为从属挂载(slave mount)。既不传播也不接收传播事件的挂载对象称为私有挂载(private mount)。另一种特殊的挂载对象称为不可绑定的挂载(unbindable mount),它们与私有挂载相似,但是不允许执行绑定挂载,即创建 mount namespace 时这块文件对象不可被复制。
共享挂载的应用场景非常明显,就是为了文件数据的共享所必须的一种挂载方式;从属挂载更大的意义在于一些“只读”场景;私有挂载则是纯粹的隔离,作为独立个体存在;不可绑定挂载则有助于防止没必要的文件拷贝。
默认情况下,所有挂载都是私有的。从共享挂载克隆的挂载对象也是共享的挂载,它们互相传播挂载事件。
从属挂载克隆的挂载对象也是从属的挂载,它也从属于原来的从属挂载的主挂载对象。
mount --make-shared /mntS # 将挂载点设置为共享关系属性
mount --make-private /mntP # 将挂载点设置为私有关系属性
mount --make-slave /mntY # 将挂载点设置为从属关系属性
mount --make-unbindable /mntU # 将挂载点设置为不可绑定属性
3.1.4、挂载信息的查看
/proc/[pid]/mounts
文件中每行数据的含义为:
字段 | 含义 |
---|---|
挂载源(Source) | 表示文件系统的挂载点,可以是设备文件路径(如/dev/sda1 )、网络路径(如//server/share )或特殊文件系统(如proc、sysfs )。 |
挂载点(Mount Point) | 表示挂载源被挂载到的目录路径。 |
文件系统类型(Filesystem Type) | 指示挂载的文件系统类型,如ext4、tmpfs、proc、sysfs 等。 |
挂载选项(Mount Options) | 表示挂载时使用的选项,多个选项以逗号分隔。 |
使用的主设备号(Major Device Number) | 表示挂载设备的主设备号。仅适用于块设备文件。 |
使用的次设备号(Minor Device Number) | 表示挂载设备的次设备号。仅适用于块设备文件 |
/proc/[pid]/mounts
文件的内容可以提供有关特定进程的挂载信息,帮助用户了解进程所使用的文件系统和挂载选项等信息。可以通过读取该文件来获取进程的挂载点和文件系统类型等相关信息。
需要注意的是,/proc/[pid]/mounts
文件是只读的,只能用于查看挂载信息,不能修改其中的内容。
常见挂载选项:
选项 | 含义 |
---|---|
rw | 挂载为可读写的文件系统。可以在该文件系统上进行读取和写入操作。 |
ro | 挂载为只读的文件系统。只能在该文件系统上进行读取操作,不能进行写入操作。 |
relatime | 更新访问时间,但仅在访问时间超过修改时间时更新。相较于atime选项,relatime选项减少了对文件系统的访问次数,提高了性能。 |
async | 异步写入。文件系统将写入操作放入缓冲区,然后立即返回,而不等待写入操作完成。 |
sync | 同步写入。文件系统在执行写入操作时会等待写入操作完成,然后再返回。 |
noexec | 不允许在该文件系统上执行可执行文件。即禁止在该文件系统上运行程序。 |
nodev | 不允许在该文件系统上创建设备文件。即禁止在该文件系统上创建字符设备或块设备。 |
nosuid | 禁止设置setuid和setgid位。即禁止在该文件系统上设置可执行文件的setuid和setgid权限。 |
noatime | 不更新访问时间。即不会更新文件或目录的访问时间戳。 |
nodiratime | 不更新目录的访问时间。即只有文件的访问时间会被更新。 |
index | Btrfs文件系统中用于禁用或启用文件系统的索引功能 |
metacopy | Btrfs文件系统中用于启用元数据拷贝功能 |
/proc/[pid]/mountinfo
字段 | 含义 |
---|---|
Mount ID | 挂载的ID号,用于唯一标识每个挂载点。 |
Parent ID | 父挂载点的ID号,表示当前挂载点的父级挂载点。 |
Major:Minor | 挂载的设备的主次设备号。 |
Root | 挂载点的根目录。 |
Mount Point | 设备或文件系统被挂载到的目标路径。 |
Mount Options | 挂载时使用的选项,多个选项以逗号分隔。 |
Optional Fields | 可选字段,可能包含一些附加的挂载信息,如安全标签(security label)、备份目录(backup directory)等。 |
Filesystem Type | 文件系统的类型,如ext4、tmpfs、proc等。 |
Mount Source | 设备或文件系统的源路径。 |
Super Options | 超级选项,用于指定与挂载点相关的额外选项。 |
Subtree Options | 子树选项,用于指定与挂载点子树相关的选项。 |
proc/[pid]/mountinfo
文件提供了有关挂载点的详细信息,包括设备、文件系统类型、挂载路径、挂载选项等。通过读取该文件,可以了解系统中的挂载点及其相关信息,对于了解文件系统的结构和配置非常有用。
/proc/[pid]/mountstats
字段 | 含义 |
---|---|
device | 挂载设备的路径或标识符。 |
path | 挂载点的路径。 |
type | 文件系统类型。 |
mountinfo | 与挂载点相关的详细信息的文件路径。 |
mountsource | 挂载点的源路径。 |
superoptions | 超级选项,用于指定与挂载点相关的额外选项。 |
options | 挂载选项,用于指定挂载时使用的选项。 |
age | 挂载与卸载之间的时间差,以秒为单位。 |
opts | 挂载选项的统计信息。 |
mount_time | 挂载点的挂载时间,以纳秒为单位。 |
umount_time | 挂载点的卸载时间,以纳秒为单位。 |
num_mounts | 挂载点的总挂载次数。 |
num_mnt_errs | 挂载点的挂载错误次数。 |
num_mount_errors | 挂载点的严重错误次数。 |
num_mounts_succeed | 挂载点的成功挂载次数。 |
num_umounts | 挂载点的总卸载次数。 |
num_umount_errors | 挂载点的卸载错误次数。 |
/proc/[pid]/mountstats
文件提供了有关挂载点的统计信息,包括挂载次数、挂载选项的使用情况、挂载时间等。通过读取该文件,可以了解挂载点的使用情况和性能统计,对于监控和调优文件系统的挂载点非常有用。
3.5、User Namespaces
用户命名空间(User Namespace)用于隔离用户和用户组的视图。它允许在一个命名空间中重新映射用户和用户组的ID,从而实现用户和用户组的隔离和管理。User namespace 主要隔离了安全相关的标识符(identifiers)和属性(attributes),包括用户ID、用户组ID、root目录、 key(指密钥)以及特殊权限
。说得通俗一点,一个普通用户的进程通过clone() 创建的新进程在新user namespace 中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是他创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。用户命名空间的主要目的是提供更安全的环境,使不同用户在同一系统上运行时能够相互隔离,避免权限冲突和攻击。以下是用户命名空间的一些重要概念和特性:
用户ID映射:用户命名空间允许重新映射用户ID(UID)和组ID(GID)到不同的值,称为用户ID映射。这样做可以在命名空间中创建和管理独立的用户和用户组。例如,一个用户在一个用户命名空间中可能有ID为0的特权用户,但在全局命名空间中却没有这个特权。
用户权限隔离:用户命名空间隔离了用户和用户组的权限,使得在不同的命名空间中用户的权限不会影响其他命名空间的用户。这提供了更好的安全性和隔离性,防止不同用户之间的权限冲突,以及减少攻击面。
文件系统隔离:用户命名空间还可以隔离文件系统的视图,使得在不同命名空间中的用户可以有独立的文件系统,并且对文件和目录的访问权限不会相互干扰。
容器化和虚拟化:用户命名空间是实现容器化和虚拟化的关键组成部分。它允许不同的容器或虚拟机在同一主机上运行,每个容器或虚拟机都有自己独立的用户和用户组。
3.5.1、查看user ns方法:
$ lsns -t userNS TYPE NPROCS PID USER COMMAND
4026531837 user 263 1 root /usr/lib/systemd/systemd --system --deserialize 17$ ls /proc/1/ns/user
/proc/1/ns/user$ ll /proc/1/ns/user
lrwxrwxrwx 1 root root 0 Sep 1 10:21 /proc/1/ns/user -> user:[4026531837]$ readlink /proc/1/ns/user
user:[4026531837]
3.5.2、User Namespaces的创建和销毁
# 使用unshare -U /bin/bash 创建新的shell会话
$ lsns -t userNS TYPE NPROCS PID USER COMMAND
4026531837 user 3 1 root /bin/bash[root@ce31e508d31c /]
$ unshare -U sh
whoami: cannot find name for user ID 65534[I have no name!@ce31e508d31c /]
$ sudo ip netns add test
sudo: /etc/sudo.conf is owned by uid 65534, should be 0
sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set
whoami: cannot find name for user ID 65534[I have no name!@ce31e508d31c /]
$ lsns -t userNS TYPE NPROCS PID USER COMMAND
4026534745 user 2 155 65534 sh
whoami: cannot find name for user ID 65534
具有足够权限的用户(如root用户)才能销毁用户命名空间。
# root用户下查看
$ lsns -t userNS TYPE NPROCS PID USER COMMAND
4026531837 user 261 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026534745 user 1 613 root sh$ kill -9 613$ lsns -t userNS TYPE NPROCS PID USER COMMAND
4026531837 user 263 1 root /usr/lib/systemd/systemd --system --deserialize 17
四、namespace 生命周期
每个命名空间都有自己的生命周期,可以创建、运行、销毁和释放。
下面是Linux命名空间的典型生命周期:
1、创建命名空间:
使用系统调用(如clone()
或unshare()
)创建新的命名空间。通过指定不同的标识符,可以创建各种类型的命名空间,例如PID命名空间、网络命名空间、挂载命名空间等。
2、运行命名空间:
在创建命名空间后,可以将进程加入到该命名空间中。通过调用setns()
系统调用或使用nsenter
命令,可以将进程从父命名空间切换到新的命名空间。在命名空间中,进程可以访问和修改属于该命名空间的资源。
3、销毁命名空间:
当不再需要命名空间时,可以将其销毁。通过调用unshare()
系统调用或使用ip netns delete
等命令,可以销毁命名空间。销毁命名空间将释放该命名空间所占用的资源,并将其中的进程重新归入到原始的父命名空间中。
需要注意的是,命名空间可以被继承和共享。例如,一个进程创建了一个新的PID命名空间,并在其中启动了一个子进程,那么该子进程将成为新命名空间的一部分。此外,命名空间可以通过不同的手段进行通信和共享资源,如Unix域套接字或Mount命名空间的绑定挂载点。
总结起来,Linux命名空间的生命周期涉及创建、运行、销毁和释放操作,通过这些操作可以实现进程资源的隔离和管理。
命名空间可能不会被销毁的情况
1、进程仍在命名空间中运行:
如果有一个或多个进程仍在使用命名空间,并且没有退出或切换到其他命名空间,那么该命名空间将保持存在。只有在最后一个进程退出或切换到其他命名空间时,命名空间才会被销毁。
2、子命名空间的存在:
如果一个命名空间是另一个命名空间的子命名空间,并且子命名空间仍然活跃,那么父命名空间将一直保持存在。只有当父命名空间和所有子命名空间中的进程都退出或切换到其他命名空间时,该命名空间才会被销毁。
3、共享命名空间:
如果一个命名空间被多个进程共享,并且这些进程仍在活跃状态,那么该命名空间将一直保持存在。只有当最后一个共享此命名空间的进程退出或切换到其他命名空间时,该命名空间才会被销毁。
需要注意的是,命名空间的生命周期取决于其中的进程和命名空间之间的关系。只要还有进程活跃或者仍有命名空间之间的继承或共享关系存在,命名空间就会被保留下来。因此,确保在不再需要使用命名空间时进行适当的清理操作非常重要,以避免资源的浪费和潜在的问题。
五、查看进程所属的 namespace
5.1、lsns
lsns
命令不需要任何参数,它会列出当前系统上存在的所有命名空间的信息。每个命名空间都有一行输出,包含以下信息:
NS TYPE
:命名空间的类型,如pid、mnt、net、ipc、uts、cgroup
等。NS ID
:命名空间的ID,用于标识不同的命名空间。NPROCS
:命名空间中当前运行的进程数。FLAGS
:命名空间的标志位,表示命名空间的属性和状态。
lsns
命令的一些常用选项包括:
-a
或--all
:显示所有命名空间,包括未被引用的命名空间。-t TYPE
或--type TYPE
:只显示指定类型的命名空间。-n NAMESPACE
或--namespace NAMESPACE
:只显示指定命名空间ID或名称的命名空间信息。
$ lsnsNS TYPE NPROCS PID USER COMMAND
4026531836 pid 217 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026531837 user 262 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026531838 uts 221 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026531839 ipc 217 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026531840 mnt 206 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026531860 mnt 1 50 root kdevtmpfs
4026531992 net 227 1 root /usr/lib/systemd/systemd --system --deserialize 17
4026532380 mnt 9 68131 root nginx: master process /usr/sbin/nginx
4026532384 mnt 1 79144 root /pause
4026532385 uts 1 79144 root /pause
4026532386 ipc 2 79144 root /pause
4026532387 pid 1 79144 root /pause
4026532389 net 2 79144 root /pause
4026532427 mnt 1 9682 ntp /usr/sbin/ntpd -u ntp:ntp -g
4026532464 mnt 1 79146 root /pause
4026532466 uts 1 79146 root /pause
5.2、ls -l /proc/[pid]/ns
$ ls -l /proc/99997/ns
total 0
lrwxrwxrwx 1 root root 0 Sep 4 14:31 cgroup -> cgroup:[4026531835]
lrwxrwxrwx 1 root root 0 Sep 1 10:21 ipc -> ipc:[4026532661]
lrwxrwxrwx 1 root root 0 Sep 1 10:21 mnt -> mnt:[4026532659]
lrwxrwxrwx 1 root root 0 Sep 1 10:21 net -> net:[4026532664]
lrwxrwxrwx 1 root root 0 Sep 1 10:21 pid -> pid:[4026532662]
lrwxrwxrwx 1 root root 0 Sep 4 14:31 pid_for_children -> pid:[4026532662]
lrwxrwxrwx 1 root root 0 Sep 4 14:31 time -> time:[4026531834]
lrwxrwxrwx 1 root root 0 Sep 4 14:31 time_for_children -> time:[4026531834]
lrwxrwxrwx 1 root root 0 Sep 1 10:21 user -> user:[4026531837]
lrwxrwxrwx 1 root root 0 Sep 1 10:21 uts -> uts:[4026532660]
每行输出中,第一个字段表示符号链接的权限和属性,第九个字段表示符号链接的目标。目标的格式为[type]:[namespace_id]
,其中type表示命名空间的类型,namespace_id则是命名空间的ID。
通过执行ls -l /proc/[pid]/ns命令,可以查看特定进程所属的所有命名空间的符号链接。这样可以了解进程当前所在的命名空间以及命名空间的类型和ID。
5.3、pstree -p
pstree -p命令用于以树状结构显示进程及其子进程,并显示它们的PID(进程ID)。
$ pstree -p | grep nginx|-nginx(68131)-+-nginx(68132)| |-nginx(68133)| |-nginx(68134)| |-nginx(68135)| |-nginx(68136)| |-nginx(68137)| |-nginx(68139)| `-nginx(68140)
5.4、ip netns list
参考文档
1、https://blog.csdn.net/y3over/article/details/128863060
2、https://www.cnblogs.com/sally-zhou/p/13398260.html
3、http://www.taodudu.cc/news/show-320037.html?action=onClick
4、http://www.360doc.com/content/21/0803/11/31115656_989326901.shtml
5、http://www.noobyard.com/article/p-nqmbazhv-s.html
6、https://www.cnblogs.com/sparkdev/p/8214455.html
7、https://blog.csdn.net/key_3_feng/article/details/129942638