K8s根本甩不掉Docker,原因一说就懂

原标题:K8s根本甩不掉Docker,原因一说就懂

题图摄于北京前门

注:微信公众号不按照时间排序,请关注“亨利笔记”,并加星标以置顶,以免错过更新。

2020云原生生态大会,大咖云集,立刻报名!

上个月 Kubernetes 1.20 beta 版的发布记录(release note)里面声明了 kubelet 的 dockershim 模块已经过时了(deprecated),最快将在 1.23 版本中移除,即大约是一年之后。

这本来是个很普通的消息,没想到上周突然冒出了一批抢眼球的文章,说什么 Kubernetes 终于“甩掉”了 Docker ,一时间这条消息被炒得沸沸扬扬。不明就里的用户被吓得战战兢兢,不知所措。

这个 dockershim 其实是 Kubernetes 早期生长的一颗乳牙而已,现在“恒牙”已经长结实了,乳牙自然脱落就好。所以说,移去 dockershim 只是项目发展的必然结果,对用户影响微乎其微,不必多虑。

下面是一个简单的示意图,根据笔者在《Harbor权威指南》一书中的插图略微修改而来。Kubernetes 的 kubelet 可以支持多种符合 CRI 规范的运行时(runtime),例如 containerd 和 CRI-O。

而用户熟悉的 Docker(图中的 dockerd)不符合 CRI 规范,因此当年 kubelet 内置了一个模块 dockershim,用来对 Docker 进行 CRI 接口的适配。经过几年的发展,CRI 的运行时已经很成熟了,用户在 Kubernetes 中可以直接使用 containerd或者 CRI-O ,无需再通过 dockershim – dockerd – containerd 绕一圈(图中红色箭头),既费时又费力的。由此可见,dockershim 就是那颗已完成历史使命的乳牙而已,无足轻重了。

至于说 Kubernetes 彻底 “甩掉”了 Docker,也只是耸人听闻罢了。在可见的将来,Kubernetes 都无法真正摆脱 Docker 的影响。

先说说容器运行时,符合 CRI 标准的 containerd,以及底层的 runC,都是从Docker 项目中分拆出来的,蕴含了挥之不去的 Docker 印记。

此外,Docker 最精华的部分并不是容器运行时。因为容器的运行时归根到底仅仅是 Linux 内核功能的调用而已,Docker 的容器运行时是可以被替代的。

Docker 最具革命性的创新,是应用程序的封装方式,即容器镜像的格式定义。笔者在 2015 年文章中就旗帜鲜明地指出,Docker的核心价值是容器镜像 。容器镜像是真正改变世界的技术,这个观点至今仍然未变。Kubernetes 上跑的容器,离不开 Docker 镜像的使用。

截至2020年初,Docker Hub 中的镜像累计下载了 1300 亿次,用户创建了约 600 万个容器镜像库。 摘自《Harbor权威指南》

截至2020年初,Docker Hub 中的镜像累计下载了 1300 亿次,用户创建了约 600 万个容器镜像库。 摘自《Harbor权威指南》

Docker 镜像格式已是实际上的标准, OCI 的镜像规范是以 Docker 镜像格式为蓝本制定的,在大多数情况下两者是兼容的。开发者平时用到的“Docker”,除了可以运行容器之外,还有一个重要的功能就是构建容器镜像(例如 docker build),是上图中 dockerd 提供的主要功能之一。这部分面向开发者的功能在运行环境中确实用处不大,是 dockershim 被移除的原因之一。

因为镜像的格式已经标准化了,除了 Docker 以外,其他工具也可以构建镜像,如红帽的 Podman 等,但这些工具万变不离其宗,依然(必须)使用 Docker 开创的镜像格式标准。

Docker 公司有个著名的口号:“ Build, Ship and Run”,翻译过来就是三个动词:“ 构建、传送和运行”,简练地描绘出了应用开发的精髓,其中隐含的意思是:构建镜像、传送镜像和运行镜像,一切皆以镜像为中心。OCI 组织对应有三个规范,分别与上述三个动词对应,即镜像规范(构建)、运行时规范(运行)和正在制定的分发规范(传送)。镜像是容器应用的关键技术,围绕镜像的一系列管理工作将是实际运维中的重要组成部分,这也是我们当初创建 Harbor 开源项目所希望解决的问题。

