本文翻译自 A Decade of Docker Containers,原载于 Hacker News。
引言
Docker 是一个广受开发者欢迎的工具,它首先简化了应用栈的组装(docker build),然后支持快速分发可执行文件和数据(docker push),最后还支持在同一台机器上运行多个相互隔离的应用(docker run)。开发者可以通过源代码旁边的 Dockerfile 编译自己的 Docker 镜像,并复用其他已发布的镜像,在全球范围内的各种编程语言和应用栈中共享打包工作。
自 2013 年首次发布以来,Docker 在各个领域得到了快速采用,从 Proxima Fusion 的仿星器模拟到 Netflix 的流媒体服务,再到使用 BalenaOS 在太空中部署软件。它也是开发者们乐于使用的工具,在 Stack Overflow 的社区排名中始终位居”最受渴望”和”最常用”开发者工具的榜首。仅 Docker Hub(多个镜像共享注册中心之一)就托管了超过 1400 万个应用镜像,每月交付超过 110 亿次镜像拉取。
Docker 的受欢迎源于它解决了许多开发者面临的长期问题:如何开发和部署日益用多种语言编写的微服务。它已成为我们在 Kubernetes 等多租户平台上管理云原生应用的事实标准,并为可重现的科学研究设定了更高的(虽然还不是完美的)标准。
但是,当我们透过看似简单的命令行界面来看,Docker 到底是什么?它是一个建立在操作系统栈数十年进步基础上的系统,自最初发布以来不断发展,以提供流畅的开发者体验为目标,融入了大量系统研究成果。在本文中,我们将解释 Docker 的技术基础,从其在 Linux 上的起源开始,然后介绍我们如何重新构建它以在 macOS 和 Windows 上工作而不牺牲易用性。如今,开发者工作流随着 AI 驱动的工作负载而快速发展,因此我们还将讨论 Docker 的未来,因为它适应支持异构硬件,如 GPGPU 和 FPGA。
技术起源
在 2000 年代初期,手动安装带有无数依赖项的 Linux 发行版,并手工编译和配置一批软件在新机器上运行是常见做法。到 2010 年,随着云计算的兴起,这个过程变得更加复杂,应用被期望在多台虚拟机上跨具有不同资源需求的主机运行。Docker 通过让开发者将应用及其所有依赖项打包成一系列文件系统镜像或”容器”来简化这个过程,这些容器可以在任何安装了 Docker 的机器上运行。而且与虚拟机体验(涉及安装整个操作系统)不同,它只需要几个命令就能启动和运行。
典型工作流
使用 Docker 的开发者编写一个 Dockerfile,使用熟悉的 shell 语法描述如何构建应用,并扩展为逐步过程。例如,基于 Python 的网站可能有这样的 Dockerfile:
FROM python:3
COPY requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /app
EXPOSE 80
CMD ["python", "app.py"]
然后开发者运行 docker build 通过执行这个 Dockerfile 来创建 Docker 容器镜像。然后可以将此镜像推送到 Docker Hub,Docker Hub 充当镜像的中央注册中心:
$ docker build -t avsm/my-python-app .
$ docker push avsm/my-python-app
镜像可以在任何安装了 Docker 的机器上下载和运行。例如,要运行带有本地数据卷挂载和单个网络端口暴露给主机的镜像,开发者会告诉他们的用户运行:
docker run -v data:/app/data -p 80:80 avsm/my-python-app
应用现在在容器中运行,与主机系统和同一台机器上运行的任何其他容器隔离。开发者可以迭代他们的应用,当他们准备发布新版本时,重新构建镜像并将其推送到 Docker Hub。用户可以独立于彼此更新他们使用的镜像,而不必担心同一软件的不同版本之间的冲突。
多年来,Docker 命令行界面已经发展,纳入了更多命令,它使用的后端系统也完全重新架构,但编写 Dockerfile 并使用 docker build 和 docker run 的原始工作流自 2013 年以来保持一致。在 GitHub 上搜索发现,在托管在那里的公共存储库根目录中有超过 340 万个 Dockerfile,显示了这种分发机制在几乎任何类型的软件项目中的受欢迎程度。
底层原理
现在让我们了解 Docker 容器在 Linux 内部如何在较低级别工作。操作系统内核彼此隔离进程内存空间,但故意共享其他几种类型的系统资源。OS 内核从单个共享文件系统启动,该文件系统包含配置文件、动态库和每个应用的状态。虽然方便,但如果多个应用具有冲突的动态库需求,这个单一共享文件系统使得同时安装它们变得非常困难。进程还需要相互通信;例如,Web 前端需要与后端数据库通信。Linux 支持多种进程间通信方法,包括网络、Unix 信号和 Unix 域套接字。虽然这种共享通道对于协作进程至关重要,但如果应用之间存在冲突,共享也可能导致不希望的干扰,例如在网络端口的选择上。
解决这些冲突的一种方法是在各自的虚拟机(VM)中运行每个应用,使用单独的客户内核、用户空间和文件系统。虚拟机监控程序在共享硬件上复用许多 VM。这有效但重量级:它需要多个内核、重复的文件系统、重复的缓存和桥接网络接口,如果用户只是想快速运行几个应用,会引入显著的复杂性。也很难有效地去重存储和内存,因为每个客户 OS 通过假设它是硬件的唯一用户而独立行动。
这些挑战提出了一个问题:我们能否使用 OS 原语而不是重量级 VM?1978 年,Unix v7 添加了 chroot() 以允许进程使用完全独立的根文件系统,但不支持组合来自不同应用的多个文件系统。Nix 和 Guix 等系统要求将软件重新打包到不同的每个应用目录中,并使用动态链接解析正确的库版本。虽然有效,但这需要修改所有软件打包,这对于专有软件并不总是可能的。这也只是部分解决方案,因为它不能解决应用之间网络端口冲突的问题。
Docker 选择使用 Linux 的一个特性叫命名空间(namespaces),这在 Nix 首次创建时还不可用。命名空间为每个进程提供更多控制权来控制如何访问共享资源,如文件和目录。例如,在图 1 中,在包含 /alice/etc/passwd 和 /bob/etc/passwd 的根文件系统中,在不同命名空间下的两个进程可以看到不同的 /etc/passwd 并解析到 /alice 或 /bob 下的版本。进程本身不知道其请求正在重新映射到更广泛的根文件系统中,并且它永远无法”看到”其范围之外的文件。关键的是,命名空间仅在打开资源时应用,生成的文件描述符作为普通内核资源用于后续操作,如读取或写入,没有进一步的开销。这允许 Linux 内核高效地管理共享资源,同时仍然提供应用与底层文件系统所需的隔离级别。一旦打开,文件描述符也可以以通常的方式跨进程传递,确保与 Unix 编程规范的兼容性。

