容器运行共享内核原理与GPU支持深度解析
潘忠显 / 2025-12-09
前段时间,在做模型服务的开发,该场景会在 Docker 中使用 GPU 的能力。当时对构建这样的基础镜像不是很理解,后来梳理学习理解透彻。
不过在介绍 Docker 使用 GPU 之前,会先讨论容器服务在原生 Linux 环境和非原生环境(如 macOS 和 Windows)上的工作机制,以及内核共享的原理。由浅入深,让你真正理解容器运行技术。
一、Linux 宿主机上容器的运行机制
提起容器技术,很多人都对下边比较容器和虚拟机的图烂熟于心。
左图里边的 Docker 层负责管理复杂的生命周期、镜像、网络和存储的高级管理工具,它将这些高级操作最终转换为对底层 Linux 内核 Namespaces 和 Cgroups 的低级系统调用,从而实现容器的隔离和运行。
图片来自官网:https://www.docker.com/resources/what-container/

上图其实并不完全正确——缺少了一点说明——这是在「在原生 Linux 环境中」。
容器化技术起源于 Linux,在原生 Linux 宿主机上运行时最为高效和直接,因为它不需要任何虚拟化层。我们这节就先讨论一下原生 Linux 环境下,容器是如何运行的。
容器共享的是什么
Linux 容器并非完整的操作系统,它本质上是一组被隔离的宿主机进程。所有容器共享宿主机的 唯一一个 Linux 内核。
容器的轻量级和高性能正是源于这种直接的内核共享模式。
内核是什么
Linux 内核是操作系统的核心,它提供了一个抽象层,将底层硬件的复杂性封装起来,并通过一套标准接口(系统调用)为用户空间(即 Linux 发行版、Shell、应用程序和库)提供所有基础服务,包括进程、内存、设备、文件系统的四大管理服务,并利用 Namespaces 和 Cgroups 实现了现代计算环境所需的隔离和资源控制。
上边说的其实非常抽象,听上去看不见摸不着。内核既不是进程,也不是文件,他具体的存在形式是:常驻于内存、运行在特权模式下的核心代码和数据结构,它通过系统调用和中断机制被动激活,以管理硬件和系统资源,并为上层的所有用户进程(包括容器进程)提供服务。
正因为它仅仅是内存中的一块代码和数据,所以它具备以下两个核心特性,从而实现了容器级的共享:
- 内核在内存中只存在一份实例
- 内核的设计是可重入(re-entrant)的:多个进程可以同时进入内核模式,请求执行同一段内核代码(例如
sys_read或sys_fork)。内核通过复杂的锁和同步机制来确保并发访问时的安全性和数据完整性。
我们可以使用 uname -r 来查看内核版本,下图展示了在不同容器以及宿主机的内核版本是相同的:
内核不是什么
当你真正理解了什么是内核之后,就很容易理解哪些东西不属于内核:
- Glibc /
libc.so(C 标准库): 这是一个用户空间的库。它提供了高级函数(如printf),这些函数内部封装了对内核的系统调用请求。它是应用程序和内核之间的软件层。 - Systemd: 这是一个运行在用户空间的进程(PID 1)。它是系统的初始化程序和服务管理器,负责管理和监控其他用户进程。
- 发行版(Distribution):比如 Ubuntu 22.04 或者 24.04,指的是用户空间的完整文件集合,它被打包并运行在内核之上。
但这三样又跟内核有密切的联系:
- Glibc 依赖于内核提供稳定的系统调用接口,内核的任何功能都必须通过 Glibc或类似的库的系统调用才能被用户应用程序使用
- Systemd 是内核在启动的最后阶段,必须加载并运行的1号进程。之后利用内核提供的底层能力(系统调用、Namespaces、Cgroups)来组织和管理整个系统的用户进程和服务。
- 发行版是内核之上运行的“用户生态环境”。提供的文件合集中就包括GlibC 和 Systemd 等。发行版有依赖内核的最低版本要求。
容器与内核的关系
如果我们在 Linux 机器上通过 Docker 启动一个 Nginx 容器,那么这个容器包括什么内容呢?
- 应用程序本身,二进制文件+相关配置
- 运行时依赖:二进制文件依赖许多动态链接库(如
libc.so.6、libssl.so等)。这些库文件是 Linux 操作系统基础环境的一部分,必须被打包进容器镜像中。 - 基础工具和文件系统: 虽然容器是轻量级的,但它通常需要一个基本的用户空间:基础的文件系统结构(如
/etc,/var,/usr/bin等)、Shell 程序(如/bin/bash)、基础工具(如ls,cat,mkdir等)
最重要的一点,容器不包含自己的 Linux 内核。它与所有运行在该 Linux 上的其他容器共享同一个内核。
内核的兼容性
写Dockerfile的时候,会有 FROM Ubuntu 22.04 或 FROM Ubuntu 24.04 来指定基础镜像。前面介绍来,这只是用户空间的工具包和皮肤的不同。但是,毕竟不同发行版使用的 Glibc 的版本也不一样,他们是如何在同一内核上运行呢?
尽管 Glibc 版本不同,但是它们调用的底层系统调用接口(Syscall Number)是保持一致的。Linux 内核通过确保系统调用号的向后兼容性,来保证即使上层的 glibc 版本不同,也能稳定地请求内核服务。
向后兼容性具体点的例子:在 5.15 编译的用户程序(如 Nginx、libc 等)通常可以在更新的 6.1 内核上完美运行。
我刚入职的时候,为了在开发机上用更新版本的 GCC,从别的机器复制了一个 glibc 库过去,结果因为内核版本过低而无法使用——其实这就是向前兼容性限制 (Forward Compatibility Limitation)。
上述絮叨一堆,主要为了说明:为了能尽可能稳定地兼容各种容器,宿主机内核的版本应该保持相对较新(如主流发行版最新的 LTS 版本),以确保所有容器都能找到它们需要的系统调用,并能够利用最新的隔离和性能增强功能。
通过 ldd --version 指令可以查看 glibc 的版本,下图展示了在不同容器以及宿主机的glibc版本是不同的:
容器隔离的是什么
容器在原生 Linux 上运行依赖于两个核心的内核特性:
- Namespaces(命名空间): 提供视图隔离。每个容器被分配一套独立的命名空间,使其感觉自己拥有独立的系统资源。隔离内容:包括 PID (进程 ID), Mount (文件系统), Network (网络堆栈), User (用户和用户组), UTS (主机名) 等。例如,一个容器看不到宿主机上其他进程的 PID,只会看到自己内部的进程。
- Cgroups(控制组):提供资源限制和核算。Cgroups 限制了容器能使用的硬件资源(CPU、内存、I/O 和网络带宽),防止单个容器耗尽宿主机资源,确保公平和稳定性。
这些具体的介绍可以看之前的文章:《揭秘容器(一):内核空间 chroot & namespace》
容器启动运行流程
当在原生 Linux 上运行容器时(例如使用 Docker Engine 或 containerd):
- 容器运行时(Runtime,如 runc)接收到创建容器的指令。
- 运行时利用 Namespaces 创建一个隔离的视图环境。
- 运行时利用 Cgroups 为容器分配和限制资源。
- 容器内的进程直接在宿主机的 Linux 内核上启动并运行。
这些具体介绍可以看之前的文章:《揭秘容器(二):容器运行时》
二、 非Linux环境上容器的运行机制
由于 macOS 和 Windows 宿主机运行的不是 Linux 内核,不能依赖其提供的特定隔离特性,包括 Cgroups和 Namespaces,也不能共享其内核。因此它们不能直接运行 Linux 容器,必须引入虚拟化层作为桥梁。
macOS 上的容器运行
我这里日常用的就是一个 MacBook,因为现在公司合规要求不让使用 Docker Desktop,所以下边的介绍都是以 Podman 为例进行的。如果你用 Docker Desktop 应该是类似的。
在 macOS 上的容器运行架构中,主要有四个层次协作:
- 宿主操作系统 (macOS): 位于最底层,负责提供硬件资源和运行环境,并使用 macOS Hypervisor 框架或 HyperKit 等技术来支持虚拟化。
- 虚拟化层 (轻量级 Linux 虚拟机 - Podman Machine): 这是一个完整的 Linux 虚拟机 (VM),它作为容器的真正宿主机,运行着 Linux 内核。它依赖 HyperKit 或 Virtualization.framework 来实现。
- 容器引擎 (Podman 守护进程): 运行在 Linux VM 内部,负责接收来自 macOS 宿主机的指令,并管理容器的整个生命周期。
- Linux 容器: 运行在 Linux VM 内部,容器本身共享该 VM 的 Linux 内核,并利用 Namespaces 和 Cgroups 等核心技术实现其视图隔离和资源限制。
这里我们可以通过 podman 的相关指令,查看一些信息。通过 podman machine list 我们可以列出启动的 Linux VM(applehv 字段也表明使用 macOS 原生的 Hypervisor 框架进行虚拟化):

