程序印象

为 Kubernetes 选择合适的 container runtime(译)

2018/09/29 Share

原文地址:https://joejulian.name/post/kubernetes-container-engine-comparison
作者:Joe Julian
翻译:狄卫华
发表时间: 2018/3/23

Kubelet 可通过配置项 container-runtimecontainer-runtime-endpointimage-service-endpoint 来选择使用 docker,rkt(不建议使用)或任何 CRI API 兼容的 container-runtime

意见

虽然基于虚拟机的机制提供了额外的安全隔离,但资源限制太多,需要较多的开销才能将软件在生产环境上部署使用,但是为了方案的完整性,本文也涵盖其相关的实现,但在整体解决方案章节将不再完整描述或分析。

Kubernetes 接口

Native

Docker

Docker 是迄今为止最常用的容器引擎,主要得益于其容器仓库和社区。Docker 是 dockerd 、containerd、containerd-shim 和 runc 的组合,其中 runc 作为 container-runtime。 由于 kubernetes 移除对于 container-runtime 的直接和非 CRI 接口的支持,因此 Native 支持的方式的后续将会被移除。

rktnetes

截至 kubernetes 1.10.0,直接集成 rkt(或 rktnetes)支持已被弃用。

CRI

容器运行时接口(CRI)的出现是因为 CoreOS 想要为 kubernetes 添加 rkt 支持。Kubernetes 初期提供了对于 docker 的支持,但在后续添加 rkt 支持的补丁后,代码变得丑陋务必,充斥着大量的 “if this do rkt else do docker”。为后续出现的 runtimes 添加支持将无法再继续维护,基于此,CRI 诞生了。

CRI 是一个基于 protobuf 定义的 api,包括两个 gRPC 服务,ImageServiceRuntimeServiceImageService 提供 RPC 以从仓库中提取镜像,检查和删除镜像。RuntimeService 包含用于管理 pod 和容器生命周期的 RPC,也提供与容器交互的调用(exec/attach/port-forward)。

cri-containerd

cri-containerd-interaction

cri-containerd 是一种向 containerd 添加 CRI 支持的服务,containerd是由 Docker 创建并捐赠给 CNCF ,提供 runtime 管理和镜像服务。后续 containerd 与 Docker 分离,将 runtime 管理器与 docker 其余的工具分离,以利于不断增长的容器管理工具生态系统在 docker api上实现标准化。这有助于 docker 社区的扩展并帮助在 docker 仓库中提供丰富的容器镜像。

cri-containerd 在kubernetes 1.9 中处于 beta 阶段。

rktlet

rktlet 是一个使用 rkt 作为容器 runtime 的 Kubernetes 接口实现。

rktlet-interaction

当 kubelet 请求创建一个 pod 时,rktlet 将启动一个 systemd 服务,该服务将通过运行 rkt 二进制文件创建一个新的 rkt sandbox。 创建 sandbox 后,kubelet 可以请求为 pod 添加/删除容器等。然后 rktlet 将使用相应的 rkt app 命令(app add,app rm,app start 或 app stop)运行 rkt 二进制文件。 其余的 CRI 方法也通过执行 rkt 二进制文件来处理。

rktlet 在 kubernetes 1.9 中为 alpha 状态。

cri-o

cri-o 是一个 CRI 实现,旨在为 kubernetes 提供 CRI 接口支撑。 cri-o 提供了一组最小的工具和接口来下载、提取和管理镜像、维护容器生命周期、并提供满足 CRI 所需的监控和日志记录。 它可以使用任何实现 OCI 运行时规范的 runtime,默认使用 runc。

cri-o-interaction

cri-o 使用配置的 runtime(默认情况下为runc)创建容器 sandbox(pod),然后再次使用 runtime 在该 pod 中创建容器。

从kubernetes 1.9开始,cri-o 被认为是 stable 的。

frakti

Frakti 允许 Kubernetes 通过 runV 直接在虚拟机管理程序中运行 pod 和容器 。

