是时候和 Docker 说再见了

2025-05-25

是时候和 Docker 说再见了

在容器的远古时代(大概四年前),Docker是容器领域唯一的玩家。但如今情况已然不同,Docker 不再是唯一的,而只是容器领域中一个普通的引擎。Docker 允许我们构建、运行、拉取、推送或检查容器镜像,但对于这些任务,还有其他替代工具可能比 Docker 做得更好。所以,让我们探索一下容器领域,(或许彻底卸载并忘掉 Docker……

那么为什么不使用 Docker 呢?

如果你已经是 docker 的老用户了,我想你可能需要一些时间来说服自己考虑切换到其他工具。所以,接下来是:

首先,Docker 是一个单体工具。它试图包揽所有事情,这通常并非最佳方案。大多数情况下,最好选择一个专门的工具,只做一件事,但要做得非常好。

如果您担心切换到不同的工具集,因为您必须学习使用不同的 CLI、不同的 API 或总体而言不同的概念,那么这完全没问题。选择本文介绍的任何工具都可以完全无缝衔接,因为它们(包括 Docker)都遵循 OCI(开放容器倡议)下的相同规范。该倡议包含容器运行时容器分发容器镜像的规范,涵盖了使用容器所需的所有功能。

借助 OCI,您可以选择一组最适合您需求的工具,同时您仍然可以享受使用与 Docker 相同的 API 和相同的 CLI 命令。

因此,如果您愿意尝试新工具,那么让我们比较一下 Docker 及其竞争对手的优点、缺点和功能,看看是否真的有必要考虑放弃 Docker 而选择一些新的闪亮工具。

容器引擎

在将 Docker 与其他工具进行比较时,我们需要将其分解成不同的组件,而我们首先要讨论的是容器引擎。容器引擎是一种提供用户界面的工具,用于操作镜像和容器,这样你就不必费心处理诸如SECCOMP规则或 SELinux 策略之类的东西。它的工作还包括从远程仓库拉取镜像并将其解压到你的磁盘。它表面上也运行容器,但实际上它的工作是创建包含镜像层的容器清单和目录。然后,它将它们传递给容器运行时,例如runccrun(我们稍后会讨论)。

有许多可用的容器引擎,但 Docker 最突出的竞争对手是Red Hat开发的Podman。与 Docker 不同,Podman 不需要守护进程即可运行,也不需要 root 权限,这一直是 Docker 长期关注的问题。根据名称,Podman 不仅可以运行容器,还可以运行pod。如果您不熟悉 pod 的概念,那么 pod 是 Kubernetes 的最小计算单元。它由一个或多个容器(主容器和所谓的sidecar)组成,用于执行支持任务。这使得 Podman 用户以后可以更轻松地将其工作负载迁移到 Kubernetes。因此,作为一个简单的演示,以下是在单个 pod 中运行 2 个容器的方法:

~ $ podman pod create --name mypod
~ $ podman pod list

POD ID         NAME    STATUS    CREATED         # OF CONTAINERS   INFRA ID
211eaecd307b   mypod   Running   2 minutes ago   1                 a901868616a5

~ $ podman run -d --pod mypod nginx  # First container
~ $ podman run -d --pod mypod nginx  # Second container
~ $ podman ps -a --pod

CONTAINER ID  IMAGE                           COMMAND               CREATED        STATUS            PORTS  NAMES               POD           POD NAME
3b27d9eaa35c  docker.io/library/nginx:latest  nginx -g daemon o...  2 seconds ago  Up 1 second ago          brave_ritchie       211eaecd307b  mypod
d638ac011412  docker.io/library/nginx:latest  nginx -g daemon o...  5 minutes ago  Up 5 minutes ago         cool_albattani      211eaecd307b  mypod
a901868616a5  k8s.gcr.io/pause:3.2                                  6 minutes ago  Up 5 minutes ago         211eaecd307b-infra  211eaecd307b  mypod
Enter fullscreen mode Exit fullscreen mode

最后,Podman 提供了与 Docker 完全相同的 CLI 命令,因此您只需执行操作alias docker=podman并假装没有任何改变。

除了 Docker 和 Podman 之外,还有其他容器引擎,但我认为它们都是死路一条的技术,或者不适合本地开发和使用。但为了有一个完整的了解,我们至少可以提一下现有的容器引擎:

  • LXD - LXD 是 LXC(Linux 容器)的容器管理器(守护进程)。此工具能够运行系统容器,提供更类似于虚拟机的容器环境。它占用空间非常小,用户也不多,因此除非您有非常具体的用例,否则最好使用 Docker 或 Podman。

  • CRI-O - 当你在 Google 上搜索 cri-o 是什么时,你可能会发现它被描述为容器引擎。但它实际上是一个容器运行时。除了它实际上不是一个引擎之外,它也不适合“正常”使用。我的意思是,它是专门为用作 Kubernetes 运行时 (CRI) 而构建的,而不是为最终用户使用而构建的。

  • rkt - rkt(简称“rocket” )是由CoreOS开发的容器引擎。这里仅仅为了完整性而提及该项目,因为该项目已经结束,开发也已停止,因此不应再使用。

构建图像

就容器引擎而言,Docker 实际上只有一个替代方案。但在构建镜像方面,我们有更多的选择。

首先,让我介绍一下Buildah。Buildah 是 Red Hat 开发的另一款工具,它与 Podman 配合得很好。如果您已经安装了 Podman,您可能已经注意到这个podman build子命令,它实际上只是伪装的 Buildah,因为它的二进制文件包含在 Podman 中。

至于其功能,它与 Podman 秉承相同的路线——无守护进程、无 root 权限,并生成符合 OCI 标准的镜像,因此可以保证您的镜像的运行方式与使用 Docker 构建的镜像相同。它还能够从Dockerfile或(更合适的名称)构建镜像Containerfile,两者本质上是同一个东西,只是名称不同。除此之外,Buildah 还提供了对镜像层的更精细的控制,允许您将多项更改提交到单个层。与 Docker 相比,一个意想不到但(在我看来)很棒的区别是,Buildah 构建的镜像是特定于用户的,因此您只能列出自己构建的镜像。

现在,考虑到 Buildah 已经包含在 Podman CLI 中,您可能会问为什么还要使用单独的buildahCLI?好吧,buildahCLI 是 中包含的命令的超集podman build,因此您可能不需要接触buildahCLI,但通过使用它,您可能还会发现一些额外的有用功能(有关podman build和之间的区别的详细信息,buildah请参阅以下文章)。

话虽如此,让我们来看一个小演示:

~ $ buildah bud -f Dockerfile .

~ $ buildah from alpine:latest  # Create starting container - equivalent to "FROM alpine:latest"
Getting image source signatures
Copying blob df20fa9351a1 done  
Copying config a24bb40132 done  
Writing manifest to image destination
Storing signatures
alpine-working-container  # Name of the temporary container
~ $ buildah run alpine-working-container -- apk add --update --no-cache python3  # equivalent to "RUN apk add --update --no-cache python3"
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/x86_64/APKINDEX.tar.gz
...

~ $ buildah commit alpine-working-container my-final-image  # Create final image
Getting image source signatures
Copying blob 50644c29ef5a skipped: already exists  
Copying blob 362b9ae56246 done  
Copying config 1ff90ec2e2 done  
Writing manifest to image destination
Storing signatures
1ff90ec2e26e7c0a6b45b2c62901956d0eda138fa6093d8cbb29a88f6b95124c

~ # buildah images
REPOSITORY               TAG     IMAGE ID      CREATED         SIZE
localhost/my-final-image latest  1ff90ec2e26e  22 seconds ago  51.4 MB
Enter fullscreen mode Exit fullscreen mode

从上面的脚本中您可以看到,您可以简单地使用 来构建图像buildah bud,其中bud代表使用 Dockerfile 构建,但您也可以使用 Buildahs fromrun和等脚本化方法copy,它们是与 Dockerfile 中的命令(FROM imageRUN ...COPY ...)等效的命令。

接下来是谷歌的Kaniko。Kaniko也支持从 Dockerfile 构建容器镜像,并且与 Buildah 类似,它也不需要守护进程。与 Buildah 的主要区别在于,Kaniko 更专注于在 Kubernetes 中构建镜像。

Kaniko 旨在以镜像形式运行,使用gcr.io/kaniko-project/executor,这对于 Kubernetes 来说很合理,但对于本地构建来说不太方便,而且有点违背了初衷,因为你需要使用 Docker 运行 Kaniko 镜像来构建镜像。话虽如此,如果你正在寻找在 Kubernetes 集群中构建镜像的工具(例如在 CI/CD 流水线中),那么 Kaniko 可能是一个不错的选择,因为它没有守护进程,而且(可能)更安全。

不过,从我的个人经验来看,我使用 Kaniko 和 Buildah 在 Kubernetes/OpenShift 集群中构建图像,我认为两者都可以很好地完成工作,但使用 Kaniko 时,我看到一些随机构建崩溃并在将图像推送到注册表时失败。

第三个竞争者是buildkit,它也可以称为下一代。它是 Mobydocker build项目的一部分(Docker 也是),并且可以作为实验性功能与 Docker 一起使用。好吧,但这究竟会给你带来什么呢?它引入了许多改进和很酷的功能,包括并行构建步骤、跳过未使用的阶段、更好的增量构建和无根构建。然而,另一方面,它仍然需要守护进程才能运行()。所以,如果你不想摆脱 Docker,但想要一些新功能和不错的改进,那么使用 buildkit 可能是你的选择。DOCKER_BUILDKIT=1 docker build ...buildkitd

与上一节一样,这里我们也有一些“荣誉提名”,它们满足了一些非常具体的用例,但不会是我的首选之一:

  • Source-To-Image (S2I)是一个无需 Dockerfile 即可直接从源代码构建镜像的工具包。此工具非常适合简单且符合预期的场景和工作流程,但如果您需要过多的自定义,或者您的项目没有预期的布局,它很快就会变得烦人且笨拙。如果您对 Docker 还不是很熟悉,或者您在 OpenShift 集群上构建镜像,您可以考虑使用 S2I,因为使用 S2I 构建镜像是内置功能。

  • Jib是 Google 推出的另一款工具,专门用于构建 Java 镜像。它包含MavenGradle插件,可让您轻松构建镜像,而无需处理 Dockerfile。

  • 最后但同样重要的是Bazel,这是 Google 的另一个工具。它不仅仅用于构建容器镜像,而是一个完整的构建系统。如果您只是想构建镜像,那么深入研究 Bazel 可能有点过头,但绝对是一个很好的学习体验,所以如果您愿意的话,rules_docker部分是一个不错的起点。

容器运行时

最后一个大难题是容器运行时,它负责运行容器。容器运行时是整个容器生命周期/堆栈的一部分,除非您对速度、安全性等有非常具体的要求,否则您很可能不会去碰它。所以,如果您已经厌倦了我的介绍,那么您可能想跳过这一节。另一方面,如果您只是想知道有哪些选项,那么请继续阅读:

runc是基于 OCI 容器运行时规范创建的最流行的容器运行时。Docker(通过containerd)、Podman 和 CRI-O 都使用它,所以除了 LXD(使用 LXC)之外,几乎所有容器运行时都使用它。我没什么可补充的了。它是(几乎)所有容器运行时的默认设置,所以即使你读完这篇文章后放弃了 Docker,你很可能仍然会使用 runc。

runc 的一个替代方案与crun类似(且容易混淆)。这是一个由 Red Hat 开发的工具,完全用 C 语言编写(runc 用 Go 语言编写)。这使得它比 runc 更快,内存效率更高。考虑到它也是符合 OCI 标准的运行时,如果您想亲自测试,应该可以轻松切换到它。虽然它目前还不是很流行,但从 RHEL 8.3 版本开始,它将作为 OCI 运行时的替代方案进入技术预览版。考虑到它是 Red Hat 产品,我们最终可能会将其作为 Podman 或 CRI-O 的默认运行时。

说到 CRI-O。之前我说过,CRI-O 并非真正的容器引擎,而是容器运行时。这是因为 CRI-O 不包含诸如镜像推送之类的功能,而这些功能正是容器引擎所期望的。CRI-O 作为运行时,内部使用 runc 来运行容器。您不应该在您的机器上尝试使用这个运行时,因为它是为 Kubernetes 节点上的运行时而构建的,并且您可以看到它被描述为“Kubernetes 所需的所有运行时,仅此而已”。所以,除非您正在搭建 Kubernetes 集群(或 OpenShift 集群——CRI-O 已经是默认集群),否则您可能不应该尝试使用它。

本节的最后一个项目是containerd,它是一个 CNCF 毕业项目。它是一个守护进程,充当各种容器运行时和操作系统的 API 外观。它在后台依赖于 runc ,并且是 Docker 引擎的默认运行时。Google Kubernetes Engine (GKE) 和 IBM Kubernetes Service (IKS) 也使用它。它是 Kubernetes 容器运行时接口 (CRI-O) 的一个实现,因此它是 Kubernetes 集群运行时的理想候选。

图像检查与分发

容器技术栈的最后一部分是镜像检查和分发。这有效地替代了镜像仓库docker inspect,并且(可选)增加了在远程镜像仓库之间复制/镜像的功能。

我在这里要提到的唯一一个可以完成这些任务的工具是Skopeo。它由 Red Hat 开发,是 Buildah、Podman 和 CRI-O 的配套工具。除了skopeo inspect我们熟知的 Docker 基本功能外,Skopeo 还支持镜像复制,skopeo copy这使得你可以在远程镜像仓库之间创建镜像,而无需先将它们拉取到本地镜像仓库。如果你使用本地镜像仓库,此功能也可以用作拉取/推送。

另外,我还想提一下Dive,这是一款用于检查、探索和分析镜像的工具。它更加用户友好,输出更易读,并且可以更深入地挖掘(或者我猜是潜入)你的镜像,分析和衡量其效率。它也适用于 CI 管道,可以衡量你的镜像是否“足够高效”,或者换句话说,它是否浪费了太多空间。

结论

本文并非旨在劝您彻底放弃 Docker,而是向您展示 Docker 的全貌以及构建、运行、管理和分发容器及其镜像的所有选项。包括 Docker 在内的每种工具都有其优缺点,评估哪一套工具最适合您的工作流程和用例至关重要,我希望本文能够帮助您。

资源

文章来源:https://dev.to/martinheinz/it-s-time-to-say-goodbye-to-docker-386h
PREV
你的下一个 Golang 项目的终极设置
NEXT
Java 企业级 101:使用 Spring Boot 构建 REST 服务器 设置项目 开始工作!我们的第一个 Spring REST 端点 启动 测试 结束语