Docker 其实很怪:容器并非真实存在

Docker is weird

我决定亲手写一个容器运行时,结果发现 Docker 真的很奇怪。容器根本不是虚拟机,只是受限制的内核进程;所谓的容器技术,其实是 Namespaces、cgroups 等五个 Linux 内核功能的组合。Image 本质上只是文件夹,Docker 通过 chroot 将其设为根目录。更有趣的是,runc 用 Go 编写,但 Go 的多线程特性和缺乏 fork 机制让实现变得复杂,不得不依赖 C 模块和特殊技巧来模拟 fork。此外,Docker 通常需 root 权限,若要在无 root 环境下运行,甚至需要重写整个网络栈。亲手造轮子让我对 Docker 有了全新理解。

容器根本不是真实存在,它们只是带着特殊限制的进程,你的内核仍在运行它们。
  1. shykes

    嗨,我是 Docker 的创始人,偶然看到了这篇帖子。看到其他工程师思考系统设计背后的逻辑,总是很有趣。虽然我已经很多年没有参与 Docker 的开发了,但这趟回忆之旅还是很愉快的。

    我很乐意回答你们可能有的任何问题。

  2. vbezhenar

    在我看来,让 Docker 独一无二的并不是容器技术本身。20 年前我就用过 chroot,效果很好。没错,当时没有网络命名空间,也没有进程命名空间,但我甚至觉得这些也没那么重要。

    让 Docker 独一无二的其实是以下几点:

    1. Dockerfile 的概念。逐层构建镜像,缓存层,所有内容都在一个文件里。这让快速迭代镜像成为可能,也让高效实现 CI 构建成为可能。

    2. Docker 网络注册表的概念。将通用镜像放到服务器上并与全世界共享。

    3. 势头。许多传统上部署复杂的软件,突然都出现在了 Docker 服务器上。我还记得当我能用 tiny docker-compose 部署 WordPress 网站时有多着迷,而且它是如此独立。我可以从宿主机上彻底删除它的所有痕迹。我一直对把各种东西安装到宿主机上感到不安,而 Docker 正好解决了这种不安。

  3. jlhawn

    > Docker 的容器运行时 runc 是用 Go 写的。

    我在 2014 年到 2019 年间在 Docker 工作。实际上最重要的部分是用 C 写的。

    [我本来写了一整条评论来解释这个 hack 是如何工作的,因为我太纠结于“用 Go 写”这个说法在技术上是不准确的。然后我回去读了文章剩下的部分,结果发现紧接着的这一段其实已经用自己的方式解释了!这应该是一个教训:在发帖之前一定要读完整篇文章。]

  4. slowin

    我大多数时候更喜欢 Podman,但 Docker 确实/曾经非常棒,它是在所谓的“裹着风衣的 5 种不同 Linux 内核特性”之上构建的极佳开发者体验(DX)。这就是它的卖点:它超级轻量,充分利用了操作系统中已有的(或正在开发的)特性,而这些特性以前使用起来很别扭,需要大量的专业知识。

  5. colinmarc

    这个帖子里有很多正面的观点,我就不重复了。相反,我想补充几点模棱两可的看法:

    - 如今,Docker 中的许多功能已经上游到了(Linux)内核中,因此容器可以简单得多(且无需 root 权限)。Docker 仍然背负着一些遗留包袱,这也使得它比 Podman 更慢、更重。

    - 作为一种开发者体验技术,Docker/容器取得了巨大的成功,但在基础设施层面并非如此。容器从未变得足够安全,以至于在没有其他形式隔离的情况下运行多租户工作负载,因此在生产环境中,它们经常与 KVM(或 gVisor,或类似的第二层)变得冗余。

    第二个矛盾导致了大量的低效。例如,在 Google App Engine 上,多年来当你运行部署时,它都会创建一个 VM 仅仅为了运行单个容器。

  6. fbn79

    我最近发现了 INCUS,当简单的容器不够用时,它是一个非常好的 Docker 替代品 https://linuxcontainers.org/incus/

  7. apexalpha

    很有趣的读后体验!谢谢。

    不过,如果能将 Docker 的做法与 Podman 进行比较会更好,我认为 Podman 的做法有些不同。

    给像我这样的其他人:

    Footgun(自伤陷阱):

    Footgun 是编程或系统设计中的一个术语,指那些让用户或开发者非常容易“自己打伤自己脚”的特性、工具或 API——意思是,在完全按预期工作的情况下,却导致了自我伤害或严重错误。

  8. majke

    为了解决网络设置问题,通常需要某种形式的“slirp”。Slirp 一方面从某处获取(伪造或真实的)数据包,另一方面将其传递给一个正常的 Linux 进程,该进程执行 CONNECT / BIND / SEND / RECV。

    事实证明,你可以在用户空间完全没问题地处理所有数据包魔术(连接跟踪、syn 处理等),然后确实将其交给用户空间系统调用。

    由于对基于 C 的经典 slirp 和 rust smoltcp(功能不足)感到不满,我挖掘了 Fuchsia 用户空间 tcp/ip 栈 - netstack - 并将其包裹在类似 slirp 的外壳中。

    https://github.com/cloudflare/slirpnetstack

    所以这个家伙本质上就是那样——一方面你向它传递数据包(可能来自容器内部),另一方面它对宿主机操作系统执行 connect/bind 等操作。

    这种方法,以某种形式或另一种形式,被 tailscale、podman 和许多其他容器运行时所采用。

同日更多故事

2026-10-23