图 1. Linux mount 命名空间允许进程控制文件名如何解析。
命名空间不是 Linux 中的现代特性,多年来逐步添加。文件系统(或”mount”)命名空间于 2001 年首次添加到 Linux 2.5.2 内核,随后是 2006 年在 Linux 2.6.19 中的进程间通信命名空间,然后是 2007 年在 Linux 2.6.24 中的网络栈。多年来,Linux 已经积累了对七种不同类型命名空间的支持,当一起使用时,允许单个内核以最小的开销在将资源分配给进程方面具有巨大的灵活性。然而,由于它们是零碎地引入的,而不是 OS 为它们设计的——就像 Plan 9 一样——这些命名空间是低级的且难以使用。FreeBSD 和 Solaris 等其他操作系统中的类似变体也从未突破到广泛使用。因此,Docker 在 2013 年做出的重大进步是使用命名空间找到务实平衡,在 VM 提供的重量级隔离和 OS 原语提供的易用性和与现有软件的兼容性之间。接下来,我们将看看它是如何工作的。
Docker 如何运行 Linux 容器
Docker 是一个客户端-服务器应用程序,有一个在主机上运行的服务器守护进程(dockerd)和一个通过 RESTful Docker API 发送请求的 docker CLI 客户端。守护进程创建和管理所有系统资源,如容器、镜像、网络和卷。当开发者调用 docker CLI 命令时,它通过众所周知的 Unix 域套接字发送 API 调用。虽然守护进程曾经是一个单一程序,但在 2015 年左右,我们将其拆分为专门的组件(图 2)。第一个组件 buildkit 组装文件系统镜像,然后 containerd 管理这些镜像到运行容器的实例化以及相关的网络和存储资源。