OCI (Open Container Initiative) 兼容 Runtimes

Containers

bwrap-oci

bwrap-oci 是为容器工具 bubblewrap 提供 OCI 兼容的包装器,bubblewrap 是一个无特权的容器工具。作者为:Per Giuseppe Scrivano,bwrap-oci 不满足e2e Kubernetes测试所需的许多功能,例如:无法指定绑定装载的选项,也无法通过 cgroup 限制资源。

crun

crun是用 C 编写的 OCI 运行时规范实现。配合 cri-o,作者 Giuseppe Scrivano 用它来通过了完整的 e2e 套件测试。它比同类型功能中的任何其他工具更小更轻。

railcar

railcar 是 OCI runtime-spec 规范的 rust 语言实现。参考并实现了 runc 的部分功能。一般来说,railcar 与 runc 非常相似,但不支持某些 runc 命令。截至目前不受支持的命令列表为:checkpoint,events,exec,init,list,pause,restore,resume,spec。 railcar 始终运行与容器进程分开的 init 进程。 railcar 在其研发过程号称发现了 OCI 运行时规范中的一些缺陷。通过使用 rust 语言重写 rust,作者能够消除在 go 语言实现过程中对于 c 语言中介层的的需要。

我尝试向编写缺陷那篇文章的作者询问了有关缺陷的具体内容,但是未得到回复。

rkt

rkt 是一个在 LInux 上运行容器而编写的 CLI 工具。它采用多层设计,允许 runtime 可根据实现者的需求更改 。 默认情况下,rkt 结合使用 systemdsystemd-nspawn 来创建容器。 systemd-nspawn 用于管理执行 systemd 以管理 cgroup 的命名空间。容器应用程序作为 systemd 单元运行。通过使用不同与 “1阶段” 镜像,该工具可将运行应用程序的工具可以从 systemd 工具更改为其他任何工具。

rkt 包含与 runc 相同的功能,但是并不使用 OCI runtime-spec。相反,rkt 提供了一个命令行接口来提供类似的功能集。

runc

runc 是一个主要用 go 编写的 CLI 工具(需要 c 语言的中介层 shim 来完成某些 go 不能完成的功能)。runc 是最受欢迎的 OCI runtime,被 containerd 和 cri-o 使用。

在大多数情况下,所有 runc 都会在生成进程时配置 namespace 和 cgroup。 这实际上就是是一个容器,包括 namespace 和 cgroups。

runc 依赖并跟踪 OCI runtime-spec,以保证 runc 和 OCI 规范主要版本保持同步。这意味着 runc 1.0.0 实现了 OCI 1.0 版本的规范。

runlxc

runlxc 是阿里巴巴即将开源的 OCI 兼容 runtime。 runlxc 目前尚未对外发布,与 pouch 配合使用。

Virtual Machines

Clear Containers

cc-runtime 兼容 OCI runtime,其通过启动一个 Intel VT-x 安全 Clear Containers 管理程序,而不是标准的Linux 容器。

runv

runv 是基于 hypervisor 的 OCI runtime,使用 KVM,Xen 或 QEMU 来运行 OCI 镜像。它不会创建容器。

分析

技术

containerd

containerd 是 CRI 实现中最复杂的,提供镜像和 runtime 服务所需的 3 个组件,采用 Go 编写。

这些组件包含两个独立的项目 cri,提供了 19,000 行 Go 代码和 containerd 提供 runtime 的守护进程,运行程序和中介层代码一共 112,000 行。 containerd 默认使用它自己的 runc 分支,但是可以配置任何 OCI runtime 规范兼容的实现。

Docker/containerd 对 CRI 支持处于 beta 阶段。

我无法在 Arch Linux 中编译。由于时间有限,我没有花很多时间在这上面。依据 README.md 中的构建指令无法成功完成 make install.deps 阶段。

rkt

