为什么需要 Docker 多架构支持(而不是 Docker 引擎抽象出差异)

11

简短版

我想知道为什么Docker镜像需要针对多个架构创建,这个问题的技术原因是什么?此外,是否应该为每个CPU架构或操作系统创建一个镜像也不是很清楚。难道操作系统不能抽象出架构吗?

详细版

我可以理解为什么Docker引擎必须被移植到多个架构。它是一种将与操作系统交互、进行系统调用的软件,最终只是以一系列指令的形式表示特定架构上的指令集。因此,就像Microsoft Word必须移植一样,Docker引擎必须移植到多个操作系统/架构。

同样的情况也发生在JVM或VirtualBox中。

但是,与Docker不同的是,为Windows编写的JVM软件可以在Linux上运行。JVM会抽象底层操作系统/架构的差异,并在两个平台上运行相同的代码。

为什么Docker镜像不是这种情况呢?为什么Docker引擎不能只抽象出差异,提供一个通用接口,使得镜像本身不需要与特定的操作系统/架构兼容?

这是一个决策(比如"让我们为每个架构创建不同的镜像,因为X原因更好"),还是Docker工作方式的结果(比如"我们需要这样做,因为Docker需要Y")?

注意事项

  • 我不是在抱怨或批评。这并不是一篇发泄文章,我只是在寻找有关不同架构需要不同镜像的技术解释。
  • 我不是要求如何创建多架构镜像。
  • 我不是要求诸如“多架构镜像是为了在各种平台上运行镜像”之类的答案,这回答了“为什么要这样做?”而不是“为什么需要这样做?”(这是我的问题)。

此外,当您查看镜像时,通常会在其摘要中包含os/arch,如下所示:

docker image digest

该镜像的目标到底是什么?操作系统、架构还是两者都是?操作系统不能抽象出底层架构吗?


编辑:我开始认为,每个架构需要不同的镜像是因为镜像本身包含应用程序。比如,它将包含Go编译器。Go编译器本身是必须已经编译为不同架构的二进制文件。x86-64的镜像将包含已编译为x86-64的Go编译器,等等。这样正确吗?如果这样正确,这是唯一的原因吗?


使用相同基础镜像安装软件包时,在不同的架构上,包名称可能会有很大差异。例如,CentOS 7上,在英特尔架构中有一个名为llvm-toolset-7的软件包,在ppc64le上则被称为llvm-toolset-7.0。这种情况经常发生,有些软件仓库甚至没有特定架构的软件包。抽象化这种情况是不可能的任务。 - clemens
谢谢,但我仍然不明白实际的区别。您提供了一个关于不同架构包名称的答案,但我不知道这与操作系统有什么关系(操作系统是否根据底层架构下载不同的软件包?),以及不同架构的软件包之间实际上有什么区别(除了名称)。 - Rafael Eyng
Docker不是虚拟机,它只是配置操作系统,使程序受到限制(cgroups)和隔离(namespaces + chroot + layered fs)。容器直接在主机操作系统中运行。不同的主机有不同的CPU。Docker不会介于CPU(或任何其他硬件)和容器之间。 - Margaret Bloom
感谢@MargaretBloom,这开始回答了我的问题。“Docker不是虚拟机”->这是Docker 101,我认为我的问题水平应该意味着我已经知道了。“Docker不会坐在CPU之间”->这是更好的信息。但仍然有一个问题:镜像本身中有什么需要被移植到不同的操作系统/架构中?我们是在谈论镜像可能包含的编译程序,还是其他东西?而且这里的兼容性目标是哪一个:操作系统还是CPU?操作系统难道不应该抽象出CPU吗(诚实的问题,不知道应该怎么做)? - Rafael Eyng
编译后的程序,当然是针对特定CPU编译的。这就是为什么Java和CIL(.NET)字节码是字节码而不是机器码。Docker只支持Linux(在Win和Mac上运行Linux VM),虽然一些功能由docker镜像提供,但容器看到的内核是主机内核,因此可能存在不兼容性的另一个来源。操作系统并没有将CPU抽象化,因为它首先运行操作系统。即使是虚拟机也不能完全抽象化CPU,只有模拟器可以。 - Margaret Bloom
@MargaretBloom 很好的回答,我觉得我快理解了!那么,如果操作系统没有抽象出CPU,为什么我们需要为在同一CPU上运行的不同操作系统制作不同版本呢?是因为不同操作系统的系统调用转换为CPU指令的方式不同吗?但是,如果底层CPU相同且指令集未更改,则应该可以工作。所以,如果操作系统没有抽象出CPU,我不明白为什么不同的操作系统需要不同的二进制文件。您能详细说明一下吗?还有,在Docker多架构中为什么要同时指定架构和操作系统(为什么两者都要)? - Rafael Eyng
2个回答