图 2. Docker 组件架构。
容器镜像 当调用 docker build 时,Docker 构建一个文件系统镜像,代表来自输入 Dockerfile 的可执行文件和数据。容器镜像以分层文件系统格式存储,其中每一层都应用在上一层之上。这些层的底部通常从操作系统发行版(如 Debian 或 Alpine Linux)引导,但也可以通过简单的 tar 存档手工组装。随后的层对应于 Dockerfile 中单个命令执行产生的文件系统差异。这是 Docker Hub 在互联网上共享镜像能力的基础。镜像格式本身自 2016 年以来已由开放容器倡议(OCI)中的用户社区标准化,现在有多个独立实现可用。
镜像本身存储在内容寻址存储系统中,其中文件系统镜像的哈希用作管理它的键。这允许有效地去重存储,并确保镜像一旦推送到 Docker Hub 就是不可变的。镜像可以被任何用户拉取并在任何机器上运行,哈希可用于验证镜像未被篡改。Docker 使用现代 Linux 文件系统,如 overlayfs、btrfs 或 ZFS,直接管理具有高效快照和克隆的写时复制层。Docker 还通过 stargz 存储快照器支持镜像的延迟拉取。
容器实例 在 OCI 镜像上调用 docker run 会导致分配系统资源来创建一个命名空间隔离的进程(或”容器”),该进程从文件系统镜像引导。containerd 进程负责动态配置每个容器所需的命名空间,执行以下任务:
- 为资源隔离和 I/O 速率限制定义进程”控制组”
- 将容器内的本地网络端口重新映射到主机接口上外部暴露的端口
- 从主机文件系统附加可变存储卷用于持久应用状态
- 使用 PID 命名空间隔离容器的进程树
- 使用用户命名空间将容器的本地用户 ID 映射到主机上的不同用户 ID,例如,
avsm用户可能在容器内始终一致地显示为 UID 1000,但实际上映射到不同主机上的非干扰 UID 12345 或 23456。
虽然构建这些命名空间涉及一些开销,但它远低于生成完整的 Linux VM,并且在大多数情况下可以在几分之一秒内完成。Linux 内核本身会垃圾回收已退出的容器,就像普通进程一样。
超越 Linux 的演进
这种客户端-服务器架构使管理远程 Docker 实例变得容易,因为 CLI 可以配置为通过安全网络连接发送其命令,例如,到云上运行的 Docker 主机。2015 年,我们利用这种灵活性来解决 Docker 日益增长的影响带来的另一个紧迫问题。在发布后的两年中,Docker 已成为 Linux 开发的广泛采用工具,但遇到了可用性障碍。大多数开发者仍在使用 macOS 或 Windows 作为他们的主要开发环境,但 Docker 文件系统镜像只能在 Linux 内核上运行。与此同时,公有云的兴起使 Linux 成为部署的首选。我们需要迅速找到一种方法让 Linux 容器在 macOS 和 Windows 上运行,以消除开发云服务的巨大障碍。
构建无缝的 Docker for Mac 应用
在设计 Docker for Mac 和 Windows 时的关键约束是,它必须无需额外配置就能为已经熟悉 Linux 版本 Docker 的开发者工作,并且能够运行相同的 Docker 镜像。答案在于将虚拟机监控程序虚拟化的最新技术与 Linux 命名空间的最佳实践相结合。与在桌面 OS 旁边运行 Linux 的传统方法不同,我们通过将虚拟机监控程序嵌入在 macOS 或 Windows 上运行的用户空间应用程序中来反转软件架构,并在该应用程序内运行 Linux。这种方法的灵感来自我们对 unikernel 的研究,该研究表明可以将 OS 组件灵活地嵌入更大的应用程序中。
将 Linux 嵌入应用程序中 我们首先设计了一个名为 HyperKit 的库虚拟机监控器(VMM),它使用 Intel CPU 中的硬件虚拟化扩展在普通用户进程中运行 Linux 内核。这个嵌入式 Linux 内核然后运行 Docker 守护进程,该守护进程反过来运行容器并充当普通 Docker 服务器端点。我们将所有 Linux 管理细节隐藏在桌面应用程序中,允许桌面上运行的 docker build 和 docker run 通过将调用转发到嵌入式 Linux 实例来”正常工作”。这种方法非常成功,以至于它已被其他容器系统(如 Podman)采用,现在是在 macOS 和 Windows 上运行容器的标准方式。

