在初步了解了docker之后,我希望通过了解docker背后的技术原理来深入学习和使用docker。接下来的几篇文章简要介绍了linux命名空间的概念和基本用法。
命名空间概念
命名空间是Linux内核用于隔离内核资源的方式。通过命名空间,某些进程只能看到与自身相关的部分资源,而其他进程只能看到与自身相关的资源。这两个过程根本不会感觉到另一方的存在。具体实现是指定同一名称空间中的一个或多个进程的相关资源。
Linux命名空间是对全局系统资源的一种封装,因此不同命名空间中的进程具有独立的全局系统资源。更改命名空间中的系统资源只会影响当前命名空间中的进程,并且不会影响其他命名空间中的进程。 。
使用命名空间
也许像我这样的绝大多数用户在使用docker之后开始了解Linux的命名空间技术。实际上,Linux内核实现命名空间的主要目的之一是实现轻量级虚拟化(容器)服务。同一命名空间中的进程可以在不知道外部进程的情况下感知彼此的更改。这允许容器中的进程产生错觉,即您处于单独的系统中以实现隔离。换句话说,Linux内核提供的命名空间技术为docker等容器技术的出现和发展提供了基本条件。
我们可以从docker实现者的角度考虑如何实现资源隔离的容器。例如,是否可以通过使用chroot命令切换根目录的安装点来隔离文件系统。为了在分布式环境中进行通信和定位,容器必须具有单独的IP,端口和路由等,这需要隔离网络。同时,容器需要单独的主机名来在网络上标识自己。接下来,需要在进程,用户权限等之间进行隔离。最后,在容器中运行的应用程序需要有一个进程号(PID),它自然需要与主机中的PID隔离。换句话说,这六种隔离功能是实现容器的基础。让我们看一下Linux内核命名空间功能的隔离功能:第二,将这些链接文件放在/proc/[pid]/ns中的另一个影响是,一旦打开这些链接文件,只要打开文件描述符(fd)存在,那么即使命名空间下的所有进程都是最后,命名空间将始终存在,并且可以添加后续进程。
除了打开文件的方式之外,我们还可以通过文件的装载方式防止命名空间被删除。例如,我们可以将当前进程中的uts挂载到〜/uts文件:
$ touch~/uts
$ sudo mount --bind/proc/$$/ns/uts~/uts
使用stat命令检查结果:
令人惊讶的是,〜/uts的inode与链接文件中的inode编号相同,它们是同一个文件。
克隆功能
我们可以在使用clone()创建新进程的同时创建命名空间。 C库中clone()的声明如下:
/* glibc包装函数的原型* /
#define _GNU_SOURCE
#包括
Int clone(int(* fn)(void *),void * child_stack,int flags,void * arg);
实际上,clone()是在C库中定义的包装函数,它负责构建新进程的堆栈并调用对程序员隐藏的clone()系统调用。 Clone()实际上是linux系统调用fork()的更通用的实现,它可以控制flags使用了多少功能。有超过20种falg(flag)参数以CLONE_开头来控制克隆过程的所有方面(例如是否与父进程共享虚拟内存等)。下面我们只介绍与命名空间相关的四个参数:
Fn:指定新进程要执行的函数。当此函数返回时,子进程终止。此函数返回一个表示子进程的退出代码的整数。
Child_stack:传入子进程使用的堆栈空间,即用户状态堆栈指针被分配给子进程的esp寄存器。调用进程(调用clone()的进程)应始终为子进程分配一个新堆栈。标志:指示用于以CLONE_开头的标志,以及与命名空间相关的CLONE_NEWIPC,CLONE_NEWNET,CLONE_NEWNS,CLONE_NEWPID,CLONE_NEWUSER,CLONE_NEWUTS和CLONE_NEWCGROUP。
Arg:指向传递给fn()函数的参数。
在后续文章中,我们主要通过clone()函数创建并演示了各种类型的命名空间。
设定功能
可以通过setns()函数将当前进程添加到现有命名空间。 C库中setns()的声明如下:
#define _GNU_SOURCE
#包括
Int setns(int fd,int nstype);
与clone()函数一样,C库中的setns()函数也是setns()系统调用的包装器:
Fd:表示要添加到命名空间的文件描述符。它是指向/proc/[pid]/ns目录中文件的文件描述符,可以通过直接打开该目录中的链接文件或打开在该目录中安装链接文件的文件来获取该文件描述符。
Nstype:参数nstype允许调用者检查fd指向的命名空间类型是否满足实际要求。将此参数设置为0表示不检查。
如前所述,可以通过挂载来保留命名空间。保留命名空间的目的是准备稍后为其添加命名空间的过程。在docker中,使用docker exec命令在已经运行的容器中执行新命令需要setns()函数。为了利用新添加的命名空间,还需要引入execve()系列函数(我在《Linux 创建子进程执行任务》中介绍了execve()系列函数,有兴趣的同学可以去了解),这个函数可以执行用户更常用的命令是调用/bin/bash并接受运行shell的参数。
取消共享功能和命令
命名空间隔离可以通过unshare函数在原始进程上完成。也就是说,创建并添加新的命名空间。 C库中的unshare()声明如下:#define _GNU_SOURCE
#包括
Int unshare(int flags);
与前两个函数一样,C库中的unshare()函数也是unshare()系统调用的包装器。调用unshare()的主要功能是在不启动新进程的情况下禁用资源隔离,这相当于跳出原始名称空间。