[牛思杰 潘碧莹 庞涛]
容器技术是一种轻量级、可移植、自包含的软件打包技术,使应用程序可以在几乎任何地方以相同的方式运行,现阶段已经发展成为应用分发和交付的标准技术。
随着移动通信技术发展到5G 时代,容器技术助力云边计算一体化,实现了云端和边缘端的业务灵活编排,任务灵活调度,资源按需分配,但终端的发展还是垂直烟囱式、碎片化的现状,终端和边云不能按需进行任务的动态分配和资源的灵活共享,并且很难实现不同终端之间的资源共享。受限于终端相对紧缺的硬件资源以及各种终端之间硬件架构的差异,目前云端以及边缘端上广泛应用的容器技术栈无法很好地运行在各种类型的终端上。因此,终端侧亟需一次技术革新,借鉴云端容器技术,研发可通用在各种异构终端上的轻量化容器技术,推动终端的虚拟化改造,以满足云网融合的战略机遇下端边云网一体化的发展需求。
本文将通过研究对比目前业界的主流容器技术,结合终端容器的特点和需求,总结终端轻量化容器技术的发展方向。文中第2 节将介绍本次研究评测的背景,包括容器技术的发展现状、目前业界4 种主流容器引擎即Docker、Containerd、iSulad 和balenaEngine 的技术特点,以及相关文献中工作的梳理与借鉴。第3 节中将介绍本次测试的测试指标、工具和测试环境。第4 节中将展示本次测试的测试结果,并针对结果数据进行研究评测。第5 节中将给出本次测试的测试结论。第6 节中进行总结并展望未来终端轻量化容器技术的发展方向。
容器技术的概念可以追溯到1979 年的Unix Chroot,Chroot 为每个进程提供一套隔离化磁盘空间,进而虚拟化进程文件目录,不能够防止进程恶意访问系统。2000 年提出Freebsd Jail技术,包含有进程沙箱机制以对文件系统、用户及网络等资源进行隔离,实现了操作系统级别的虚拟化,然而这种简单性的隔离影响Jails 中应用访问系统资源的灵活性[1]。2004 年出现了专为X86 和SPARC 系统设计的虚拟化技术Solaris Zone,该技术真正的引入了容器资源管理的概念。Solaris 容器是系统资源控制和通过 "区域" 提供边界隔离的组合,Solaris Zone 技术让应用在隔离的Zone 中运行,并实现有效的资源管理,每一个Zone 拥有自己的文件系统,进程空间,防火墙,网络配置等[2]。2008 年提出的LXC(Linux Containers)通过Namespaces和Cgroups 实现容器的隔离和资源控制,将容器支持集成到主流Linux 内核,允许使用单个Linux 内核在宿主机上运行多个独立的系统[3]。随着容器技术的发展,在原版Linux 内核上运行容器已经成为行业共识,容器技术因其特有的优势也在不同领域和设备中广泛应用,如云平台的Docker 和Containerd、嵌入式设备的Belena、资源受限端侧设备的isulad,也在虚拟化技术中发挥着关键作用。
容器位于物理服务器及其主机操作系统之上,将应用所需的软件和依赖项目打包成标准化单元,以用于开发、交付和部署。容器具有如下特点:
隔离性:容器底层运用Namespaces 和Cgroups 技术实现容器进程对外界的隔离,即在单一主机上安全的运行多个应用,其中的每个应用都有自己的隔离环境。
高效性:容器对操作系统进行抽象,每个容器只包括应用程序与必要的依赖资源,共享操作系统内核,因此占用空间小,能够快速启动和迁移。
可移植:容器消除了开发、测试、生产环境的不一致性,容器应用可以被移植到其它宿主机器中,为开发和运维提供标准化的解决方案。
2.1.1 Docker
Docker 是一个开源的应用容器引擎,基于Go 语言开发并遵循了Apache2.0 协议开源。Docker 可以让开发者将应用和依赖包打包到一个轻量级、可移植的容器中,发布到任何流行的Linux 服务器[4]。
Docker 首次发布时由两个核心组件构成:Docker daemon 和LXC(Linux Container)。Docker daemon 是单一的二进制文件,包含诸如Docker 客户端、Docker API、容器运行时、镜像构建等;LXC 提供了对诸如命名空间(Namespaces)和控制组(Cgroups)等基础工具的操作能力[5,6]。Docker daemon、LXC 和操作系统之间的交互关系如图1 所示。
图1 Docker 调用链示意图
随着时间的推移,Docker 公司也在不断完善Docker 的功能。Docker 公司开发了与平台无关的工具Libcontainer,将容器引擎的底层实现抽象化到Libcontainer 接口,只要实现了Libcontainer 定义的一组接口,Docker 就可以运行,进而实现基于不同内核为Docker 上层提供必要的容器交互的功能。在Docker 0.9 版本中,Libcontainer 取代LXC成为Docker 默认的执行驱动。2015 年,Docker 公司将Libcontainer 捐给一个完全中立的基金会管理,并改名为runC。功能完备的Docker daemon 使得Docker 难于变更、运行越来越慢,不利于Docker 生态的发展,因此Docker公司开始着手模块化Docker daemon 进程,拆解出其中的功能特性,重构为小而专的工具来实现这些功能。
2.1.2 Containerd
2016 年,Docker 把负责容器生命周期的模块拆分出来捐赠给CNCF 社区,这便是Containerd 的前身,后社区为其添加了镜像管理模块和CRI 模块,使得Containerd具备支持kubelet 创建Pod 所需的全部功能。2019 年2 月Containerd 从CNCF 社区毕业,进入生产环境作为容器运行时使用。Containerd 项目源自Docker,在集成Docker 特性的基础上,也进行了功能优化,通过将image、filesystem、runtime 解耦合,实现插件式的拓展和重用。Containerd 支持OCI 和CRI 规范,可以与低级运行时(runC)和容器编排工具(如Kubernetes)交互,不需Docker-shim 的参与,简化了管理容器生命周期的调用链,便于Kubernetes 项目的维护工作[7]。
目前,Containerd 已经成为了一个工业级容器技术了,采用标准的C/S 架构,服务端通过GRPC 协议提供稳定的 API,客户端通过调用服务端的API 进行高级的操作。开发人员或者终端用户可以在宿主机中管理完整的容器生命周期,包括容器镜像的传输和存储、容器的执行和管理、存储和网络等。在容器技术逐步标准化后,Containerd 在相关的技术栈中将占据非常重要的地位,Containerd 提供的核心服务将成为底层管理容器的标准。届时,更上层的容器化应用平台将直接使用Containerd 提供的基础服务。在开源社区方面,Containerd 一直保持很活跃的状态,这个项目的成熟离不开社区广大contributors 的贡献。
2.1.3 iSulad
iSula是华为开源的一种云原生轻量级容器解决方案,可通过统一、灵活的架构满足ICT 领域端、边、云场景的多种需求[8]。iSulad 是iSula 技术链中的通用容器引擎,它提供统一的架构设计来满足CT 和IT 领域的不同需求。相比Golang 编写的Docker,iSulad 基于C/C++编写,具有轻、灵、巧、快的特点,不受硬件规格和架构的限制,底噪开销更小,可应用领域更为广泛。iSulad 作为轻量化的容器底座,可以为多种场景提供灵活、稳定、安全的底层支撑。
架构设计上,除了启动容器部分需要通过fork/exec的方式,其他部分均使用调用函数库的方式加快执行速度;通过将镜像和rootfs 部分独立为服务,以及优化镜像模块元数据的隔离性,实现了不同镜像和rootfs 之间的操作完全隔离。
由于iSulad 资源占用较低,对容器引擎占用资源限制要求比较高的场景,如CT、嵌入式、边缘侧等,可以使用iSulad 容器引擎。iSulad 支持北向CRI 标准接口,可应用于对接Kubernetes 实现容器的编排管理。此外,iSulad 除了支持运行普通容器,还支持运行系统容器、安全容器满足用户不同应用场景下的需求。
2.1.4 balenaEngine
balenaEngine 是Balena.io 公司推出的一款与Docker兼容的用于嵌入式设备上的容器引擎,专门针对嵌入式和IoT 用例而构建,并且与Docker 容器兼容[9]。balenaEngine 基于Docker 的Moby Project,支持容器增量,二进制文件更小,更保守地使用RAM 和存储,并专注于容器抽取的原子性和持久性。
与Docker 相比,balenaEngine 具备以下特点:①支持容器镜像的增量拉取,节省10x~70x 的下载带宽。②容器镜像可以中断后重新拉取。这意味着,当嵌入式设备掉电再重新上电时,容器镜像可以继续拉取。③容器执行文件体积是Docker 执行文件体积的2/7 左右。④改进了镜像拉取使用页面缓存的机制,避免在低内存的嵌入式设备下出现页面缓存抖动。⑤去除了Docker 几个不适用于嵌入式场景下的功能,例如Docker Swarm,插件支持,云端日志系统,overlay 网络驱动等。⑥只支持Linux 系统。
关于终端容器技术研究与评测的文献很少,大多数相关的研究只对容器在基于X86 架构的服务器上运行时的各项性能指标进行了部分的研究对比。
通过前期的调查研究,文献[10]中对比了Containerd和crio 在,runC 和gvisor 的底层runtime 下,10/20*run 命令下,CPU、MEM、Disk I/O 的性能对比。文献[11]中测试了不同容器引擎在串行和并行情况下创建、启动、停止、删除等容器生命周期操作下的耗时。[12]中使用cAdvisor和mpstat 测试容器运行产生的CPU 负载,使用docker stats 测试容器中运行线程请求的CPU 资源,使用iostat 工具监测系统磁盘I/O。[13]中介绍了在docker 与KVM 中运行程序的性能对比,对比的主要性能指标为系统boot 时间和其中进行CPU 运算的速度,在两项指标中docker 均明显优于KVM。[14]中针对CPU、内存、磁盘I/O、请求负载、处理速度等方面对docker 和虚拟机进行了性能对比,测试中使用了Sysbench,Phoronix,和apache benchmarking 等测试工具。[15]对资源相对受限的移动边缘计算环境下进行了研究,发现无论容器化进程的CPU 周期消耗如何,Docker 进程的CPU 使用是恒定的。文献[16]中提出针对CPU 和I/O 密集型任务负载对Docker 进行了基准测试,结果显示了当容器生命周期结束时,Docker 引擎的资源开销从10%减少至5%。[17]中的试验将Docker 与Flockport容器(基于LXC 的一款开源容器)进行了比较,结果显示,对于这两种容器实现,I/O 和系统调用是造成性能开销的主要原因,Docker 的内存性能略优于Flockport。[18][19]中多个测试维度对比了虚拟化技术和容器技术的系统资源开销。
综合考虑相关文献中的测试方案以及终端设备的相关硬件指标,本次测试的测试指标项包括。
磁盘空间占用:对于很多的终端设备来说,磁盘空间都是很紧缺的资源,不同于云端以及边缘端服务器动辄数以TB 的磁盘阵列,有些终端(例如摄像头)上的磁盘空间只有数十MB,这意味着体积过大的容器引擎在终端上是行不通的。本次测试中使用ls 查看容器引擎二进制文件的尺寸大小。
内存占用:内存占用指的是此进程所开销的内存,内存占用过大,会影响机器的整体性能。终端设备通常内存配置较低,所以要求容器引擎的内存占用要尽量减少。本次测试中使用free 命令查看容器引擎服务的系统内存占用情况。
容器生命周期操作耗时:容器的生命周期操作是指容器的创建、启动、运行、停止和删除等操作,其中运行操作可视为创建和启动两个操作顺序执行。容器生命周期各项操作的耗时是衡量容器引擎性能的重要指标,尤其在容器的批量操作时,细微的差距会被倍数的放大。本项测试的前置条件是使用docker 官方的busybox 镜像且镜像已提前拉取到本地,测试中使用shall 编写脚本进行容器各项操作耗时的测试,并对单独执行一次操作以及并行执行十次操作两种情况进行分别测试。
CPU 使用率:CPU 使用率是系统性能监测中最重要的性能指标之一。它是确定应用程序处理速度的主要分析值,而处理速度是网络和服务器运行状况的关键性能指标。终端设备通常CPU 算力配置较低,对容器引擎的CPU 使用有一定限制。本项测试的前置条件是使用docker 官方的busybox 镜像且镜像已提前拉取到本地,测试中使用sar工具查看容器生命周期操作中CPU 的实时使用率,并对单独执行一次操作以及并行执行十次操作两种情况进行分别测试。sar 是 sysstat 工具包的组成部分,可以收集并报告操作系统中广泛的系统活动,包括CPU 使用率、上下文切换和中断速率、页换入和页换出速率、共享内存使用情况、缓冲区使用情况以及网络使用情况。
磁盘I/O 占用磁盘的输入输出。输入指的是对磁盘写入数据,输出指的是从磁盘读出数据。磁盘I/O 负载过高会造成数据阻塞,导致系统响应速度变慢。本项测试的前置条件是使用docker 官方的busybox 镜像且镜像已提前拉取到本地,测试中使用sar 工具查看容器生命周期操作中磁盘I/O 占用情况,并对单独执行一次操作以及并行执行十次操作两种情况进行分别测试。
本次测试的硬件环境基于树莓派4B0,如图2 所示。
图2 树莓派4B 嵌入式开发板样式图
主要配置情况如下。
处理器:Broadcom BCM2711 ARM V8 Cortex-A72 64 位1.5 GHZ 4 核处理器
内存:4G DDR4 内存
硬盘:16G 硬盘
本次测试的软件环境如下:
操作系统:CentOS 7.3.10
容器软件版本:Docker 20.10.6,Containerd 1.4.4,iSulad 2.0.0,Balena-engine v17.12.0
4 种容器引擎的体积占用磁盘空间情况如图3 所示,其中灰色的柱体表示各容器引擎软件包总体大小,由于四种容器引擎均采用CS 架构设计,所以软件包均包括服务器端和客户端两个二进制文件,除此之外,软件包中还包含一些功能组件。
图3 磁盘空间占用对比图
从对比可以看出,使用C 语言编写的iSulad 在服务器端以及客户端体积上优势明显,而Docker 作为目前在云端和边缘端最广泛运用的容器引擎,体积远高于其他3种容器引擎。值得一提的是,balenaEngine 的服务器端和客户端以及其他组件集成到了同一个二进制文件中,故其软件包的体积即为服务器端的体积。Containerd 的软件包总体大小为100 M 左右,对于目前的手机终端来说尚可接受,但是对于诸如摄像头等一些资源受限的终端来说还是过于庞大。
4 种容器引擎启动后进程的内存占用情况如图4 所示,Docker 进程的内存占用超过120 M,对于很多中低配置的终端设备来说是沉重的负担,这意味着无法保留足够多的内存来保证容器内业务应用的运行。Containerd、iSulad 和balenaEngine 进程的内存占用情况相对较低,相对Docker 都减少了一半以上,分布在40~60 M 之间,其中Containerd 内存占用最少,为43.07 MB。以一个普通智能摄像头的的配置为例,内存的配置为128 M,上述3种容器引擎的内存占用情况是在相对可以接受的范围之内的。
图4 进程内存占用对比图
4 种容器引擎生命周期各项操作耗时情况如图5、6所示,结果显示,不管是在单次还是并行10 次执行生命周期操作情况下,4 种容器引擎的性能优劣顺序是基本一致的。Docker 作为4 种容器引擎中体积最大、占用内存最多的一种,它的各项生命周期操作耗时也是最多的,单次运行容器耗时达到了1 201 ms,而该项最优的Containerd耗时仅为303 ms。从测试结果中可以看出,Containerd和iSulad 的整体表现明显优于Docker 和balenaEngine,Containerd 在运行容器操作上最具优势,而iSulad 停止、删除容器操作上略胜一筹。从整体而言,并行10 次的执行耗时会比单次耗时的10 倍少30%~50%。
图5 单次生命周期操作耗时对比图
图6 并发10 次生命周期操作耗时对比图
4 种容器引擎生命周期各项操作的CPU 使用情况如图7、图8 所示。在单次执行情况下,4 种容器引擎在容器的创建和运行两种操作的耗时差距很大,Containerd 和iSulad 的表现明显优于Docker 和balenaEngine。而在容器的停止和停止方面,iSulad 和balenaEngine 具有一定优势。而在并发10 次执行情况下,Docker 和balenaEngine 基本CPU 满载,在测试的硬件环境下达到性能瓶颈,相比之下iSulad 的表现最好。
图7 单次生命周期操作CPU 使用对比图
图8 并发10 次生命周期操作CPU 使用对比图
4 种容器引擎生命周期各项操作的磁盘I/O 占用情况如图9、图10 所示。从执行情况来看,在单次执行情况下,除Docker 外,其他3 种容器引擎均低于20%,在并发10 次执行的情况下,balenaEngine 各项操作的磁盘I/O占用飙升,成为了4 种容器引擎中最占用资源的一种,而Containerd 和iSulad 的表现基本和单次执行的结果呈现倍数关系。值得注意的是,4 种容器引擎在容器的停止操作时的磁盘I/O 占用均趋近于[20],说明该过程中不涉及磁盘数据的读写。
图9 单次生命周期操作磁盘I/O 占用对比图
图10 并发10 次生命周期操作磁盘I/O 占用对比图
对于终端上的容器引擎来说,磁盘空间占用以及进程的内存占用首先需要考虑的,这决定了容器引擎是否可以成功运行。从磁盘空间占用比较来看,基于C 语言编写的isulad 最为轻量,其他依次为balena-engine、Containerd和Docker,前三者相对较为轻量,在尺寸上基本满足在终端设备上运行的需求。从运行占用内存方面比较,Containerd 整体来说占用内存最少,40 MB 左右的内存占用可以保留足够多的内存来保证容器内业务应用的运行,对于终端设备来说是在可以接受的范围之内的。从容器生命周期耗时比较来看,Containerd 和isulad 整体优于其他两种种容器引擎,但在容器启动时间上Containerd 更具优势;从容器生命周期的CPU 使用率和磁盘I/O 占用方面比较,isulad 和Containerd 相对来说更符合容器轻量化的要求。整体来说除了Docker 外,其他3 种容器引擎方案均在一定程度上满足了终端对于容器引擎轻量化的需求。
随着容器技术的发展,Docker 容器引擎的地位受到众多后起之秀的挑战,不久前,Kubernetes 官方发布公告宣布自v1.20 起放弃对Docker 的支持,届时用户将收到Docker 弃用警告,并需要改用其他容器运行时。而得到CNCF 官方推荐并作为内置模块集成进Kubernetes 的Containerd 将在容器引擎领域得到良好的发展。在特定的应用场景下,Docker 等主流容器引擎显得力不从心,因此一些针对某种用例进行过专门优化的容器引擎技术纷纷入场。本文以终端作为容器的硬件载体,以可用于终端的轻量化容器引擎为出发点,对目前业界的4 种主流容器引擎进行研究评测。
Docker 作为广泛应用于云端的成熟容器解决方案来说,对于终端并不适用。业界有通过尝试裁剪Docker 以实现容器的轻量化方案,对Docker 进行轻量化改造,对其裁剪和精简化、去除不需要的功能、优化组件结构等,甚至还对Go 语言环境的编译进行了优化。但是,由于Docker 本身体积比较大,且由于要运行在端侧的嵌入式设备上,这种裁剪和压榨资源的做法所能取得的效果很有限。
iSulad 作为华为OpenEuler 开源系统上的全量的容器软件栈其中的轻量化容器引擎,目前还处于项目的起步阶段,虽然在对比测试中的各项性能指标相对其他容器引擎有一定优势,但目前和业界的相关合作并不是很多,还没有得到充分的应用和验证。
balenaEngine 虽然比Docker 轻量很多,但在实际架构设计上依然延续Docker 的思路,从本次测试的结果也可以看出,在很多测试指标上都与Docker 较为接近,目前看来其在容器引擎这条赛道上很难脱颖而出。
Containerd 在容器引擎的测试对比中表现比较优秀,并且作为CNCF 官方推荐,在开源社区获得了广泛的关注,其在整个容器生态链中的影响力也在不断提高。另外,Containerd 系统架构的插件机制决定了它可以很灵活地裁剪体积和拓展功能,对于其在各式各样终端设备上的应用增添了可行性。
5G 的全面普及,推进了“万物互联”时代的到来,在这个智能终端之间保持随时随地的连接互通的泛连接时代,终端形态也逐渐的扩展和变化,泛智能终端设备必将从单设备、多设备逐渐走向跨平台、分布式。在这样的趋势下,终端容器技术将向着,轻量化、异构化、模块化的方向发展,助力端边云网一体化合力构建的全场景生态和服务。