图 3. 使用传统独立虚拟机监控程序(上)与 Docker 使用库虚拟机监控器并嵌入 Linux VM 的方法(下)。
我们设计了一个名为 LinuxKit 的自定义 Linux 发行版,以反映它——而不是作为传统的独立 Linux 发行版——旨在用作组件并嵌入更大的应用程序中。为了最小化应用启动时间,我们构建了一个自定义用户空间,仅包含运行 Docker 容器所需的必要组件,并在容器本身内运行每个单个组件,在引导时使用的根命名空间中根本不运行任何东西。这使我们能够利用 Docker 容器本身使用的相同写时复制文件系统和网络命名空间,并以高度隔离的方式运行整个系统。LinuxKit 和 HyperKit 的组合几乎可以像原生 macOS 进程一样快地启动 Linux 进程;因此,Docker for Mac 和 Windows 应用程序诞生并于 2016 年发布。

图 4. Docker for Mac 应用架构。
网络 然而,虽然 Linux 容器现在在 macOS 和 Windows 上运行良好,但将网络连接到嵌入式 Linux 容器证明出奇地棘手。从桌面桥接以太网网络流量到 Linux VM 的传统方法需要复杂的网络管理。更糟糕的是,桥接方法还违反了企业桌面上的防火墙和病毒检查器,后者将此检测为潜在恶意流量,导致我们的测试用户有数千个错误报告。幸运的是,一个名为 SLIRP 的古老工具提供了一个可行的解决方案,这种方法首次用于在 1990 年代中期将 PalmPilot PDA 连接到互联网!
传出网络流量会在安全扫描器中触发误报,因为这些扫描器通常配置为阻止来自绕过主机 OS 网络栈的未知进程的所有流量——这正是 Linux VM 直接将其流量桥接到桌面网络栈时发生的情况。作为变通方法,我们转向 MirageOS 的 unikernel 库来在 Linux 网络请求和 macOS 及 Windows 原生套接字调用之间进行转换。当容器尝试 TCP 握手时,包含 TCP SYN 的以太网帧通过 virtio 协议使用 Mac 上的共享内存发送到主机。然后这被”库 VMM”接收,并通过 sendmsg 发送到主机 OS 上运行的用户空间 TCP/IP 栈。这个用 OCaml 编写的用户空间栈(称为 vpnkit)然后调用 macOS connect() syscall 并完成 TCP 握手或发出错误信号。使用这种架构,来自 Linux 容器的传出流量将被 VPN 策略感知为源自 Docker 应用程序而不是来自单独的机器。在 2016 年的测试中部署 vpnkit 将企业用户的错误报告减少了超过 99%,这种方法自此成为 Docker for Mac 和 Windows 的关键组件。SLIRP 方法随后在无服务器云世界中被其他地方采用,重振了古老的拨号网络技巧来解决容器管理中的新问题。

