Docker容器核心技术:Linux命名空间Namespaces、控制组cgroups、联合文件系统UnionFS

1.容器的本质——进程

Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的 cgroup,namespace,以及 OverlayFS 类的 Union FS 等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。

由上可知,容器是独立于宿主和其它的隔离的进程的进程,即容器的本质是进程,而这也是容器与虚拟机的最本质区别。容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。

2.Namespaces命名空间:隔离容器

2.1 Namespaces命名空间介绍

命名空间是 Linux 内核的一个强大特性,它可以将不同的进程或容器隔离开来,使它们看起来像是在独立的操作系统中运行一样。在 Docker 中,每个容器都有自己单独的命名空间,其中包括 pid、net、ipc、mnt、uts 和 user 命名空间。

  • pid 命名空间:隔离不同用户的进程,每个命名空间中可以有相同的 pid。系统调用参数为CLONE_NEWPID
  • net 命名空间:隔离网络端口、设备、协议栈,每个命名空间有独立的网络设备、IP 地址、路由表和 /proc/net 目录。系统调用参数为CLONE_NEWNET
  • ipc 命名空间:隔离进程间通信,如信号量、消息队列和共享内存,每个 IPC 资源有一个唯一的 32 位 id。系统调用参数为CLONE_NEWIPC
  • mnt 命名空间:隔离文件系统,每个命名空间中的进程所看到的文件目录被隔离开了。系统调用参数为CLONE_NEWNS
  • uts 命名空间:隔离主机名和域名,使容器在网络上可以被视作一个独立的节点。系统调用参数为CLONE_NEWUTS
  • user 命名空间:隔离用户和组 id,使容器内部的用户可以执行程序而非主机上的用户。系统调用参数为CLONE_NEWUSER

很多编程语言都包含了命名空间的概念,这是一种封装,它可以实现代码的隔离。

在操作系统中,命名空间提供的是系统资源的隔离,包括进程、网络、文件系统等。在同一个命名空间下的进程可以感知彼此的变化,而对其他命名空间的进程一无所知,这样就可以让容器中的进程产生一个错觉,仿佛他自己置身于一个独立的系统环境当中,以此达到独立和隔离的目的。

创建容器进程时,默认情况下,Docker会为每个容器创建一个新的命名空间,这是为了确保容器之间的隔离性和安全性。这意味着每个容器都有自己的PID、IPC、UTS、网络和挂载点命名空间,这些命名空间是相互隔离的,不会相互干扰。这样,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。通过命名空间的隔离,容器之间互不影响,提高了容器的安全性和可靠性。

2.2 namespace API操作

主要是三个系统调用

  • clone() :实现线程的系统调用,用来创建一个新的进程,并可以通过设计参数达到隔离。
  • unshare() : 使某进程脱离某个namespace
  • setns() : 把某进程加入到某个namespace

2.3 Docker指定namespace

Docker run命令有几个参数与命名空间相关。以下是这些参数的简要说明:

  1. --ipc:指定容器使用的IPC命名空间。IPC命名空间用于控制进程间通信(IPC)机制,如共享内存和信号量。

  2. --pid:指定容器使用的PID命名空间。PID命名空间用于控制进程ID(PID)的范围,以便容器中的进程只能看到其自己的PID和其子进程的PID。

  3. --userns:指定容器使用的用户命名空间。用户命名空间用于控制容器中的用户和组ID的范围,以便容器中的进程只能看到其己的用户和组ID。

  4. --uts:指定容器使用的UTS命名空间。UTS命名空间用于控制主机名和域名的范围,以便容器中的进程只能看到其自己的主机名和域名。

  5. --userns-remap=default|uid:gid|user:group|user|uid:指定容器的用户命名空间,默认是创建新的 UID 和 GID 映射到容器内进程。

如果您不指定这些参数,创建容器进程时,默认情况下,Docker会为每个容器创建一个新的命名空间,这是为了确保容器之间的隔离性和安全性。这意味着每个容器都有自己的PID、IPC、UTS、网络和挂载点命名空间,这些命名空间是相互隔离的,不会相互干扰。这样,容器就只能“看”到当前 Namespace 所限定的资源、文件、设备、状态,或者配置。通过命名空间的隔离,容器之间互不影响,提高了容器的安全性和可靠性。

3.cgroups——Control groups控制组:资源分配

3.1 cgroups介绍

虽然容器使用了 NameSpace 技术进行进程隔离,但是它们仍然与宿主机上的进处于同等级别的资源竞争关系。这就需要使用 Linux Control groups进行资源限制,它最主要的作用,就是限制一个进程组能够使用的资源上限,包括 CPU、内存、磁盘、网络带宽等等。

  • 任务(Tasks):就是系统的一个进程。
  • 控制组(Control Group):一组按照某种标准划分的进程,其表示了某进程组,Cgroups中的资源控制都是以控制组为单位实现,一个进程可以加入到某个控制组。而资源的限制是定义在这个组上,简单点说,cgroup的呈现就是一个目录带一系列的可配置文件。
  • 层级(Hierarchy):控制组可以组织成hierarchical的形式,既一颗控制组的树(目录结构)。控制组树上的子节点继承父结点的属性。简单点说,hierarchy就是在一个或多个子系统上的cgroups目录树。
  • 子系统(Subsystem):一个子系统就是一个资源控制器,比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用,一个子系统附加到某个层级以后,这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以根据不同的需求进行配置和使用,比如限制CPU使用、限制内存使用、限制磁盘等等。每个子系统都有自己的特点和用途,可以根据具体的场景进行选择和配置,如下所示:
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/36960c789f5042368e3325acf181def6.png