Kubernetes 还将在较长时间内使用 Docker 创立的技术和规范。为帮助读者理解,下面摘录 《Harbor权威指南》第1章的部分内容,介绍各种容器运行时之间的关系。本公众号后续文章将给大家解释容器镜像的各种原理,请关注本号的文章更新。

Harbor 是原创于中国、广泛应用于全球的云原生开源项目,主要的维护者和贡献者均来自中国。《Harbor权威指南》是第一本全面介绍 Harbor 云原生制品仓库的书籍,由 Harbor 项目维护者和贡献者编写。双十二期间在京东、当当等网站半价优惠中。

容器的运行时 (《Harbor权威指南》节选)

Linux提供了命名空间和控制组两大系统功能,它们是容器的基础。但是,要把进程运行在容器中,还需要有便捷的 SDK 或命令来调用 Linux 的系统功能,从而创建出容器。容器的运行时(runtime)就是容器进程运行和管理的工具。

容器运行时分为低层运行时和高层运行时,功能各有侧重。低层运行时主要负责运行容器,可在给定的容器文件系统上运行容器的进程;高层运行时则主要为容器准备必要的运行环境,如容器镜像下载和解压并转化为容器所需的文件系统、创建容器的网络等,然后调用低层运行时启动容器。主要的容器运行时的关系如下图所示。

OCI运行时规范

成立于 2015 年的 OCI 是Linux基金会旗下的合作项目,以开放治理的方式制定操作系统虚拟化(特别是Linux容器)的开放工业标准,主要包括容器镜像格式和容器运行时(runtime)。初始成员包括 Docker、亚马逊、谷歌和VMware等公司。OCI成立之初,Docker 公司为其捐赠了容器镜像格式和运行时的草案及相应的实现代码。原来属于Docker 的 libcontainer 项目被捐赠给OCI,成为独立的容器运行时项目 runC。

OCI 运行时规范定义了容器配置、运行时和生命周期的标准,主流的容器运行时都遵循OCI运行时的规范,从而提高系统的可移植性和互操作性,用户可根据需要进行选择。

首先,容器启动前需要在文件系统中按一定格式存放所需的文件。OCI运行时规范定义了容器文件系统包(filesystem bundle)的标准,在OCI运行时的实现中通常由高层运行时下载 OCI 镜像,并将OCI镜像解压成OCI运行时文件系统包,然后 OCI 运行时读取配置信息和启动容器里的进程。OCI运行时文件系统包主要包括以下两部分。

  • config.json:这是必需的配置文件,存放于文件系统包的根目录下。OCI运行时规范对Linux、Windows、Solaris和虚拟机4种平台的运行时做了相应的配置规范。
  • 容器的根文件系统:容器启动后进程所使用的根文件系统,由 config.json 中的root.path属性确定该文件系统的路径,通常是“rootfs/”。

然后,在定义文件系统包的基础上,OCI运行时规范制定了运行时和生命周期管理规范。生命周期定义了容器从创建到删除的全过程。

runC

runC 是 OCI 运行时规范的参考实现,也是最常用的容器运行时,被其他多个项目使用,如 containerd 和CRI-O等。runC也是低层容器运行时,开发人员可通过runC实现容器的生命周期管理,避免烦琐的操作系统调用。根据OCI运行时规范,runC不包括容器镜像的管理功能,它假定容器的文件包已经从镜像里解压出来并存放于文件系统中。runC创建的容器需要手动配置网络才能与其他容器或者网络节点连通,为此可在容器启动之前通过OCI定义的事件钩子来设置网络。

由于runC提供的功能比较单一,复杂的环境需要更高层的容器运行时来生成,所以runC常常成为其他高层容器运行时的底层实现基础。

containerd

在OCI成立时,Docker公司把其Docker项目拆分为runC的低层运行时及高层运行时功能。2017年,Docker公司把这部分高层容器运行时的功能集中到containerd项目里,捐赠给云原生计算基金会。

