containerd containerd

从 Docker 到 containerd:Kubernetes 运行时简史

为什么 Kubernetes 不再直接与 Docker 通信,以及 containerd 与它究竟是什么关系。

9 min 初级

较早的 Kubernetes 教程假定 Docker 就是容器运行时,而当前的教程则假定是 containerd。两者都没有错,它们描述的是 Kubernetes 历史上的不同阶段,理解其中发生了什么变化,可以澄清围绕 ctr、crictl 的大部分困惑,以及为什么 docker ps 在集群节点上不再显示有用的内容。

Docker 先出现,Kubernetes 是围绕它构建的

Docker 让容器变得可用:一个 CLI、一种镜像格式、一个运行一切的守护进程。早期的 Kubernetes 就是专门为编排 Docker 容器而构建的,两者紧密耦合。随着 Kubernetes 的发展,其他运行时(包括 rkt 等)也想要集成,这意味着 Kubernetes 需要一种能与多个运行时协作的方式,而不是硬绑定在 Docker 的 API 上。

CRI 与 OCI:标准化接口

Kubernetes 的答案是容器运行时接口(Container Runtime Interface,CRI),这是 kubelet 用来与已安装的任何运行时通信的 gRPC API,无需为每种运行时编写特定的代码。任何实现了 CRI 的运行时都可以接入 Kubernetes。

CRI 本身依赖**开放容器倡议(Open Container Initiative,OCI)**的标准,这是 Docker 协助建立的规范,正是为了让生态系统不依赖于某一家厂商的实现:

  • **镜像规范(image spec)**定义容器镜像的结构和寻址方式,使得由一个工具构建的镜像可以由另一个工具运行。
  • **运行时规范(runtime spec)**定义如何在主机上实际创建和运行容器,细节包括命名空间、挂载和进程配置。
  • 分发规范(distribution spec)(后来加入)定义如何从镜像仓库推送和拉取镜像。

有了 CRI 和 OCI,运行时与编排器就不必再是同一个产品,甚至不必来自同一家厂商。

Docker 早于 CRI,所以 Kubernetes 必须做桥接

Docker 并不是为讲 CRI 而构建的,因为 Docker 诞生时 CRI 还不存在,而且无论如何 Docker 仍是占主导地位的运行时。Kubernetes 的变通办法是 dockershim:一个将 CRI 调用翻译为 Docker 自身 API 的兼容层,让 kubelet 能够继续与 Docker 协作,而 Docker 自己无需改变。

containerd 一直就在 Docker 内部

Docker 从来不只是一个运行时。它捆绑了 CLI、构建系统、卷与网络管理、认证,而在这一切之下,是真正运行容器的那一部分:一个名为 containerd 的守护进程,它位于低层运行时 runc 之上。containerd 后来被拆分为独立的项目并捐赠给 CNCF,于 2019 年毕业;与 Docker 面向用户的工具不同,它原生支持 CRI。整个关系一句话就能概括:containerd 就是一直存在于 Docker 内部的运行时组件,如今可以独立使用,并且兼容 CRI。

dockershim 的移除

为一种运行时维护一个翻译层,而其他所有运行时都直接讲 CRI,这带来了持续的开销,收益却在递减。Kubernetes 在 1.24 版本(2022 年)中移除了 dockershim,同时也移除了直接以 Docker 作为集群容器运行时的支持。

这并没有破坏现有镜像。Docker 按照 OCI 镜像规范构建镜像,所以仓库中已有的任何镜像在 containerd 上的运行方式与之前完全一样。不再可用的是把 Docker Engine 本身作为节点上实际运行容器的组件;集群需要迁移到原生支持 CRI 的运行时,例如 containerd 或 CRI-O。

今天在节点上意味着什么

在现代节点上,docker ps 要么不存在,要么没有任何相关内容,因为运行你的容器的已经不是 Docker 了。该用的工具是 crictl、ctr 和 nerdctl,本系列的下一篇指南会介绍何时选用哪一个。容器镜像本身,即实际的应用层,不受这段历史的任何影响;变化的完全是其下的底层机制。

热门指南