是时候跟Docker说再见了

2020 年 12 月 23 日

是时候跟Docker说再见了

在容器的远古时代(差不多就是 4 年前),Docker 是这场游戏的唯一玩家。但现在情况已经不一样了,Docker 不再是唯一玩家,而只是一个容器引擎而已。我们可以用 Docker 构建、运行、拉取、推送或检查容器镜像,但对于这里的每一项任务,都有其他可替代的工具,它们可能比 Docker 做得更好。所以,让我们来探究一下它们,然后卸载和忘掉 Docker……

为什么说不要用 Docker 了?


如果你已经使用 Docker 很长时间了,那么要说服你考虑使用其他的工具可能需要费点唇舌。


首先,Docker 是一个整体性的工具,它试图做所有的事情,但这通常不是最好的方法。大多数情况下,选择一种专门的工具会更好,它可能只做一件事,但会做到最好。


如果你害怕使用不同的工具,可能是因为你要学习使用不同的 CLI、不同的 API 或接受不同的概念。不过请放心,选择本文介绍的工具都是完全无缝衔接的,因为它们(包括 Docker)都遵循 OCI (Open Container Initiative)规范。OCI 包含了容器运行时、容器分发和容器镜像的规范,涵盖了使用容器所需的所有特性。


多亏了 OCI,你可以选择一套最适合自己的工具,同时又能够继续使用与 Docker 一样的 API 和 CLI 命令。


所以,如果你愿意尝试新的工具,那么就让我们来比较一下 Docker 和其他工具的优缺点和特性,看看是否有必要考虑放弃 Docker,并转向其他一些新的工具。

容器引擎


在比较 Docker 和其他工具时,我们需要将其分解为组件,首先我们要讨论的是容器引擎。容器引擎是一种工具,它为处理镜像和容器提供了用户界面,这样你就不需要处理 SECCOMP 规则或 SELinux 策略之类的事情。它的工作还包括从远程存储库提取镜像并将其解压到磁盘。它似乎也运行容器,但实际上它的工作是创建容器清单和包含了镜像层的目录。然后它将它们传到容器运行时,例如使用 runc 或 crun(稍后我们将讨论这个)。


目前有很多可用的容器引擎,不过 Docker 最突出的竞争对手是由 Red Hat 开发的 Podman。与 Docker 不同,Podman 不需要守护进程,也不需要 root 特权,这是 Docker 长期以来一直存在的问题。从它的名字就可以看出来,Podman 不仅可以运行容器,还可以运行 Pod。Pod 是 Kubernetes 的最小计算单元,由一个或多个容器(主容器和所谓的边车)组成,Podman 用户在以后可以更容易地将他们的工作负载迁移到 Kubernetes。以下演示了如何在一个 Pod 中运行两个容器:


~ $ 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 mypodd638ac011412 docker.io/library/nginx:latest nginx -g daemon o... 5 minutes ago Up 5 minutes ago cool_albattani

211eaecd307b mypoda901868616a5 k8s.gcr.io/pause:3.2 6 minutes ago Up 5 minutes ago 211eaecd307b-infra 211eaecd307b mypod


Podman 提供了与 Docker 完全相同的 CLI 命令,因此你只需执行 alias Docker=Podman,然后就像什么都没有发生改变一样。


除了 Docker 和 Podman 之外,还有其他容器引擎,但我认为它们没有出路或者都不适合用于本地开发。不过如果你想要对容器引擎有一个较为完整的了解,我们可以列出一些:


  • LXD——是LXC (Linux容器)的容器管理器(守护进程)。这个工具提供了运行系统容器的能力,这些系统容器提供了类似于VM的容器环境。它比较小众,没有很多用户,所以除非你有特定的用例,否则最好使用Docker或Podman。


  • CRI-O——如果你在网上搜索cri-o是什么东西,你可能会发现它被描述为一种容器引擎。不过,它实际上是一种容器运行时。除了不是容器引擎之外,它也不适合用于“一般”的情况。我的意思是,它是专门为Kubernetes运行时(CRI)而构建的,并不是给最终用户使用的。


  • rkt——rkt(“rocket”)是由CoreOS开发的容器引擎。这里提到这个项目只是为了清单的完整性,因为这个项目已经结束了,它的开发也停止了——因此它不应该再被使用。


