实操|如何将 Containerd 用做 Kubernetes runtime

日前专为开发者提供技术分享的又拍云 OpenTalk 公开课邀请了网易有道资深运维开发工程师张晋涛,直播分享《Containerd 上手实践 》,详细介绍 Containerd 的发展历程、主要特性,以及如何将其做为 Kubernetes runtime 的上手实践。如下是直播内容整理html

关于做者:张晋涛,现就任于网易有道, 对 Docker、Kubernetes 及相关生态有大量实践及深刻源码的研究,《Docker 核心知识必知必会》专栏做者。PS 讲师长期坚持更新 K8S 生态周报,若有兴趣可订阅其公众号【MoeLove】。node

你们好,今天分享的内容将会从 Kubernetes 宣布弃用 dockershim 提及,介绍 Containerd 相关特性,分享如何将 Containerd 用做 Kubernetes 的 runtime。linux

Kubernetes 宣布弃用 dockershim

不少媒体将该事件宣称为 Kubernetes 宣布弃用 Docker,其实这是一种误导。那么应该如何正确的去看待呢?首先是了解整个事情的来龙去脉,得须要知道 dockershim 是什么。redis

dockershimdocker

dockershim 是 Kubernetes 的一个组件,主要目的是为了经过 CRI 操做 Docker。Docker在 2013 年就出现了,2014 年 Kubernetes 发布并默认使用 Docker 做为容器运行时,而 dockershim首次正式出现是在 2016 年。Docker 在建立之初并无考虑到容器编排或者是考虑 Kubernetes,但 Kubernetes 在建立之初便采用Docker 做为它的默认容器进行时,后续代码当中包含了不少对 Docker 相关的操做逻辑。后期 Kubernetes 为了可以作解耦,兼容更多的容器进行时,将操做 Docker 相关逻辑总体独立起来组成了 dockershim。网络

Container Runtime Interface架构

再说 CRI(Container Runtime Interface)即容器运行时接口,该概念是由Kubernetes 提出并在 2016 年末开始应用,其主要目标是加强 Kubernetes 的可扩展性,能够不固定、不捆绑某一个容器运行时,实现可插拔式的容器进行时。好比可使用 Docker 为容器运行时也可使用其余的例如 rkt,而且但愿经过开放 CRI 这个统一的接口来提升代码的可维护性,而不是须要支持 Docker 时就对 Docker 进行适配,须要支持另外一个运行时就得对其作相关的适配。它但愿是任何一个成为 Kubernetes 的容器运行时都遵照 CRI 统一的接口与规范,实现了 CRI 就能够做为 Kubernetes 的运行时,并不须要关注具体是什么。运维

为何要弃用 dockershim工具

dockershim 的目的是为了 Kubernetes 经过 CRI 操做 Docker,因此Kubernetes 任何的功能变更或 Docker 有任何的功能特性变动,dockershim 代码必须加以改动保证可以支持变动。另一个缘由是随着容器技术的推动,容器运行时已经多种多样了,好比本次分享的主角 Containerd,还有 cri-o 以及 rkt 的容器运行时,不过这个 rkt 容器运行时的项目已经不维护了。阿里云

此外,原先 Kubernetes 须要去调用 dockershim 跟 Docker 作沟通,Docker 的底层运行时是 containerd,能够发现最终都是要调用 containerd,而且 containerd 自身也是能够支持 CRI 的。那为何要先绕过一层 Docker 呢?是否是能够直接经过 CRI 跟 Containerd 进行交互呢?这也就形成了如今 Kubernetes 社区但愿弃用 dockershim。

弃用 dockershim 的影响

终端⽤⼾⽆任何影响。这里指的是使用来自云厂商/使用别人提供的Kubernetes 集群的终端用户,他们不须要关注集群自己的容器运行时究竟是什么,由于和你交互的都是 Kubernetes 自身的 CRI,而任何一个能够做为 Kubernetes 底层容器运行时的东西都必须是兼容 CRI 接口的,上层就已经屏蔽掉这个细节了。

对负责维护 Kubernetes 集群的工做人员有必定影响。当升级 Kubernetes 集群版本时,须要考虑是否切换容器进行时。若是目前在用最新版本 Kubernetes V1.20,而且使用 Docker 做为容器运行时,要考虑它可否正常工做。实际上是能够正常工做的,只是在启动 Kubernetes 的时候会发现一条日志,提醒你当前使用的容器运行时 Docker 已经再也不被 Kubernetes 支持,由于已经准备弃用dockershim,所以会有这个提醒。