图 5. 来自传统桥接网络的流量被本地策略阻止,而来自本地进程和通过 SLIRP 间接传输的 VM 流量被接受。
传入网络流量也是一个挑战,但原因不同。默认情况下,当 Linux 容器监听端口时,除非在 CLI 上请求,否则它不会自动暴露给互联网(例如,docker run -p 80:80 nginx 在端口 80 上暴露 nginx)。运行容器的理想用户体验是容器端口直接出现在桌面 IP 地址上,可通过浏览器在 http://localhost:8080 等 URL 上访问。使用 VMware Fusion 等桌面虚拟化软件的传统方法会暴露临时中间 IP 而不是 localhost。我们的 LinuxKit 内核安装了一个自定义 eBPF 程序,触发在桌面主机上创建相应的监听套接字,并激活端口转发器以允许容器透明地接收连接而没有太多开销。这允许在 Mac 上运行 Linux 容器的完美开发者体验,并立即在 localhost 上可访问,就像在原生 Linux 机器上一样。
存储 文件存储也存在类似问题,因为开发者需要本地编辑代码并访问数据文件,同时仍然能够在容器中运行代码和测试。这种实时文件访问通常通过 Linux 上的”绑定挂载”完成,表示为 docker run -v /host:/container。绑定挂载是一个不可移植的 Linux 内核文件系统概念,其中文件系统的一部分被嫁接到树的另一部分。由于 macOS 和 Windows 是不同的内核,这不会工作,因此 Docker 使用起源于 KVM 虚拟机监控程序的 virtio-fs 共享内存协议将文件系统操作发送到主机,格式为 FUSE 请求。主机接收这些请求并调用相应的 open、read 和 write syscall。这也意味着开发者的代码和数据可以保留在主机文件系统上,使其可用于备份和搜索工具,如 Apple 的 Time Capsule 或 Spotlight,而不需要将这些工具集成到 Linux VM 中。
Windows Services for Linux 的到来
到 2017 年,Linux 在云上部署的普及变得清晰,Microsoft 发布了 Windows Services for Linux(WSL)子系统以允许直接在 Windows 上运行 Linux 应用程序。这个子系统的第一个版本不使用虚拟化,而是更喜欢通过另一个库操作系统将 Linux 二进制文件调用的系统调用动态转换为相应的 Windows 系统调用。这种方法对许多应用程序很成功,但 Docker 容器对此工作来说太过分了。Linux 内核有大量系统调用,WSL 不支持足够的系统调用来运行 Docker 容器。
2018 年,Microsoft 重新架构 WSL,发布版本 2,采用类似于 Docker for Mac 的方法,通过在后台运行完整的 Linux VM。此时,Docker for Windows 集成现在是无缝的;WSL2 Docker 在 LinuxKit WSL 发行版内运行守护进程和用户容器,并负责从 Windows 本身和其他 Linux 发行版转发 Docker API 和网络端口。