3.2 cgroups操作接口

Linux 中,Cgroups 给用户暴露出来的操作接口是文件系统,它以文件和目录的方式组织在 /sys/fs/cgroup 路径下,有很多诸如 cpuset、cpu、 memory 这样的子目录,即子系统。这些是可以被 Cgroups 进行限制的资源种类。例如查看PU限制目录:
在这里插入图片描述
cfs_period 和 cfs_quota两个可能不会陌生,这两个参数需要组合使用,可以用来限制进程在长度为cfs_period 的一段时间内,只能被分配到总量为cfs_quota 的 CPU 时间。
在这里插入图片描述
cpu.cfs_quota_us值为-1代表没有任何限制,cpu.cfs_period_us 则是默认的100000us ,即100 ms。

下面将向cpu_limit_demo控制组的cpu.cfs_quota_us文件写入50ms即50000us,这表示在100ms周期内,cpu最多使用%50,同时将进程的pid号为10069写入对应的tasks文件,表示对进程进行CPU限制:
在这里插入图片描述

于是pid为10069的进程cpu就被限制成了%50.0,此时利用top命令在此查看cpu使用情况:在这里插入图片描述

3.3 cgroups限制Docker资源

默认情况下,Docker会在需要限制的子系统下创建一个目录为docker的控制组,当容器运行后,会在这些目录生成以容器ID为目录的子目录用于限制的容器资源。

docker主要提供两种类别的资源限制:CPU和内存,通过docker run 时指定参数实现。cpu限制资源限制有多种维度,以下为限制cpu配额为25%的代码示例:

docker run -d --cpu-period=100000 --cpu-quota=250000 --name test-c1 nginx:latest

其余有关cgroups实现Docker资源限制的命令可以参考:https://www.cnblogs.com/wdliu/p/10509045.html

4.UnionFS联合文件系统:

4.1UnionFS介绍

UnionFS(Union File System)是一种堆叠式文件系统,它可以把多个目录内容联合挂载到同一个目录下,而目录的物位置是分开的。UnionFS应用的地方很多,比如在多个磁盘分区上合并不同文件系统的主目录,或把几张CD光盘合并成一个统一的光盘目录(归档)。以下是UnionFS的一些特点:

  • 可以将不同目录挂载到同一个虚拟文件系统下,从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录。如下所示
    在这里插入图片描述在这里插入图片描述

  • 支持为每一个成员目录设定 readonly、readwrite 和 whiteout-able 权限。

  • 具有写时复制(copy-on-write)功能,允许只读文件系统的修改可以保存到可写文件系统当中。

  • 文件系统分层,对 readonly 权限的 branch 可以逻辑上进行修改(增量地, 不影响 readonly 部分的)。

4.2 Docker镜像加载原理

容器的非运行态叫做镜像,是一种轻量级、可执行的独立软件包,用来打包软件运行环境和基于运行环境开发的软件,它包含运行某个软件所需的所有内容,包括代码、运行时库、环境变量和配置文件。
在Docker中,UnionFS被用来实现镜像的分层存储,docker的镜像实际上由一层一层的文件系统组成。
在这里插入图片描述

  • bootfs(boot file system) 主要包含bootloader 和 kernel,bootloader主要引导加载kernel,linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层时bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此使内存的使用权已由bootfs转交给内核,此使系统也会卸载bootfs。

  • rootfs(root file system) ,在bootfs之上。包含的就是典型Linux系统中的/dev、/proc、/bin、/etc等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,CentOS等等

  • 将中间只读的 rootfs 的集合称为 Docker 镜像,Docker 镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。UnionFS 使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

  • 当用docker run启动这个容器时,实际上在镜像的顶部添加了一个新的可写层,这个可写层也叫容器层,容器层之下的都叫镜像层。容器启动后,其内的应用所有对容器的改动,文件的增删改操作都只会发生在容器层中,对容器层下面的所有只读镜像层没有影响,Docker 镜像层都是只读的。

  • 每个镜像都由多个只读层和一个可读写层组成。只读层包含了镜像的文件系统,而可读写层则包含了容器运行时的文件系统。当容器启动时,Docker会将只读层和可读写层合并成一个文件系统,这样容器就可以访问到镜像中的文件系统和容器运行时的文件系统。不同的目录在这个虚拟目录里面又可以有独立的权限,也即是将两个不同的目录,挂载到同一个目录下面,然后通过读写权限的设置,使得呈现最终的文件目录给容器(模拟成了一个完整的操作系统)。

在Docker中,OverlayFS是默认的UnionFS实现方式,它具有更好的性能和更好的稳定性。Overlay 只有两层:upper 层和 Lower 层。Lower 层代表镜像层,upper 层代表容器可写层。如下所示
在这里插入图片描述

分层的优点如下:

  • 分层最大的一个优点是共享资源;
  • 多个镜像都从相同的base镜像构建而来,那么宿主机只需在磁盘上保存一份base镜像即可,对于已经存在的层就没有必要再次下载了,而是实现了资源的共享。如下所示:

在这里插入图片描述

  • 同时内存中也只需要加载一份base镜像,就可以为所有容器服务,而且镜像的每一层都可以被共享。

猜你喜欢

转载自blog.csdn.net/qq_45808700/article/details/130930345