写点什么

别费心了,K8s 根本甩不掉 Docker

用户头像
亨利笔记
关注
发布于: 2020 年 12 月 13 日
别费心了,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 权威指南》


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


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


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


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


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 权威指南》,目前京东、当当半价优惠中,不要错过。




要想了解云原生、区块链和人工智能等技术原理,请立即长按以下二维码,关注本公众号亨利笔记 ( henglibiji ),以免错过更新。



发布于: 2020 年 12 月 13 日阅读数: 73
用户头像

亨利笔记

关注

自由自在 网际穿行 2020.04.25 加入

公众号:亨利笔记。作者是执着计算机技术爱好者。中国首个CNCF开源项目Harbor镜像仓库创始人。联邦学习FATE开源项目TSC成员。《Harbor权威指南》《区块链技术指南》作者之一。关注云原生、人工智能和区块链等领域。

评论

发布
暂无评论
别费心了,K8s根本甩不掉Docker