镜像的构建


从容器引擎方面来说,除了 Docker 之外只有一种选择。但是,在构建镜像方面,我们有很多选择。


首先是 Buildah(https://buildah.io)。Buildah 是 Red Hat 开发的一款工具,可以很好地与 Podman 配合使用。如果你已经安装了 Podman,可能会注意到 podman build 子命令,它实际上是经过包装的 Buildah。


在特性方面,Buildah 遵循了与 Podman 相同的路线——它是无守护进程的,可以生成符合 OCI 的像,并保证以相同的方式来运行使用 Docker 构建的镜像。它还能基于 Dockerfile 或 Containerfile(它们实际上是同一个东西,只是叫法不一样)构建镜像。除此之外,Buildah 还提供了对镜像层更精细的控制,支持提交大量的变更到单个层。在我看来,它与 Docker 之间有一个出乎人意料的区别,使用 Buildah 构建的镜像是特定于用户的,因此你可以只列出自己构建的镜像。


你可能会问,既然 Buildah 已经被包含在 Podman CLI 中,为什么还要使用单独的 Buildah CLI?buildah CLI 是 podman build 所包含的命令的超集,你可能不需要使用 buildah CLI,但是通过使用它,你可能会发现一些额外有用的特性(有关 podman build 和 buildah 之间的差异的细节,请参考https://podman.io/blogs/2018/10/31/podman-buildah-relationship.html)


我们来看看一个小演示:


~ $ 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


从上面的脚本可以看到,你可以直接使用 buildah bud 构建镜像,其中 bud 表示使用 Dockerfile 来构建镜像,你也可以使用其他更多的命令,如 from、run 和 copy,它们分别对应 Dockerfile 中的 FROM、RUN、COPY。


第二个工具是谷歌的 Kaniko(https://github.com/GoogleContainerTools/kaniko)。Kaniko 也基于 Dockerfile 构建容器镜像,而且与 Buildah 类似,它也不需要守护进程。与 Buildah 的主要区别在于,Kaniko 更专注于在 Kubernetes 中构建镜像。


Kaniko 本身是作为镜像(gcr.io/kaniko-project/executor)运行的,这对于 Kubernetes 来说是没有问题,但对于本地构建来说不是很方便,并且在某种程度上违背了构建镜像的目的,因为你需要使用 Docker 来运行 Kaniko 镜像来构建镜像。如果你正在寻找在 Kubernetes 集群中构建镜像的工具(例如在 CI/CD 管道中),那么 Kaniko 可能是一个不错的选择,因为它是无守护进程的,而且(可能)更安全。


从我个人的经验来看——我在 Kubernetes/OpenShift 集群中使用了 Kaniko 和 Buildah 来构建镜像,我认为两者都能很好地完成任务,但在使用 Kaniko 时会随机出现构建故障,在将镜像推送到注册表时也会随机地出现失败的情况。


第三个是 buildkit(https://github.com/moby/buildkit),也可以称之为下一代 docker build。它是 Moby 项目的一部分,在运行 Docker 时通过 DOCKER_BUILDKIT=1 docker build 就可以启用它,作为 Docker 的一个实验性特性。那么,这到底会给你带来什么呢?它带来了很多改进和很酷的特性,包括并行构建步骤、跳过未使用的阶段、更好的增量构建和无根构建。但是,它仍然需要运行守护进程(buildkitd)。所以,如果你不想摆脱 Docker,同时又想要一些新的特性和更好的改进,那么使用 buildkit 可能是最好的选择。


跟之前的章节一样,这里也将提及一些工具,它们满足了一些特定的使用场景,但并不是我的首选:



  • Jib(https://github.com/GoogleContainerTools/jib)是谷歌开发的一款工具,专门用于构建Java镜像。它提供了Maven和Gradle插件,可以让你轻松地构建镜像,不需要理会Dockerfile。


  • 最后一个是Bazel(https://github.com/bazelbuild/bazel),它是谷歌的另一款工具。它不仅用于构建容器镜像,而且是一个完整的构建系统。如果你只是想构建镜像,那么使用Bazel可能有点大材小用,但这绝对是一个很好的学习体验,所以如果你愿意,可以将rules_docker(https://github.com/bazelbuild/rules_docker)为入手点。

容器运行时


最后一个是负责运行容器的容器运行时。容器运行时是整个容器生命周期的一部分,除非你对速度、安全性等有一些非常具体的要求,否则你很可能不会对其加以干扰。所以,如果你已经感到厌倦了,可以跳过这一部分。但是,如果你想知道有哪些可选择的容器运行时,可以看看以下这些:


runc(https://github.com/opencontainers/runc)是符合 OCI 容器运行时规范的容器运行时。Docker(通过 containerd)、Podman 和 CRI-O 都在使用它,它是(几乎)所有东西的默认配置,所以即使你在阅读本文后放弃使用 Docker,很可能仍然会使用 runc。


runc 的另一种替代品是 crun(https://github.com/containers/crun)。这是 Red Hat 开发的一款工具,完全用 C 语言开发(runc 是用 Go 开发的),所以它比 runc 更快,内存效率更高。因为它也是兼容 OCI 的运行时,所以你应该可以很容易上手。尽管它现在还不是很流行,但作为 RHEL 8.3 版本的技术预览,它将作为一个可选的 OCI 运行时,又因为它是 Red Hat 的产品,它可能最终会成为 Podman 或 CRI-O 的默认配置。


前面我说过,CRI-O 实际上不是容器引擎,而是容器运行时。这是因为 CRI-O 没有提供诸如镜像推送之类的特性,而这些特性是容器引擎应该具备的。CRI-O 在内部使用 runc 来运行容器。你不应该在自己的机器上尝试使用这个运行时,因为它是作为运行在 Kubernetes 节点上的运行时而设计的,并被描述为“Kubernetes 所需的运行时”。因此,除非你正在运行 Kubernetes 集群(或 OpenShift 集群——CRI-O 已经是默认设置了),否则不应该接触这个。


最后一个是 containerd(https://containerd.io),它是 CNCF 的一个毕业项目。它是一个守护进程,作为各种容器运行时和操作系统的 API 外观。在后台,它依赖 runc,是 Docker 引擎的默认运行时。谷歌 Kubernetes 引擎(GKE)和 IBM Kubernetes 服务(IKS)也在使用它。它是 Kubernetes 容器运行时接口的一个实现(与 CRI-O 一样),因此它是 Kubernetes 集群运行时的一个很好的候选对象。

镜像的检查与分发


最后一部分内容是镜像的检查与分发,主要是替代 docker inspect,并(可选地)增加远程注册表之间复制镜像的能力。


我这里要提到的一个可以完成这些任务的工具是 Skopeo(https://github.com/containers/skopeo)。它由 Red Hat 公司开发,可以与 Buildah、Podman 和 CRI-O 配套使用。除了基本的 inspect 之外,Skopeo 还提供了 skopeo copy 命令来复制镜像,可以直接在远程注册表之间复制镜像,无需将它们拉取到本地注册表。如果你使用了本地注册表,这个命令也可以作为拉取/推送的替代方案。


另外,我还想提一下 Dive(https://github.com/wagoodman/dive),这是一个检查、探索和分析镜像的工具。它对用户更友好一些,提供了更可读的输出,可以更深入地挖掘镜像,并分析和衡量其效率。它也适合被用在 CI 管道中,用于衡量你的镜像是否“足够高效”,或者换句话说——它是否浪费了太多空间。

结论


本文的目的并不是要说服你完全抛弃 Docker,而是向你展示构建、运行、管理和分发容器及其镜像的整个场景和所有可选项。包括 Docker 在内的每一种工具都有其优缺点,评估哪一组工具最适合你的工作流程和场景才是最重要的,希望本文能在这方面为你提供一些帮助。


原文链接:https://towardsdatascience.com/its-time-to-say-goodbye-to-docker-5cfec8eff833


2020 年 12 月 23 日 15:264313

评论 7 条评论

发布
用户头像
docker还是方便啊。
2020 年 12 月 28 日 19:55
回复
用户头像
标题党,一个docker为什么要用多个工具代替?
2020 年 12 月 28 日 13:34
回复
用户头像
标题党,最终不知云云
2020 年 12 月 28 日 11:35
回复
用户头像
一会儿搞一站式 一会儿又搞专业工具 我到底信哪个锤锤?
2020 年 12 月 28 日 09:59
回复
用户头像
一个工具能搞定的事,现在要搞成多个工具,现在行业的发展都这么搞了吗
2020 年 12 月 27 日 16:06
回复
用户头像
Docker 输掉了服务编排,连容器的主战场也很快要沦陷了吗?
2020 年 12 月 24 日 11:34
回复
用户头像
一个docker打趴一堆工具,其他工具还不足以成为对手
2020 年 12 月 24 日 09:35
回复
没有更多评论了
发现更多内容

架构师训练营第一周学习总结

刘志刚

图解Java垃圾回收算法及详细过程!

攀岩飞鱼

Java JVM 虚拟机 垃圾回收机制

架构师训练营第一周作业

芒夏

极客大学架构师训练营

SpringBoot基本特性以及自动化配置-SPI机制

攀岩飞鱼

Java 微服务 Spring Boot SpringCloud

ARTS-WEEK2

一周思进

ARTS 打卡计划

SpringBatch系列之并发并行能力

稻草鸟人

Spring Boot SpringBatch 批量

食堂就餐卡系统设计

飞雪

LeetCode 769. Max Chunks To Make Sorted

liu_liu

LeetCode

程序员的晚餐 | 6 月 7 日 豆腐年糕

清远

美食

SpringBoot整合Quartz实现任务定时

北漂码农有话说

SpringBoot 2

Flink源码分析之FlinkConsumer是如何保证一个partition对应一个thread的

shengjk1

flink flink 消费 kafka 实时计算 flink源码分析

极客时间-架构师培训-1期作业

Damon

每周学习总结-架构师培训一期

Damon

食堂就餐卡管理系统

孙志平

程序员陪娃系列——数学启蒙趣事

孙苏勇

程序员人生 陪伴

食堂就餐卡系统设计

刘志刚

Flink源码分析之Flink startupMode是如何起作用的

shengjk1

flink flink 消费 kafak 实时计算 flink源码 flink源码分析

dnsmasq-域名访问及解析缓存

一周思进

架构师训练营第一周作业

小树林

ARTS(2020-06-01/2020-06-07)

天行者

ARTS 打卡计划

Flink源码分析之Flink 自定义source、sink 是如何起作用的

shengjk1

flink flink源码 flink源码分析 flink自定义source flink自定义sink

架构师如何做架构总结

Karl

Flink源码分析之-如何保存 offset

shengjk1

食堂就餐卡系统架构设计

Karl

Element-UI实战系列:Tree组件的几种使用场景

brave heart

vue.js 前端 Elemen

食堂就餐卡系统设计

饶军

ARTS打卡 week 2

猫吃小怪兽

ARTS 打卡计划

愚蠢写作术(3):如何把读者带入迷宫深处

史方远

学习 读书笔记 个人成长 写作

架构方法学习总结

飞雪

Flink源码分析之Flink是如何kafka读取数据的

shengjk1

flink flink 消费 kafka flink源码分析 flink消费kafka源码解析

人人都是产品经理

二鱼先生

产品经理 个人品牌 职场成长 产品思维

解读2020之年终技术盘点特辑

解读2020之年终技术盘点特辑

是时候跟Docker说再见了-InfoQ