Kubernetes 社区计划在 2021 年将dockershim 正式移除。换个角度考虑,既然社区不想在 Kubernetes 源代码当中维护 dockershim 了,那是否是能够把 dockershim 组件给单独的拿出来呢?答案是能够的,如今 Mirantis 和 Docker 已经决定以后共同合做维护 dockershim 组件。此外,还能够经过树外的 dockershim 独立组件,继续使用 Docker 做为容器运行时,而且使用这种方式只须要作一些简单的配置,把原先使用内置的Kubernetes 自身携带的 dockershim 组件,改为使用一个独立的 dockershim 组件,自己变更很小。

那么 Docker 到底还可否使用呢?在我看来,毋庸置疑,Docker 仍然是现阶段容器构建和运行的最佳选择。

快速了解 Containerd

Containerd 是中间层的容器运行时。它构建在平台之下,做为平台下层的一个容器运行时,但又比最底层的容器运行时像 runc、gVisor 要高一点,因此被称为中间层的容器运行时。除此以外也可称做为资源管理器,能够用来管理容器的进程、镜像以及管理文件系统的快照,还有元数据和依赖的管理。既然它能够做为一个资源管理器来使用,若是想要在此之上构建一个属于本身的容器平台就会很方便。

Containerd 是由 Docker 公司建立,而且在 2017年捐赠给了 CNCF,2019 年 Containerd 从 CNCF 正式毕业。Containerd 项目一开始的目标是用来管理容器的进程,以后逐步变动成为一个完整的容器运行时,是 Docker 的底层容器运行时。须要说明的是,containerd 是能够抛开 Docker 与 Kubernetes 自身独立工做的。

Containerd 与 CRI

Containerd 在以前的版本中考虑到了 CRI,但它是将CRI 做为独立的进程存在的。在上图中看到,CRI-Containerd 实际上是一个独立组件,Kubernetes 经过 CRI 接口调用 CRI-Containerd,再由这个组件去调用 containerd。在 Containerd1.1 版本以后对该特性作了从新的设计,它将 CRI 的支持经过插件化的方式来实现,Kubernetes 经过 CRI 接口调用的实际上是 Containerd 当中 CNI 的插件,以此来达到通讯的目的,调用链更少更短了。

Containerd 的特性

  • 支持 OCI 镜像规范,即前文所提到的 runc
  • 支持 OCI 运行时规范。Docker 引导了 OCI 组织的成立,该组织主要有两个规范:镜像规范与运行时规范。这两个规范在 Docker 成立时把 Docker 镜像规范与底层容器运行时规范都给捐赠出来做为它的初始工做
  • 支持镜像的 push/pull 做用
  • 支持容器网络管理。由于能够启动和运行容器,容器启动后支持相互之间的访问,或彼此之间网络的隔离,因此须要支持容器网络的管理
  • 存储支持多租户。Containerd 的相关操做有经过 namespace 来作隔离的,能够指定不一样的 namespace 来实现,它默认的 namespace 叫 default,在 default 的 namespace下面下载多个镜像。可是在其余的 namespace 下看不到这些镜像,也用不到,以此来达到多租户的隔离
  • 支持容器运行时和容器的生命周期管理
  • 支持管理网络名称空间容器以加入现有名称空间,可让某一个容器加入到现有的 namespace 当中

Containerd 的总体架构

上图是 Containerd 总体的架构。由下往上,Containerd支持的操做系统和架构有 Linux、Windows 以及像 ARM 的一些平台。在这些底层的操做系统之上运行的就是底层容器运行时,其中有上文提到的runc、gVisor 等。在底层容器运行时之上的是Containerd 相关的组件,好比 Containerd 的 runtime、core、API、backend、store 还有metadata 等等。构筑在 Containerd 组件之上以及跟这些组件作交互的都是 Containerd 的 client,Kubernetes 跟 Containerd 经过 CRI 作交互时,自己也做为 Containerd 的一个 client。Containerd 自己有提供了一个 CRI,叫 ctr,不过这个命令行工具并非很好用。

在这些组件之上就是真正的平台,Google Cloud、Docker、IBM、阿里云、微软云还有RANCHER等等都是,这些平台目前都已经支持 containerd, 而且有些已经做为本身的默认容器运行时了。

Containerd 主要功能与上手实践

镜像管理

首先是上文中频繁提到的镜像管理。具体操做是须要经过一个 client 去跟 Containerd 作交互。如图中所示,这里选择了ctr 的命令行工具。ctr指定一个address 参数跟 Containerd交互,它是在后台持续运行的一个服务,须要指定它的地址。图中是经过 pull 一个 redis alpine linux 的镜像,接下来经过 image ls就能够看到已经成功 pull 下来的镜像。