图 6. WSL2 上的 Docker for Windows 架构。
总而言之,使 Docker 容器能够跨平台演进的架构方法是库 OS 方法,将传统的”仅内核代码”重新用作可以嵌入其他应用程序的用户空间库。这种架构的成功体现在其不可见性和普遍性——数百万开发者每天使用 Docker 及其衍生工具,而无需担心他们运行的是哪个操作系统。
新兴开发者工作流
多 CPU 架构
在 Docker 早期,云中的大多数工作负载基于 Intel 架构。随着 2018 年 Amazon Graviton ARM 处理器用于云工作负载的发布以及 2020 年 Apple M1 ARM CPU 系列的发布,这一切都改变了。突然间,通过在 ARM 上运行工作负载可以获得成本节省和性能改进,开发者希望利用这一点。如今,有必要在同一个 Docker 镜像中支持多个 CPU 架构,以便开发者可以在 Intel、ARM、POWER 或新兴的开源 RISC-V CPU 上运行他们的应用程序。在服务器端,这种能力通过用”多架构清单”支持扩展 OCI 镜像格式添加到 Docker 镜像,记录镜像为哪些架构构建。
这仍然给我们留下了如何从单个主机为多个 CPU 架构构建这些镜像的问题,而不引入众所周知的复杂交叉编译问题。我们转向 Linux 中另一个相对晦涩的特性 binfmt_misc,它允许通过自定义用户空间应用程序运行可执行文件。QEMU 可以在多个 CPU 架构之间转换,因此我们在 Docker for Desktop 的嵌入式 LinuxKit 中安装了它,以透明地在 ARM 和 Intel 二进制文件之间转换。虽然这是显著的开销,但通常只在构建阶段需要,因为生成的多架构镜像可以在任何主机上原生运行而无需修改。Apple 随后在其 CPU 系列中通过”Rosetta”引入了对 CPU 指令集转换的硬件和软件支持,这很容易集成到 Docker 架构中。如今,并排运行 Intel 和 ARM 容器对开发者来说是常见的工作流。
使用可信执行环境管理密钥
管理密钥(如密码或 API 密钥)一直是容器化环境的挑战,因为它们必须动态注入容器而不是烘焙到文件系统镜像中。Docker 一直支持套接字转发,因此本地域套接字可以挂载到容器中,包括在 Docker for Mac 或 Windows 的情况下将该套接字转发到 Linux VM。这允许用户在容器内使用密钥管理系统(如 ssh-agent)而无需直接暴露密钥。套接字转发提供了良好的第一级保护,但现代环境需要更多层防御来抵御隐藏在不断增长的软件供应链中的恶意软件。
第一站是直接在容器运行时内使用虚拟机监控程序保护来增加跨容器保护级别。除此之外,Docker 一直在集成现代 CPU 中的硬件特性,可以保护秘密数据免受主机操作系统的侵害。可信执行环境(TEE)允许创建”机密 VM”,可以在应用、内核甚至虚拟机监控程序边界之间强制执行数据访问限制。然而,配置和使用 TEE 具有与 OS 虚拟化相同的许多管理复杂性,因为它实际上在 TEE 内启动迷你操作系统内核。
来自机密容器工作组的一个用户社区一直在开发可以在 TEE 内运行并通过 Docker 管理的应用程序。Docker 的客户端-服务器架构与这些应用程序集成良好,因为桌面上运行的 Docker CLI 可以通过多个转发套接字将来自本地 TEE 的加密消息从主机一直转发到云环境内运行的远程 TEE 环境。这允许开发者验证敏感云环境而无需在现场,并在桌面飞地内安全地存储其凭据并保持本地开发的便利性。
AI 工作负载的 GPGPU 支持
到目前为止,我们专注于 Docker 如何演进以在不同操作系统和 CPU 上运行,但 AI 工作负载的兴起带来了全新的挑战。机器学习工作负载主要在 GPU 上运行,Docker 生态系统必须适应支持。核心挑战是 GPU 工作负载需要精确匹配的内核 GPU 驱动程序和用户空间库,而多个容器在单个共享内核上运行。这引入了 Docker 首先设计解决的相同基本冲突:如何在同一台机器上运行具有冲突依赖项的多个应用程序。如果两个应用程序需要相同内核 GPU 驱动程序的不同版本会发生什么?
自 2023 年 3 月以来,Docker 支持容器设备接口(CDI),它支持在容器启动时自定义文件系统镜像,允许 GPU 设备文件和 GPU 特定的动态库被绑定挂载,并重新生成 ld.so 缓存。虽然这确保 Docker 镜像在特定类别或供应商的 GPU 之间可移植,但在不同操作系统和硬件品牌之间并不完全无缝。CDI 添加的可用动态库有效地定义了不同的 API,因此没有可与传统上是容器在 CPU 上运行的接口的稳定 Linux 系统调用 ABI 相比的东西。为 Nvidia GPU 设计的应用程序仍然难以在 Apple M 系列 CPU 上运行,因为底层 GPU 虚拟化支持尚未成熟到可以在如此多样化的硬件之间转换向量指令。我们继续与更广泛的容器社区和 GPU 制造商合作,开发更灵活和安全的方式来管理与 GPU 相关的依赖项,并希望便携接口的倡议能够达成共识。
结语
Docker 于 2013 年开始其生命,旨在帮助开发者更轻松地构建、共享和运行任何应用程序。它现在已深入集成到标准云和桌面开发工作流中,全球数百万开发者每天使用它,每月有数十亿次请求。我们的一贯目标之一是维护一个充满活力和多样化的开源社区,构建互操作标准,确保没有对任何单一供应商的锁定。云原生计算基金会(CNCF)作为几个核心组件的管理者,而开放容器倡议(Linux 基金会的一部分)是镜像格式的管理者。如今,这些元素的多个实现正在蓬勃发展,我们看到在云、桌面和汽车、移动甚至航天器的边缘部署越来越多。
软件开发发展迅速,因此我们不断在底层演进 Docker 以跟上最新发展。图 7 显示了 2025 年的典型开发者工作流,集成了持续测试和部署、集成开发环境(IDE)语言服务器和通过代理编码的 AI 协助。从 Docker 的角度来看,核心”构建和运行”工作流与十年前的用户体验仍然非常相似,但有更多的系统支持来减少在都需要健壮沙箱的多样化环境中运行的摩擦。