rkt 需要两个组件,rkt 和 rktlet。 尽管如此,它并没有遵循标准,而是以自己的方式做事情,尽管它的 github 描述说:“它是可组合的,安全的,并且建立在标准之上”。 它也是用 Go 编写的。

rkt 组件是 rkt,包含 71,000 行 Go 和 rktlet,包含 4,000 行。

rktlet 对 CRI 支持是 alpha 版。

cri-o

坐在中间是 cri-o。cri-o 为容器编排工具 Kubernetes 定制而生。 凭借其聚焦的单一性,它很快就获得了 “稳定” 状态,仅用了 14,000 行 Go。 它与任何 OCI 运行时规范实现接口,默认为 runc。

cri-o 对 CRI 支持是稳定。

可用性

选择 CRI 实现的一个明显因素是可用性。 您应该能够运行完整的 e2e 测试并能够运行任何 OCI 容器。 如果出现问题,应该能够快速诊断问题。

cri-o

cri-o 可以作为包装安装在大多数发行版中。 使用 cri-o 非常简单。 只需更改 kubelet 标志即可使用默认值以使用cri-o 套接字。可以应用其他配置来添加其他功能,例如 repo 限制,selinux,apparmor seccomp,镜像签名等。调试问题很简单,因为只有两个部分,cri-o 守护程序和 conmon 控制台监视器。

使用 runc 与kubernetes 1.9 我很容易通过完整的 e2e 测试。

cri-containerd

由于无法编译,我无法测试其可用性。

rktlet

rktlet 可以在大多数发行版中作为包安装。使用 rkt 可能需要一点学习曲线。有许多有效的方法来配置它,并且学习哪一个适合的用例并不是非常简单。 rkt 使用 “阶段”,并且很少有关于阶段1 镜像像用于什么目的的文档。你可以创建自己的阶段1 镜像,但似乎有适当的文档可能会提供这种需要。

它只有三个部分,诊断问题通常非常简单,因为它与 systemd 集成,将所有输出留在日志中。

在尝试使用 rktlet 作为我的 CRI 提供程序时,某些容器不能够正常工作。不幸的是,由于时间限制,所以我无法正确诊断。 我希望使用 rkt 就像更改 kubelet 标志一样简单。

社区

作为开源社区的成员,社区的健康也很重要。 一个好的社区应该寻求积极参与,快速审查贡献并有一个记录良好的贡献过程。 它应该有一个积极和有效的用户群,在其中提出问题并获得答案。

cri-o

自 2016 年 9 月 10 日以来已有 1354 Pull Requests,其中 23 个是 Open 的,最早的是 2017 年 9 月 19 日创建的,最后一次 Commit 是在 9 天前。 共有 75 名贡献者,其中 20 人在上个月活跃。 过去 30 天提交的 75% 来自Red Hat 员工。 在过去的 30 天里,已经添加了 5,141 行代码和 删除了 694 行。 5,141 行代表了 37% 的代码变化。 cri-o 有一个积极的贡献者基础,其中大部分是红帽员工,但有红帽以外的员工。 拉取请求需要合并 4 天的平均值。 问题大致相同。有一个 IRC 频道,开发人员在北美工作时间内回答用户问题,响应时间约为 4 分钟。

cri-containerd/containerd

自 2017 年 4 月 14 日以来,已有 484 次针对 containerd/cri 的 Pull Requests,其中 6 次 是 Open 的。最早的Open PR 创建于 2017 年 12 月 8 日,最后一次 Comment 时间是 2018 年 1 月 18 日。共有 31 位贡献者,其中 8 位活跃于上个月。在过去的 30 天里,IBM,Docker,中兴通讯,谷歌和英特尔最终贡献了很多。在过去的 30天里,已经添加了 2,123 行代码和 删除了 1,217 行。 2,123 行代表了 11% 的代码变化。对于 containerd /cri,并没有一个非常活跃的贡献者基础,但是这代表了社区的一个更健康的体现。