容器管理

做为一个容器运行时对容器进行管理是必不可少的功能。一样的经过 -a 参数来指定 address 与Containerd 进行通讯,经过 container create+镜像名称+容器名称来建立一个容器,经过 container ls 能够看到刚才建立的容器。须要注意的是,最后一列叫作 runtime,它的 runtime 叫作 io.containerd.runc.v2,代表是 v2 版本的 Containerd API。

在 Containerd 中经过 container create 建立出来的容器其实并无论用,还须要让其运行起来。这时一般会把它看成一个 task,对它执行 task start,就能够把刚才建立的镜像跑起来了。经过 task ls 就能够看到名叫 redis 的 Containerd,其中有一个正在运行的进程,而且展示出了进程号。

命名空间

须要说明的是,能够经过 -n 来指定一个默认的叫作 default 的命名空间,然后经过 task ls 就看到刚才启动容器的进程,它实际上是在运行中的。若是把 namespace 换一个,好比图中的 moby 就是 Docker 项目当前使用的 namespace 名称,Docker 在使用 Containerd 做为容器运行时的时候,会默认使用它。

继续往下看,经过 ctr -n 指定使用 moby 命名空间,-a 参数指定containerd 的地址,而后 task ls 来看看moby 项目当中到底运行着什么。能够看到有一条记录是正在运行当中的。这条记录如何和 Docker 当中的容器或任务作匹配比较呢?一个简单的办法就是经过 docker ps --no-trunc-- format 跟容器完整的 ID,而后 grep 就能够看到刚才经过 ctr 命令获得的ID 了。

须要注意的是,若是使用 Containerd 做为 Kubernetes 的容器运行时,那么它的 namespace 叫 k8s.io。到这里可能有些人已经发现,Containerd 做为 Docker 的运行时可使用不一样的命名空间,好比 moby。用做 Kubernetes 容器运行时也可使用不一样的命名空间,好比 k8s.io。那是否存在一种办法可让平台当中既有 Kubernetes 又有 Docker,还有 Containerd 呢?答案是确定的,直接将其所有装到一块儿,但不配置 Docker 做为容器运行时,先观察一段时间看看,这也是一种办法。

Containerd 用做 Kubernetes 的 runtime

上图是 Containerd 用做 Kubernetes 的 runtime 整个流程图。Kubernetes 经过 CRI 接口,调用到 CRI plugin,plugin 是Containerd 一个内置的插件,其中包含了最主要的两部分:一是 image service,包含了镜像服务相关的;二是 runtime service,即运行时的服务。若是在 containerd 当中部署了一个项目或服务,它首先会调度到某一台机器,这台机器上的 Kubernetes 就会开始工做,它会查询服务、须要哪些镜像,然后把相关的镜像让运行时给拉下来,再去启动对应的 pod 或者相关的容器。

其次它还会跟 CNI 作交互。CNI 即 Container Network Interface,是Kubernetes 提供的一个容器网络接口)。主要注意的是,交互过程当中可能会提早建立出来 pause的 container,是一个占位的过程,这里先不对此作更深刻的介绍。

当 Containerd 做为 Kubernetes 的容器运行时,配置相对很简单:经过 containerd config default命令能够直接查询到 Containerd 完整的默认配置,下图中能够看到主要是配置 CRI。因此在这里的配置文件当中,经过 plugins.io.containerd.grpc.vi.cri 对其进行配置,首先是 default-runtime,其次配置一个 runtime.runc。

这里简单介绍配置 runc 须要注意的参数。好比 io.containerd.runc.v2 须要配置runtime.type;涉及配置与 runc 相关的一些配置会包含一些 CNI 的配置、目录之类的,具体的配置上图中已经展现了。总而言之若是想要提起来一个服务、一个 pod 或是 container,要注意都是须要配置的。它都会有一个 pause的镜像,即 sandbox_image,能够从中指定一个默认的镜像。固然也能够经过此处换源,加快国内环境下的拉取速度。

最后还有些其余的资源,如本人长期参与的一个项目 KIND( Kubernetes in docker)。这个项目至关因而使用docker 容器做为不一样的 node,能够把这些 node 组成一个集群网络,搭建一套 Kubernetes。而这个集群使用的容器运行时就是 containerd,虽然一开始使用的是 Docker,但后期逐步都将其替换成了 containerd,相似的还有包括 K3C、K3S,效果都是差很少的。

推荐阅读

云原生网络代理(MOSN)的进化之路

聊聊 HTTP 常见的请求方式