通过 podman machine ssh 可以登入到 VM 内:
在 VM 内部,就可以找到启动容器对应的相关进程(比如我这里启动了一个redis-server,这里common是容器监控器,在另外命名空间启动了redis-server 进程,直接 ps 是看不到这个进程的,因为 ps 只展示宿主机命名空间的进程):
简而言之,macOS 上的容器并非直接在 macOS 内核上运行,而是运行在一个 Podman Desktop 启动的轻量级 Linux 虚拟机内部(Podman Machine),共享该 VM 的 Linux 内核。
很明显,这里因为引入了虚拟化技术,中间会带来性能开销。这对于我们日常的开发使用影响不大,但是如果像做压测或其他一些追求极致性能的任务,特别是针对 I/O 和计算密集型应用,那么直接在原生 Linux 服务器才是效率最高的选择。
Windows 上的容器运行
我手头不太用 Windows 环境,这里仅仅简单说明下 Windows 环境运行 Linux 容器的原理和特点。
在 Windows 上,容器运行的现代主流架构是基于 WSL 2 (Windows Subsystem for Linux 2)。整个架构分为以下几个主要层次:
- 宿主操作系统 (Windows) 位于最底层,负责提供硬件资源和运行环境,并依赖 Windows Hyper-V 平台实现虚拟化。
- 虚拟化层 是 轻量级 Linux VM (WSL 2)。它运行着一个真正的 Linux 内核实例,作为容器的真正宿主机。WSL 2 利用 Hyper-V 驱动实现了快速启动。
- 容器引擎(例如 Docker Engine 或 Podman 守护进程)运行在 WSL 2 VM 内部,负责接收指令并使用 Linux 内核 Namespaces / Cgroups 等技术管理容器的生命周期。
- 最上层的 Linux 容器 则运行在 WSL 2 VM 内部,共享该 VM 的 Linux 内核并实现隔离。
Windows 借助 WSL 2 提供了对原生 Linux 内核的深度集成,在文件系统共享、网络等性能方面非常原生 Linux 环境,比 macOS环境里容器运行有明显提升。

小结
本文详细介绍了容器化技术在 Linux 和非 Linux 宿主机(macOS 和 Windows)上的运行原理。读完之后,你应该深刻地理解了:什么是内核,容器共享的是什么,容器隔离的是什么,容器镜像里到底包含什么,以及非 Linux 宿主机上容器运行所依赖的虚拟化层。
接下我会介绍使用GPU的情况下,NVIDIA 驱动、CUDA Toolkit等是如何在 Linux 宿主机和容器上工作的。对提高深度学习应用的构建效率、运行性能都非常重要。
