都2023了!我不允许你还不了解AIGC!立即报名 了解详情
写点什么

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

  • 2019-08-12
  • 本文字数:3437 字

    阅读完需:约 11 分钟

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

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


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



图 1 CRI 在 kubernetes 中的位置

一、CRI&OCI

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


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



图 2 常用的运行时举例


OCI(OpenContainerInitiative,开放容器计划)定义了创建容器的格式和运行时的开源行业标准,包括镜像规范(ImageSpecification)和运行时规范(RuntimeSpecification)


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


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

二、ContainerdvsCri-o

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


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



图 3 容器运行时调用层级


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



图 4 containerd 和 crio 的性能比较


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



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

三、DeviceMappervs.Overlayfs

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


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


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



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

四、迁移方案

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



图 7 创建 pod 的用时比较



图 8 删除 pod 的用时比较

五、迁移挑战

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


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



图 9 具体的迁移步骤


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

01 多样的迁移策略

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

02 自动化的迁移过程

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



图 10 自动化迁移流程

03 高可用的镜像仓库

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



图 11 镜像仓库架构

04 可逆的迁移过程

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

六、用户体验

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



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



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



图 12



图 13


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



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

七、总结

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


本文转载自公众号 eBay 技术荟(ID:eBayTechRecruiting)


原文链接


https://mp.weixin.qq.com/s/9dhmQeCPuA_TysMwhzKqGA


2019-08-12 08:0011957

评论 2 条评论

发布
用户头像
啰嗦了半天你的迁移步骤呢???
2021-07-24 17:29
回复
这只是给你个思路,证明这个方案是行得通的, 步骤不得自己一点点实践么。。。
2021-12-16 18:04
回复
没有更多了
发现更多内容

模块8作业

21°Char

如何做小程序的产品设计?

石云升

产品设计 产品思维 1月月更

架构训练营 - 模块 8 作业

焦龙

架构训练营

设计消息队列存储消息数据的 MySQL 表

Beyond Ryan

设计消息队列存储消息数据的 MySQL 表格

渐行渐远

架构实战营

01-大规模异步新闻爬虫:简单的新闻爬虫

AI悦创

LabVIEW轮廓分析与比较(基础篇—8)

不脱发的程序猿

机器视觉 图像处理 LabVIEW 轮廓分析与比较

盘点 2021|在家办公(偷卷)的一年

悟空聊架构

passjava 悟空聊架构 2021年终总结 盘点 2021

模块八作业-架构实战营

Geek_99eefd

#架构实战营 「架构实战营」

预言机不预测

Rayjun

区块链 预言机

架构实战营模块八作业

孙志强

架构实战营

Rust 入门 快速配置 Rust 开发环境并编写一个小应用!

贾献华

rust

架构训练营 -- 模块五

LJK

架构训练营

第八单元总结

张靖

#架构实战营

架构训练营 - 模块八作业

VegetableBird

架构训练营

模块八 设计消息队列存储消息数据的 MySQL 表格

小朱

架构实战营

【架构实战营】模块八作业

liu🍊

模块八作业

doublechun

架构师实战营

Greenplum 内核源码分析 - 分布式事务 (四)

王凤刚(ginobiliwang)

源码分析 分布式事务 最佳实践 greenplum

架构实战营模块八作业

spark99

架构实战营

看金沙遗址-理科生博物馆的正确打开方式

wood

成都 300天创作 博物馆 金沙遗址

Go编译原理系列3(词法分析)

书旅

Go 编译原理 后端技术

flink输出到iceberg

聚变

为什么私有方法上的Spring Cache注解不生效?

看点代码再上班

spring 源码 程序员 spring aop Spring Cache

复习

IT蜗壳-Tango

1月月更

02 Prometheus之监控方法论及指标

穿过生命散发芬芳

Prometheus 1月月更

流沙·进化岛|编程、艺术、新媒体等皆可学(你的终身进化岛1000+人)

AI悦创

AI悦创

一个cpp协程库的前世今生(八)env的状态与标识位

SkyFire

c++ cocpp

设计消息队列的Mysql表结构

AHUI

「架构实战营」

节日与科技撞个满怀,一场氛围感的升级

脑极体

模块8作业

Asha

  • 需要帮助,请添加网站小助手,进入 InfoQ 技术交流群
容器运行时从docker到containerd的迁移_技术管理_eBay技术荟_InfoQ精选文章