1
尽管 Docker 的承诺是在移动软件时消除差异,但您仍然会面临一个问题,即 Docker 运行于主机的 CPU 架构上,这在 Docker 中是无法跨越的。
无论是 Docker 还是虚拟机都没有将 CPU 抽象化以实现完全的交叉兼容性。
仿真器可以做到。如果 Docker 和虚拟机都在仿真器上运行,它们的性能就不如今天的表现。 docker buildx 命令和 --build-arg ARCH 标志利用了 qemu 仿真器的优势,在构建期间模拟具有体系结构的完整系统。仿真的缺点是它比普通构建速度慢得多。

1
“Docker引擎为什么不能抽象出差异并提供通用接口?”
“性能将是一个重要因素。考虑一下Cygwin在Windows上通过模拟一些不直接映射到Windows API的POSIX事物来提供POSIX API时,某些操作会变得非常缓慢。(例如,单独执行fork() / exec而不是CreateProcess)。而这只是源代码兼容性;生成的二进制文件是特定于Windows上的Cygwin。如果您想在运行时进行二进制兼容性(而不是源代码兼容性),情况会更糟。”

此外,Docker需要提供一个高效的可移植JIT编译虚拟机,覆盖各种操作系统和CPU ISAs,例如x86-64与AArch64甚至没有共同的机器码。

如果Docker选择这条路线,它实际上只是在重新发明基于JVM或.NET CLR字节码的虚拟机。

更有可能的是,它会使用现有的虚拟机,并在其上添加镜像管理。但是,除非将原生程序转换为Java或CLR字节码,否则无法处理用C编写的原生程序。


你需要理解像 JVM 这样的东西在汇编层面上是如何工作的。这正是我正在努力做的。而这个答案比我想写的要长得多。我试图深入了解堆栈底部,但很难找到好的、可达的材料,也没有人愿意向其他人解释。这一定是一个非常神秘的社团。 - Rafael Eyng
@RafaelEyng:这不是“秘密”,只是许多不同的知识片段,比如不同的操作系统具有不同的系统调用API,例如在cygwin上的POSIX fork():https://www.cygwin.com/faq.html#faq.api.fork - Peter Cordes
"你并不真的需要知道细节。" -> 如果我无法访问这种类型的知识,那么我该如何尝试为实现容器运行时的公司工作呢? "仅仅是CPU通过获取机器码字节并将其解码为指令来工作的事实" -> 这就是我实际需要的解释,并且这与Docker镜像有何关联,使得为每个架构都需要有一个不同的镜像呢? - Rafael Eyng
1
@RafaelEyng:我刚才只是在谈论镜像中的软件包。除了程序(和它们的数据)之外,Docker镜像中的其他部分据我所知只是元数据,供Docker引擎读取,而Docker引擎已经必须被移植到每个平台。 - Peter Cordes
1
@RafaelEyng:顺便说一句,我知道这个答案不完整也不详细,但我希望它比没有好,例如可能作为进一步研究的指针。我本来只想评论,但它几乎是一个答案,所以我就这样发布了。如果你很幸运,有时间和兴趣的其他人会深入探讨。 - Peter Cordes
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接