51

干货 | 容器运行时从docker到containerd的迁移

 4 years ago
source link: https://www.tuicool.com/articles/bYNN7nz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2e26Jnn.jpg!web

供稿 | eBay Infrastructure Engineering 苏菲

翻译&编辑 | 顾欣怡

本文2634字,预计阅读时间8分钟

更多干货请关注“eBay技术荟”公众号

RveEJnI.gif

导读

目前,docker是kubernetes默认的 容器运行时(Container Runtime) 。由于docker过于复杂,操作不便, eBay将容器运行时从docker迁移到containerd,并将存储驱动程序Device Mapper换成Overlayfs。 尽管在迁移过程中,我们遇到了不少挑战,但都一一克服并最终完成了此次迁移。

UBF7Bfn.gif

容器运行时(Container Runtime),运行于 kubernetes(k8s) 集群的每个节点中,负责容器的整个生命周期。其中 docker 是目前应用最广的。随着容器云的发展,越来越多的容器运行时涌现。为了解决这些容器运行时和k8s的集成问题,在k8s 1.5版本中,社区推出了 CRI(Container Runtime Interface, 容器运行时接口) (如图1所示),以支持更多的容器运行时。

Kubelet通过CRI和容器运行时进行通信,使得容器运行时能够像插件一样单独运行。可以说每个容器运行时都有自己的优势,这就允许用户更容易选择和替换自己的容器运行时。

RfeqA3Y.png!web

图1 CRI在kubernetes中的位置

一、CRI & OCI

UBF7Bfn.gif

CRI是kubernetes定义的一组gRPC服务。Kubelet作为客户端,基于 gRPC框架 ,通过Socket和容器运行时通信。 它包括两类服务: 镜像服务(Image Service)和运行时服务(Runtime Service)。 镜像服务 提供下载、检查和删除镜像的远程程序调用。 运行时服务 包含用于管理容器生命周期,以及与容器交互的调用(exec / attach / port-forward)的远程程序调用。

如图2所示,dockershim, containerd 和cri-o都是遵循CRI的容器运行时,我们称他们为 高层级运行时(High-level Runtime)

Fn2Ajiy.png!web

图2 常用的运行时举例

OCI(Open Container Initiative,开放容器计划)定义了创建容器的格式和运行时的开源行业标准,包括 镜像规范(Image Specification)和运行时规范(Runtime Specification)。

镜像规范定义了OCI 镜像的标准。如图2所示,高层级运行时将会下载一个OCI 镜像,并把它解压成OCI 运行时文件系统包(filesystem bundle)。

运行时规范则描述了如何从OCI 运行时文件系统包运行容器程序,并且定义它的配置、运行环境和生命周期。如何为新容器设置 命名空间(namepsaces)控制组(cgroups) ,以及挂载根文件系统等等操作,都是在这里定义的。它的一个参考实现是 runC 。我们称其为 低层级运行时(Low-level Runtime) 。除runC以外,也有很多其他的运行时遵循OCI标准,例如kata-runtime。

RveEJnI.gif

二、Containerd vs Cri-o

目前docker仍是kubernetes默认的容器运行时。 那为什么会选择换掉docker呢? 主要的原因是它的复杂性。

如图3所示,我们总结了docker, containerd以及cri-o的详细调用层级。Docker的多层封装和调用,导致其在可维护性上略逊一筹,增加了线上问题的定位难度(貌似除了重启docker,我们就毫无他法了)。Containerd和cri-o的方案比起docker简洁很多。 因此我们更偏向于选用更加简单和纯粹的containerd和cri-o作为我们的容器运行时。

jyYFNjq.png!web

图3 容器运行时调用层级

我们对containerd和cri-o进行了一组性能测试 包括创建、启动、停止和删除容器,以比较它们所耗的时间。如图4所示,containerd在各个方面都表现良好,除了启动容器这项。从总用时来看,containerd的用时还是要比cri-o要短的。

B3amQvn.jpg!web

图4 containerd和crio的性能比较

如图5所示,从 功能性 来讲,containerd和cri-o都符合CRI和OCI的标准。从 稳定性 来说,单独使用containerd和cri-o都没有足够的生产环境经验。但庆幸的是,containerd一直在docker里使用,而docker的生产环境经验可以说比较充足。 可见 在稳定性上containerd略胜一筹。 所以我们最终选用了containerd。

bEJFVz2.png!web

图5 containerd和cri-o的综合比较

RveEJnI.gif

三、Device Mapper vs. Overlayfs

容器运行 时使用存 储驱动程序(storage driver)来管理镜像和容器的数据。 目前我们生产环境选用的是 Device Mapper 。然而目前Device Mapper在新版本的docker中已经被弃用,containerd也放弃对Device Mapper的支持。

当初选用Device Mapper,也是有历史原因的。我们大概是在 2014年 开始k8s这个项目的。那时候Overlayfs都还没合进kernel。当时我们评估了docker支持的存储驱动程序,发现Device Mapper是最稳定的。所以我们选用了Device Mapper。但是实际使用下来,由Device Mapper引起的docker问题也不少。 所以我们也借这个契机把Device Mapper给换掉,换成现在containerd和docker都默认的Overlayfs。