图 7. 2026 年的 Docker 开发者工作流。
如果你是开发者,我们的目标是让 Docker 成为一个不可见的伴侣,帮助你更快地发布代码——并让你享受这个过程。Docker 设计得足够可扩展,可以随着你的需求演进,特别是在面对现代 AI 编码工作流时。我们希望你能将其重新混合到你发现的任何软件环境中,并与我们的社区分享你学到的东西。
核心要点总结
-
技术基础:Docker 建立在 Linux 内核的命名空间特性之上,通过文件系统、进程间通信、网络栈等多种命名空间实现了轻量级的进程隔离。
-
架构演进:从最初的单一守护进程演进到模块化架构(buildkit + containerd),实现了构建和运行时的解耦。
-
跨平台突破:通过 HyperKit 库虚拟机监控器和 LinuxKit 自定义发行版,巧妙地将 Linux 嵌入 macOS 和 Windows 应用中,实现了无缝的跨平台体验。
-
网络创新:采用 SLIRP 用户空间网络栈(vpnkit)解决了企业环境中的防火墙和病毒检查器兼容性问题。
-
多架构支持:通过 QEMU 和
binfmt_misc实现了在单主机上构建多 CPU 架构镜像的能力。 -
安全增强:集成可信执行环境(TEE)支持机密容器,提供更强的密钥管理和数据保护。
-
AI 时代适配:通过容器设备接口(CDI)支持 GPU 工作负载,适应机器学习和 AI 开发的需求。
Docker 的成功不仅在于技术实现,更在于它如何将复杂的系统级特性封装成简单易用的开发者工具。十年间,它从一个简化应用部署的工具演变成了现代软件开发的基础设施,这正是开源社区协作和持续创新的典范。对于中国开发者而言,理解 Docker 的底层原理和演进历程,不仅能帮助我们更好地使用这个工具,也能启发我们在面对复杂技术挑战时寻找务实的解决方案。
致谢:感谢 Jon Crowcroft、Michael W. Dales、Patrick Ferris、Ryan Gibb 和 Hamed Haddadi 对本文的评论。我们也要感谢 Docker 社区,过去和现在,对 Docker 项目的反馈和贡献。