容器虚拟化原理

容器虚拟化原理

理解容器技术

容器是一种沙箱技术。顾名思义,沙箱就像容器一样,能够将应用“打包”起来,确保应用之间由于设定的边界而互不干扰。而且,在这些“容器”中运行的应用可以迁移并在不同的系统环境中运行。在深入了解容器技术的原理之前,理解进程的概念至关重要。进程的静态表现形式是程序,通常静静驻留在磁盘上。一旦运行,它就变成计算机中数据和状态的总和——即其动态表现形式。容器技术的核心功能是通过约束和修改进程的动态行为,为进程创建一个“边界”。

Docker 容器技术

对于大多数 Linux 容器(包括 Docker)来说,主要使用 Cgroups 技术施加约束,而 Namespace 技术则是修改进程视图的主要方法。假设你在 Linux 操作系统(例如 Ubuntu 22.04)上运行一个 Docker 项目。我们来创建一个容器进行实验:

$ docker run -it busybox /bin/sh

这条 Docker 命令会启动一个基于 busybox 镜像的容器,并在其中运行交互式 shell 会话。

  • docker run:创建并启动一个新的容器实例。
  • -i:即使没有挂载终端也保持 STDIN 打开,允许与容器交互。
  • -t:分配一个伪终端或终端,创建交互式 shell 环境。
  • /bin/sh:指定容器启动时要运行的命令,此例中是在容器内启动一个 shell 会话。

容器内的进程

于是,Ubuntu 机器成为宿主机,一个运行着 /bin/sh 的容器在其中工作。这个例子及其背后的原理对经验丰富的 Docker 用户来说应该很熟悉。如果在容器内执行 ps 命令,你会注意到一些有趣的事情:

/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
10 root 0:00 ps

这里,在 Docker 中首先运行的 /bin/sh 是容器内的 1 号进程(PID=1),并且只有两个进程在运行。这意味着 /bin/sh 和我们刚才执行的 ps 命令被隔离在一个与宿主机不同的环境中。

Namespace 机制

这是如何实现的呢?通常,当 /bin/sh 程序在宿主机上运行时,操作系统会分配一个进程 ID,例如 PID=100,用来唯一标识它。当在 Docker 容器中运行这个程序时,Docker 施加了一种“幻象”,使得这个实际为 PID=100 的进程认为自己就是第一个进程(PID=1)。这种机制操纵了被隔离应用的进程空间,让它们看到重新计算过的进程 ID。

Linux 的 Namespace 技术

Namespace 的使用非常有趣:它只是 Linux 中创建新进程的一个可选参数。Linux 中创建进程的系统调用是 clone(),如下所示:

int pid = clone(main_function, stack_size, SIGCHLD, NULL);

这个调用创建了一个新进程并返回其进程 ID pid。当使用 clone() 系统调用创建新进程时,可以指定 CLONE_NEWPID 参数:

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

这样,新创建的进程就会“看到”一个全新的进程空间,其中它的 PID 是 1。这种“幻象”并不会改变宿主机进程空间中实际的 PID,它仍然是真实的值,比如 100。

多个 Namespace 的运用

重复执行上述 clone() 调用会创建多个 PID Namespace。每个 Namespace 中的应用进程都认为自己是各自容器内的第一个进程,并且不知道宿主机的实际进程空间或其他 PID Namespace 的细节。除了 PID Namespace,Linux 还提供了 Mount、UTS、IPC、Network 和 User Namespace 来“掩盖”进程的各类上下文。Mount Namespace

  • Mount Namespace:允许被隔离的进程仅看到当前 Namespace 相关的挂载点,意味着容器内的进程不知道宿主机上的其他挂载点。

Network Namespace

  • Network Namespace:为被隔离的进程提供当前 Namespace 特有的网络设备和配置视图。每个 Network Namespace 都有自己的网络设备、IP 地址、路由表和端口号,与宿主机和其他 Namespace 隔离。

其他 Namespace

  • UTS Namespace:隔离节点名(主机名)和网络主机名信息。
  • IPC Namespace:隔离进程间通信的资源,如消息队列和信号量。
  • User Namespace:隔离用户和组 ID,允许将容器内的用户 ID 映射到宿主机上的不同 ID,以增强安全性。

Docker 容器的实现原理

这就是 Linux 容器的基本实现原理。因此,看似复杂的 Docker 容器概念,本质上就是在创建容器进程时指定一组 Namespace 参数。结果,容器只能“看到”当前 Namespace 所限定的资源、文件、设备、状态或配置,而对宿主机和不相关的程序完全无知。

容器与虚拟机的比较

因此,容器是一种特殊的进程。当考虑到为进程分配独立空间的想法时,就会想到虚拟机。虚拟机通过 Hypervisor 软件模拟硬件,运行一个完整的客户操作系统来实现应用隔离。相比之下,Docker 容器通过 Namespace 和 Cgroups 技术实现隔离,同时共享宿主机的操作系统内核。

Docker 的轻量级虚拟化

Docker 常被称为“轻量级”虚拟化技术,因为它直接利用宿主机资源,无需模拟硬件或运行额外的操作系统。这使得它在启动速度、资源利用率和性能方面相比传统虚拟机具有显著优势。

结论

Docker 容器技术通过 Namespace 和 Cgroups 技术,为应用进程提供了一个隔离且轻量级的环境。这项技术使容器能够快速启动、高效运行,同时保持与宿主机及其他容器的隔离,使其成为现代云计算和微服务架构中不可或缺的技术。

Novita AI*,一站式无限创意平台,提供 100+ 个 API。从图像生成到语言处理,从音频增强到视频操控,按用量付费,帮你摆脱 GPU 维护烦恼,轻松构建自己的产品。免费试用。