自 2015 年 12月 7 日起,共向 containerd/containerd 提交了1,662 个 Pull Requests,其中 13 个是 Open 状态。 最早的 Open PR 创建于 2017 年 8 月 24 日,最后一次 Commit 于 2017 年 9 月 19 日。共有 120 名贡献者,其中 12人在上个月活跃。 过去 30 天中有 46% 来自 Docker 员工,NTT 和 IBM 分别增加了31%。 在过去的 30 天里,已经添加了 4,444 行代码 和删除了 1,724 行。 4,444 行代表 3% 的代码变化。 对于 containerd,有一个非常活跃的贡献者基础,但它仍然是多样化的。

获得对这两者之一的支持需要注册为 docker 社区成员。

rktlet/rkt

自 2014 年11月13 日起,共 2 ,406 次 Pull Requests,其中 49 份是开放的。 最旧的 Open PR 创建于 2015 年 8 月25 日,最后一次 Commente 记录时间是 2016 年 8 月 9 日。我认为 rkt 维护者可以做得更好。 接受的 PR 通常在一周内进行审核和合并。 共有 195 名贡献者,上个月没有任何活动。 rkt 的活动急剧下降。 我不确定这是不是一个活跃的项目。

在看了 rkt 的活跃状况后,我选择忽略了继续查看 rktlet 社区的健康状况。

总结

标准和其相关的 apis 定义提供了非常大的灵活性,并有助于防止锁定在特定的累积技术债中。通过使用 OCI(Open Container Initiative) 容器和 rumtime 标准以及 Kubernetes 的 CRI(Container Runtime Interface )API,可以选择演进任何任何特定选项而不用更整个堆技术栈。

为此,应该弃用 kubernetes 原生支持的 docker 机制,并且只应使用 CRI。

基于 hypervisor 的解决方案,我并没有在选择继续分析,因为在本文章编写的时候,runlxc 尚未开源。

对于 CRI 接口,rktlet/rkt 没有长期的社区支持,而且经过了很长的时间才演变成 beta 状态;cri-containerd 并没有提供一个良好的构建过程,也没有发行版的包装,我无法测试。cri-containerd 仍然只是一个 beta 版本,获得支持有障碍。cri-o 相对稳定,快速,轻松地构建,而且更适合在生产环境稳定运行,并且具备非常好的社区支持。我的结论是,cri-o 是目前最好的选择。

对于 runtime,虽然 railcar 和 crun在技术上可能更优越,但我没有时间测试它们。由于它们是可替代品,因此预先为此工具选择最佳技术并不重要。我建议使用当前默认的 runc,直到可以彻底评估它们为止。

CATALOG
  1. 1. 意见
  2. 2. Kubernetes 接口
    1. 2.1. Native
      1. 2.1.1. Docker
      2. 2.1.2. rktnetes
    2. 2.2. CRI
      1. 2.2.1. cri-containerd
      2. 2.2.2. rktlet
      3. 2.2.3. cri-o
      4. 2.2.4. frakti
  3. 3. OCI (Open Container Initiative) 兼容 Runtimes
    1. 3.1. Containers
      1. 3.1.1. bwrap-oci
      2. 3.1.2. crun
      3. 3.1.3. railcar
      4. 3.1.4. rkt
      5. 3.1.5. runc
      6. 3.1.6. runlxc
    2. 3.2. Virtual Machines
      1. 3.2.1. Clear Containers
      2. 3.2.2. runv
  4. 4. 分析
    1. 4.1. 技术
      1. 4.1.1. containerd
      2. 4.1.2. rkt
      3. 4.1.3. cri-o
    2. 4.2. 可用性
      1. 4.2.1. cri-o
      2. 4.2.2. cri-containerd
      3. 4.2.3. rktlet
    3. 4.3. 社区
      1. 4.3.1. cri-o
      2. 4.3.2. cri-containerd/containerd
      3. 4.3.3. rktlet/rkt
  5. 5. 总结