从图6的测试结果来看,Overlayfs的IO性能比Device Mapper好很多。Overlayfs的IOPS大体上能比Device Mapper高 20% ,和直接操作主机路径差不多。

uimyEji.jpg!web

图6 后端存储文件系统性能比较

四、迁移方案

2IZzY3v.gif

最终,我们选用了containerd,并以Overlayfs作为存储后端的文件系统,替换了原有的docker加Device Mapper的搭配。那迁移前后的性能是否得到提升呢?我们在同一个节点上同时起 10 30 5080 的pod,然后再同时删除,去比较迁移前后创建和删除的用时。从图7和图8可知,containerd用时明显优于docker。

rY7fYnj.jpg!web

图7 创建pod的用时比较

BfmUZjf.png!web

图8 删除pod的用时比较

五、迁移挑战

UBF7Bfn.gif

从docker+Device Mapper到containerd+ Overlayfs,容器运行时的迁移并非易事。这个过程中需要删除Device Mapper的thin_pool,全部重新下载用户的容器镜像,全新重建用户的容器。

如图9所示,迁移过程看似简单,但是这对于已运行了 5年 且拥有 100K+ 光怪陆离的应用程序的集群而言,如何将用户的影响降到最低才是最难的。Containerd在我们生产环境中是否会出现“重大”问题也未可知。

uqeIBvI.png!web

图9 具体的迁移步骤

针对这些挑战,我们也从下面几个方面做出了优化,来保证我们迁移过程的顺利进行。

01

多样的迁移策略

最基本的是以容错域(Fault Domain, fd)为单元迁移。针对我们集群,是 以rack(机架)为单元(rack by rack) 迁移。针对 云原生(cloud-native) 且跨容错域部署的应用程序,此升级策略最为安全有效。针对 非云原生 的应用程序,我们根据其特性和部署拓扑,定制了专属他们的升级策略,例如针对 Cassini 的集群,我们采用了 jenga(层层叠) 的升级策略,保证应用程序0宕机。

02

自动化的迁移过程

以rack by rack的策略为例,需要等到一个rack迁移完成以后且客户应用程序恢复到迁移前的状态,才能进行下一个rack的迁移。因此我们对 迁移 控制器(Controller) 进行了加强,利用 控制平面(Control Plane)监控指标(Metrics)数据平面(Data Plane, 即应用程序)告警(Alerts) ,实现典型问题的自动干预和修复功能,详见图10。如果问题不能被修复,错误率达到阈值,迁移才会被暂停。对于大集群,实现了人为的0干预。

7faAzaa.png!web

图10 自动化迁移流程

03

高可用的镜像仓库

一个rack共有 76台 机器。假设每个机器上只有 50个 pod,就可能最多有 3800个 镜像需要下载。这对镜像仓库的压力是非常大的。 除了使用本地仓库,这次迁移过程中还使用了基于gossip协议的镜像本地缓存的功能,来减少远端服务端的压力 ,具体参见图11。

jiemaen.jpg!web

图11 镜像仓库架构

04

可逆的迁移过程

虽然我们对containerd的问题修复是有信心的,但是毕竟缺少生产环境经验,得做好随时回退的准备。一旦发现迁移后,存在极大程度影响集群的可靠性和可用性的问题,我们就要换回docker。虽然迁移后,在线上的确发现了镜像不能成功下载,容器不能启动和删除等问题,但是我们都找到了根本原因,并修复。所以令人庆幸的是,这个回退方法并未发挥其作用。

RveEJnI.gif

六、用户体验

容器运行时是kubernetes的后端服务。 容器运行时的迁移不会改变任何的用户体验。 但是有一个Overlayfs的问题需要特别说明一下。如果容器的 基础镜像(Base Image)centos6 ,利用 Dockerfile 去创建镜像时,如果用 yum 去安装包,或者在运行的centos6容器中用yum安装包的,会报以下错误:

EFjEjyN.png!web

因为yu m在安装包的过程中,会先以 只读模 ,然后再以 写模式 去打开rmpdb文件。

qaMRRbB.png!web

如图12所示,对于Overlayfs来说,以只读模式打开一个文件的话,文件直接在 下层(lower layer) 被打开,我们得到一个 fd1 。当我们再以写模式打开,就会触发一个 copy_up 。rmpdb就会拷贝到 上层(upper layer) 。文件就会在上层打开得到 fd2 。这两个fd本来是想打开同一个文件,事实却并非如此。

yYBVnaU.png!web

图12

7BB3yqe.png!web

图13

解决方案就是在执行yum命令之前先装一个yum-plugin-ovl插件。 这个插件就是去做一个初始的copy_up, 如图13所示。将rpmdb先拷贝到上层,参考Dockerfile如下:

Abm67bQ.png!web

如果基础镜像是 centos7 ,则没有这个问题,因为centos7的基础镜像已经有这个规避方法了。

七、总结

UBF7Bfn.gif

目前我们 50个 集群, 20K+ 的节点已经全部迁到containerd,历时 2个月 (非执行时间)。 从目前情况来看,还比较稳定。虽然迁移过程中也出了不少问题,但经过各个小组的不懈努力,此次迁移终于顺利完成了。

ENjEjim.png!web

↓点击 阅读原文 ,一键投递

eBay大量优质职位,等的就是你!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK