前言

本文将主要介绍容器中资源隔离的相关概念,其中主要会涉及到Linux NameSpace的相关内容。 笔者也会将自己的理解在文中进行阐述,这也算是在和大家交流心得的一个过程。若文中有错误的理解和概念,请大家及时纠正;吸纳大家的建议,对于我来说也是很重要的学习过程之一。


(目录)


1.Linux NameSpace

1.1 概念

Linux NameSpace可以看作是一种隔离机制。其主要目的是隔离运行在同一台Linux宿主机上多个程序(进程/容器),使这些程序之间不能访问彼此的资源

这种隔离资源的操作,可以为Linux带来两个好处:

  1. 充分利用系统资源 即尽可能的在Linux宿主机运行更多的进程
  2. 安全性 这种隔离操作使不同程序(进程/容器/用户)之间不能访问对方的资源,这样就保证了每个进程的运行安全。

Linux NameSpace往往会以进程组的维度进行资源隔离,Linux NameSpace将全局共享的资源划分为多组进程间共享的资源

Tips: 若一个Linux NameSpace中的所有进程都已退出,则该Linux NameSpace也会被销毁。

需要注意的是,Linux NameSpace并不能对Linux宿主机中的所有资源实现隔离。例如在 Linux 内核中有很多资源和对象(例如时间)是不能被Namespace化的。 基于上述事实,运行在同一台Linux宿主机中进程/容器实际上还是共用了这台宿主机的操作系统内核。这一点对于容器的安全就会带来一些挑战。

Tips: 相比较而言,虚拟化技术在这一点上是强于Linux NameSpace的。

1.2 相关Linux系统调用

由于Linux中有多种类型的资源,在Linux NameSpace中也相对应的有多种类型的NameSpace来用于管理隔离这些资源。 donain name.jpg

因为Linux NameSpace是在隔离进程,因此关于NameSpace的系统调用大多数都是和进程相关的Linux内核函数。 每种类型Linux NameSpace所对应的系统调用参数(图中的Flag),实际上就是用在调用以下这些内核方法/函数时所需要传入的参数。

1.2.1 clone

clone方法用来创建新进程。 根据传入上面的不同Linux NameSpace类型来创建不同的Linux NameSpace进行隔离,并且将新进程加入到新建的Linux NameSpace中。同时,对应的子进程也会被包含到这些Linux Namespace中。 例如:

// flags就是标志用来描述需要从父进程继承哪些资源
// flags参数可以为一个或多个
int clone(int (*child_func)(void *), void *child_stac, int flags, void *arg);

1.2.2 unshare

unshare方法可将进程移出某个指定类型的Linux Namespace,并加入到新创建的Linux NameSpace 中。 容器中Linux NameSpace也是通过 unshare 系统调用创建的。 例如:

// flag含义与clone方法中的flag参数相同
int unshare(int flags);

1.2.3 setns

setns方法可将进程加入到Linux Namespace 中。 例如:

// fd:加入的NameSpace,指向/proc/[pid]/ns/目录里相应NameSpace对应的文件
// nstype:NameSpace类型
int setns(int fd, int nstype);

Tips: docker exec 的实现原理正是基于系统调用setns。


2.容器资源隔离的实现

在Linux中,主要是依靠Linux NameSpace来对运行在同一台Linux宿主机上的多个容器进行资源隔离的。

Tips: 实际上每个容器都是一个特殊的进程,因此对于容器的资源隔离操作实际上就是对进程的资源隔离操作。

对于不同类型的资源,Container Runtime会使用对应的NameSpace来实现资源的隔离。

2.1 进程隔离

PID NameSpace用于隔离不同容器内的进程。 隔离后的容器(NameSpace)内部就如同一台单独的Linux系统,每个容器(NameSpace)都有自己的初始化进程(PID为1)作为所有进程的父进程,而其他进程的 PID 会依次递增。即PID NameSpace使得容器拥有自己独立的一套pid,拥有独立的init1号进程9a289599c355210484c11fd1959572a2.png.webp 通过PID Namespace对容器进行进程隔离后,每个容器内的进程就不会互相干扰(例如进程号重复);同时对于每个容器的管理也会变的相对轻松,即容器外界可通过与容器内部的父进程/主进程交互来管理容器内部

2.2 用户空间隔离

User NameSpace用于对容器内的用户空间进行隔离。 隔离后的容器(NameSpace)内部就会有独立的 UID、GID 以及根目录等。即可在每个容器内进行独立的用户管理,而不是使用Linux宿主机上的用户管理体系(宿主机看不到容器内部的用户)。 创建容器的User NameSpaces是通过Linux宿主机上的某一个用户来进行操作的,此时该用户就会拥有双重身份:

  1. 在容器内 该用户拥有root权限,即为根用户。
  2. 在Linux宿主机内 依旧还是维持本身的相关权限以及相关配置。

2.3 UTS隔离

UTS NameSpace用于对容器内的UTS进行隔离。 UTS NameSpace能够保证每个容器都有独立的主机名或域名,即用来隔离hostname 和 NIS Domain name 两个系统标识。通过此隔离手段就可以实现不同容器之间能够使用不同的主机名0de2d34a0155f944d2c27eaf7317e8f3.png.webp

2.4 目录隔离

Mount NameSpace用于隔离容器内部的目录挂载点/路径。 Container Runtime利用 Mount NameSpace实现了容器自身的根文件系统完全独立于宿主机上的根文件系统。即Mount NameSpace保证了每个容器都有自己独立的文件目录结构。

MNT namespace2.png

Tips: 这里要注意的是,Mount NameSpace修改的是容器进程对文件系统“挂载点”的视图。也就是说只有在“挂载”这个操作发生之后,进程的视图才会被改变。即Mount NameSpace所实现的资源隔离,一定是伴随着挂载操作(mount)才能生效的

2.5 网络隔离

NET Namespace用于保障每个容器有独立的网络栈、socket 和网卡设备。 由于NET NameSpace隔离了和网络有关的资源,如网络设备、IP 地址端口等,因此其可以让每个容器拥有自己独立的(虚拟的)网络设备,而且容器内的应用可以绑定到自己的端口,每个 Namespace 内的端口都不会互相冲突。 0.0.0.080.png

2.6 进程通信隔离

IPC Namespace保障每个容器进程 IPC 通信隔离

IPC Namespace用来隔离 System V IPC 和 POSIX message queues,只有在相同 IPC 命名空间的容器进程之间才可以共享内存、信号量、消息队列通信。因此IPC Namespace让容器拥有自己的共享内存和信号量来实现进程间通讯(IPC)。 截屏2023-09-08 15.04.40.png

2.7 Cgroup隔离

Cgroup Namespace保障容器中看到的 cgroup 视图,像宿主机一样以根形式来呈现,同时让容器内使用 cgroup 变得更安全。