containerd 已经成为多个项目共同使用的高层容器运行时,提供了容器镜像的下载和解压等镜像管理功能,在运行容器时,containerd先把镜像解压成OCI的文件系统包,然后调用runC运行容器。containerd提供了API,其他应用程序可以通过API与containerd交互。“ctr”是containerd的命令行工具,和“docker”命令很相像。但作为容器运行时,containerd只注重在容器运行等方面,因而不包含开发者使用的镜像构建和镜像上传镜像仓库等功能。

Docker

Docker引擎是最早流行也是最广泛使用的容器运行时之一,是一个容器管理工具,架构如下图所示。Docker的客户端(命令行CLI工具)通过API调用容器引擎Docker Daemon(dockerd)的功能,完成各种容器管理任务。

Docker 引擎在发布时是一个单体应用,所有功能都集中在一个可执行文件里,后来按功能分拆成 runC 和 containerd 两个不同层次的运行时,分别捐献给了OCI和CNCF。上面两节已经分别介绍了runC和containerd的主要特点,剩下的dockerd就是Docker公司维护的容器运行时。

dockerd同时提供了面向开发者和面向运维人员的功能。其中,面向开发者的命令主要提供镜像管理功能。容器镜像一般可由Dockerfile构建(build)而来。Dockerfile是一个文本文件,通过一组命令关键字定义了容器镜像所包含的基础镜像(base image)、所需的软件包及有关应用程序。在Dockerfile编写完成以后,就可以用“docker build”命令构建镜像了。下面是一个Dockerfile的简单例子:

FROM ubuntu:18.04

EXPOSE 8080

CMD [ “nginx”, “-g”, “daemon off;”]

容器的镜像在构建之后被存放在本地镜像库里,当需要与其他节点共享镜像时,可上传镜像到镜像仓库(Registry)以供其他节点下载。

Docker还提供了容器存储和网络映射到宿主机的功能,大部分由containerd实现。应用的数据可以被保存在容器的私有文件系统里面,这部分数据会随着容器一起被删除。对需要数据持久化的有状态应用来说,可用数据卷Volume的方式导入宿主机上的文件目录到容器中,对该目录的所有写操作都将被保存到宿主机的文件系统中。Docker可以把容器内的网络映射到宿主机的网络上,并且可以连接外部网络。

CRI和CRI-O

Kubernetes是当今主流的容器编排平台,为了适应不同场景的需求,Kubernetes需要有使用不同容器运行时的能力。为此,Kubernetes从1.5版本开始,在kubelet中增加了一个容器运行时接口CRI(Container Runtime Interface),需要接入Kubernetes的容器运行时必须实现CRI接口。由于kubelet的任务是管理本节点的工作负载,需要有镜像管理和运行容器的能力,因此只有高层容器运行时才适合接入CRI。CRI和容器运行时的关系如下图所示。

CRI和容器运行时之间需要有个接口层,通常称之为shim(垫片),用以匹配相应的容器运行时。

由于 Docker运行时被普遍使用,它的CRI shim被称为dockershim,内置在Kubernetes 的 kubelet 中,由 Kubernetes 项目组开发和维护。其他运行时则需要提供外置的shim。containerd 从1.1版本开始内置了CRI plugin,不再需要外置shim来转发请求,因此效率更高。在安装 Docker 的最新版本时,会自动安装 containerd,所以在一些系统中,Docker 和 Kubernetes 可以同时使用 containerd 来运行容器,但是二者的镜像用了命名空间隔离,彼此是独立的,即镜像不可以共用。因为Docker 和 containerd常常同时存在,因此在不需要使用Docker的系统中只安装 containerd 即可。

containerd最早是为 Docker 设计的代码,包含一些用户相关的功能。相比之下,CRI-O是替代Docker或者containerd的高效且轻量级的容器运行时方案,是CRI的一个实现,能够运行符合OCI规范的容器,所以被称为CRI-O。CRI-O是原生为生产系统运行容器设计的,有个简单的命令行工具供测试用,但并不能进行容器管理。CRI-O支持OCI的容器镜像格式,可以从容器镜像仓库中下载镜像。CRI-O支持runC和Kata Containers这两种低层容器运行时。

更多容器运行时和镜像的说明,请参考新书《Harbor权威指南》,目前京东、当当半价优惠中,不要错过。

责任编辑:

Thenews.cc