随着虚拟化技术和软件体系结构的不断发展,云函数(也称无服务计算)逐渐成为一种新的计算范式,该范式允许用户直接部署函数代码,而不必关心基础架构,同时能够提供极高的弹性和快速生灭能力。然而,现有的云函数通常运行在功能较为完备的容器中,存在启动延迟高、资源共享度低、镜像构建和分发时间长等问题。本文分析并验证了影响Linux容器启动时延和资源共享的关键因素,提出了面向云函数的超轻量运行时环境构建方法,该方法从资源隔离、资源限制、文件系统、网络通信等方面对运行时环境进行定义,基于操作系统虚拟化技术,通过进一步抽取共享层来提高资源共享度,通过降低网络隔离和控制组操作的性能瓶颈来加快启动速度。根据上述方法实现了超轻量运行时环境引擎FRE (Function Runtime Environment)。实验中,从顺序与并发启动时间、内存资源利用率、镜像大小等方面对FRE与当前主流的Docker容器引擎进行对比实验,验证了FRE在云函数场景下的有效性。 With the development of virtualization technology and software architecture, cloud function (also known as serverless computing) has gradually become a new computing paradigm, which allows users to directly deploy function codes without having to consider the infrastructure, while providing high flexibility and rapid birth and destroy ability. However, existing cloud functions usually run in fully functional containers, with problems such as high startup latency, low resource sharing, and long image build and distribution time. This paper analyzes and verifies the key factors affecting Linux container startup latency and resource sharing, and presents a cloud function-oriented lightweight runtime environment construction method, which defines the runtime environment from four aspects: resource isolation, resource restriction, file system, and network communication. Based on the operating system virtualization technology, the resource sharing is improved by further extracting the shared layer, and the startup speed is accelerated by reducing the performance bottleneck of network isolation and control group operation. Based on these designs, we implement FRE (Function Runtime Environment), a lightweight runtime environment engine. Finally, we carry out a series of comparative experiments on sequence and concurrent startup time, memory re-source utilization, image volume etc. between FRE and Docker, and verify the effectiveness of the proposed method in the cloud function scenario.
随着虚拟化技术和软件体系结构的不断发展,云函数(也称无服务计算)逐渐成为一种新的计算范式,该范式允许用户直接部署函数代码,而不必关心基础架构,同时能够提供极高的弹性和快速生灭能力。然而,现有的云函数通常运行在功能较为完备的容器中,存在启动延迟高、资源共享度低、镜像构建和分发时间长等问题。本文分析并验证了影响Linux容器启动时延和资源共享的关键因素,提出了面向云函数的超轻量运行时环境构建方法,该方法从资源隔离、资源限制、文件系统、网络通信等方面对运行时环境进行定义,基于操作系统虚拟化技术,通过进一步抽取共享层来提高资源共享度,通过降低网络隔离和控制组操作的性能瓶颈来加快启动速度。根据上述方法实现了超轻量运行时环境引擎FRE (Function Runtime Environment)。实验中,从顺序与并发启动时间、内存资源利用率、镜像大小等方面对FRE与当前主流的Docker容器引擎进行对比实验,验证了FRE在云函数场景下的有效性。
云函数,无服务器计算,虚拟化,容器,资源共享
Shaoling Wu1, Chunqi Tian1, Xing Wan1, Wei Wang2
1Department of Computer Science and Engineering, Tongji University, Shanghai
2School of Data Science & Engineering, East China Normal University, Shanghai
Received: Nov. 3rd, 2020; accepted: Nov. 18th, 2020; published: Nov. 25th, 2020
With the development of virtualization technology and software architecture, cloud function (also known as serverless computing) has gradually become a new computing paradigm, which allows users to directly deploy function codes without having to consider the infrastructure, while providing high flexibility and rapid birth and destroy ability. However, existing cloud functions usually run in fully functional containers, with problems such as high startup latency, low resource sharing, and long image build and distribution time. This paper analyzes and verifies the key factors affecting Linux container startup latency and resource sharing, and presents a cloud function-oriented lightweight runtime environment construction method, which defines the runtime environment from four aspects: resource isolation, resource restriction, file system, and network communication. Based on the operating system virtualization technology, the resource sharing is improved by further extracting the shared layer, and the startup speed is accelerated by reducing the performance bottleneck of network isolation and control group operation. Based on these designs, we implement FRE (Function Runtime Environment), a lightweight runtime environment engine. Finally, we carry out a series of comparative experiments on sequence and concurrent startup time, memory resource utilization, image volume etc. between FRE and Docker, and verify the effectiveness of the proposed method in the cloud function scenario.
Keywords:Cloud Function, Serverless Computing, Virtualization, Container, Resource Sharing
Copyright © 2020 by author(s) and Hans Publishers Inc.
This work is licensed under the Creative Commons Attribution International License (CC BY 4.0).
http://creativecommons.org/licenses/by/4.0/
传统云计算自上而下可以分为应用层(SaaS)、平台层(PaaS)、基础层(IaaS) [
相比于传统服务,云函数具有许多新的特性:其计算的基本单位是函数,粒度更细,单主机实例密度更高;函数的运行一般靠事件驱动,运行环境的创建和销毁更加频繁。这些特性使得云函数运行环境面临新的挑战:隔离的细粒度的计算单位降低了资源共享度,尤其是内存资源;频繁的冷启动会给请求的处理带来额外的时间开销 [
近年来,容器技术一直是学术界和工业界研究的热点,并广泛用于云函数 [
尽管容器具有轻量级优势,但将现有以Docker [
容器启动速度和内存资源利用率对租户和云函数提供商都产生很大的影响,尤其是在开源协作自动化平台领域,这类平台允许用户自定义事件处理函数,为了保证安全性,处理函数通常运行在容器中。当收到事件时,系统启动容器并执行用户的函数脚本,执行完成之后立即销毁函数实例,以取得性能和成本的平衡。运行环境具备更快的启动速度和更高的资源利用率将有效提高该类平台的服务能力,同时能够有效节省成本。
本文的主要贡献有3个方面:
1) 通过分析和实验验证了命名空间与控制组对容器启动速度的影响,以及文件隔离对资源共享带来的影响。
2) 提出了一种面向云函数的超轻量运行时环境构建方法,通过降低网络隔离和控制组操作的性能瓶颈来加快启动速度,通过进一步抽象共享层来提高资源共享度,降低镜像体积和构建、分发时间。
3) 基于以上方法实现了面向云函数的超轻量运行时环境引擎FRE,在顺序和并发启动时间、内存资源利用率、镜像大小等方面进行对比实验,验证了所提方法的有效性。
本文第2节介绍容器优化相关的工作,第3节介绍影响Linux容器启动速度和资源共享的关键因素,第4节介绍面向云函数的超轻量运行时环境整体设计,第5节介绍FRE原型实现,第6节介绍实验和结果分析,第7节总结和未来展望。
类似于从汇编语言到高级编程语言的发展,云计算的发展经历了从裸机,到虚拟机,到容器,再到无服务器计算的变革。无服务器计算代表了云计算发展的下一个前沿,对运行环境的启动速度和资源利用率要求更高,学术界也在不断提出许多优化方案。
一些学者通过精简容器镜像的体积和优化镜像加载方式来提高容器的启动速度。Tyler Harter等设计了能够快速启动容器的新Docker存储驱动程序Slacker [
Pipsqueak [
SAND [
尽管基于操作系统虚拟化的容器技术已经广泛应用在包括云函数在内的各个领域中,但一些学者仍在探索一些新的虚拟化实现方式,Unikernal [
除此之外,Shillaker [
Linux容器通常使用命名空间(Namespace)机制来实现资源隔离,使用控制组(Cgroup)来限制进程可使用的资源。本节将分别讨论命名空间和控制组对容器启动速度的影响。
命名空间可实现进程资源的隔离,进程只能感知当前所在的命名空间下的资源,无法直接感知其它命名空间下的资源。常用的命名空间包括:IPC (进程间通信)、Network (网络)、Mount (文件系统挂载点)、PID (进程ID)、User (用户和组)、UTS (主机名和NIS域名)。在创建新进程时,可以通过指定资源对应的参数来创建新的命名空间,这样就实现了子进程与父进程资源隔离的效果。
我们首先测试了不启用命名空间和分别启用单个命名空间对进程并发创建时间的影响,结果表明,Network命名空间对进程的并发创建影响较大,而其它5种命名空间则几乎无影响。进一步,我们测试了同时启用6种命名空间和同时启用除Network之外的5种命名空间,然后和不启用命名空间时进程创建时间进行对比,结果如图1所示。其中a为512并发情况,b为1024并发的情况,纵轴为创建进程所需的时间(图中的值为实际值开方后的结果)。可以观察到,同时启用除Network之外的5种命名空间时,和不启用命名空间的性能基本一致。Network命名空间对并发创建进程的影响较大,这是由于Network命名空间之间共享单个全局锁 [
控制组可限制进程可使用的资源量。控制组把系统中的所有进程组织成一棵棵独立的树,每棵树都包含系统的所有进程,且和一个或者多个subsystem关联,而subsystem被用来限制每个进程组可使用的资源(CPU、内存、网络接口、磁盘IO等)。当创建新的进程时,可以为进程创建新的Cgroup并把进程加入进去,也可以将进程直接加入已有的Cgroup,从而实现进程的资源限制。
为了测试Cgroup对容器启动速度的影响,我们测试了在不同并发度下创建新的Cgroup(create)和重用已有的空闲的Cgroup(reuse)的性能,结果如图2所示。图中横轴为并发操作数,纵轴为操作所需的时间,可以看出,reuse的方式消耗时间较少(<1 ms),且几乎不受并发度的影响,而create的方式则受并发度影响较大,随着并发创建数量的增多,创建时间会延长。
图1. 命名空间并发创建时间对比
图2. Cgroup并发操作性能对比
容器基于镜像来启动,镜像打包了运行应用所需的所有内容,包括代码、运行时、库、环境变量和配置等。当前容器镜像通常封装了较为完备的文件,这样既可以保证应用运行环境的完备性,也能很好地控制依赖文件的版本,但这种方法却可能导致不同容器依赖的相同文件重复加载进内存,造成内存资源浪费。Docker使用联合文件系统机制在一定程度上提高了内存共享度,但对于其他存储驱动或非同源镜像启动的容器仍然存在内存冗余问题 [
共享库是实现内存共享的一种关键方式,Linux系统使用虚地址空间机制实现了不同应用所依赖的同一个共享库文件在内存中也只有一份,从而能够有效消除内存冗余,提高资源利用率。Ferreira等人 [
通过以上分析可知,网络命名空间和控制组会对容器的创建性能带来一定的损耗,而镜像文件的隔离也造成了内存资源的冗余。网络隔离在传统容器设计中能带来安全性和灵活性,但是在云函数场景中,云函数平台通常具有请求网关,所有请求事件会统一路由到网关,网关预处理后再交给应用函数来处理 [
上节的分析和结论给云函数运行环境的设计带来很多启发,本节根据前面的结论提出了一种全新的面向云函数的超轻量运行时环境构建方法。
云函数超轻量运行时环境基于两个主要设计目标:1) 降低命名空间和控制组对容器创建带来的时间损耗;2) 提高容器间内存资源的共享度。其设计涉及到资源隔离、资源限制、文件系统、网络通信四个方面。
为了保证安全性,资源隔离是必要的,我们采用Linux Namespace机制对进程的IPC、Mount、PID、User、UTS资源进行隔离,而根据上一节的分析结果,Network命名空间会对容器的并发创建产生非常大的时间损耗,且网络隔离给云函数带来的价值并不大,因此不进行Network命名空间的隔离。
同样的,为了保证安全性,需要对容器可使用的资源量进行限制,我们基于Linux控制组机制来实现:当创建容器时,会将容器进程加入CPU、内存、设备等资源对应的控制组中,从而将容器最多可使用的资源量限制在指定的阈值内。在上一节的分析中我们发现控制组的并发创建会给容器创建速度带来很大的影响,因此这里采用控制组缓存池机制,如图4所示,容器引擎会预先创建空闲控制组缓存池(Cgroup Pool),当创建容器时,先从缓存池获取可用的控制组,获取不到时才会执行创建操作。当容器退出时,其绑定的控制组不会被直接删除,而是加入缓存池中以备复用。
从上一节的分析可知,容器之间通过共享库文件可以有效提高内存资源的利用率,为了进一步提高内存资源的共享度,我们重新组织了容器文件系统,进一步抽取出共享层,同一台主机中函数运行所依赖的二进制文件、库文件、依赖包文件和代码及配置文件在磁盘中仅有一份,且在内存中也只加载一份,这种方式有效降低了磁盘和内存冗余。其与Docker容器结构的主要差别如图3所示。
图4展示了容器的文件系统,根据文件是否可以被共享,可以将文件分成2类:只读文件和可读写文件。只读文件具体包括二进制文件(bins)、动态链接库文件(libs)、代码和配置文件(codes)、函数依赖包文件(packages),同时为了保证容器内进程的正确运行,还只读挂载载了主机的/sys,/proc等目录(图中未画出),由于这类文件可被多个容器共享,因此通过只读的方式防止进程恶意操作是有意义且必要的。另一类是可读写文件,具体包括Unix Domain Socket文件和一个可读写的目录workDir。Unix Domain Socket文件用来实现容器进程和容器引擎之间的通信。考虑到函数运行过程中产生需要持久化存储的数据,因此文件系统还包括一个可读写的目录workDir,该目录是租户级别隔离的,同一租户下的所有函数实例均能以读写的方式访问该目录,并且该目录中的文件在主机中持久化存储,不会随容器的删除而删除。
容器所需的文件通过硬链接的方式组织到单独的目录中,并把这个目录作为容器文件系统的根目录挂载点。不同容器中依赖的只读文件会统一映射到主机文件,所以容器间相同的依赖文件具有相同的索引节点号,因此这些文件在磁盘中仅有一份,在内存中也仅有一份。
我们使用Unix Domain Socket机制来实现容器进程与容器引擎之间的通信,如图3所示,Unix Domain Socket文件由容器引擎生成,并被挂载到容器中,容器进程可通过此文件与容器引擎之间建立双向通信通道,后续所有的数据、控制指令等消息均可通过此通道来进行通信。
图3. 2种容器的架构对比
图4. 超轻量运行时环境系统架构
图5. 运行时环境创建流程
图5展示了容器化的运行时环境的创建过程,具体来说:1) 容器引擎首先调用内核的mount系统调用,为容器挂载文件系统;2) 容器引擎调用clone系统调用创建容器的根进程,同时指定生成新的命名空间,这里不包括网络命名空间;3) 创建出来的子进程作为容器的根进程,然后根进程执行chroot操作,更改根目录;4) 容器引擎从缓存池中获取或者创建新的Cgroup,然后调用内核函数将容器进程加入Cgroup中,以限制进程的可用资源量;5) 容器内的根进程准备函数所需要的上下文信息,然后调用exec系统调用创建函数进程,随后开始执行函数。
Docker容器镜像通常封装了较为完整的功能,保证了容器运行环境的一致性和可移植性,Docker采用分层的方式来组织镜像,并实现了容器镜像的配置化,用户使用Dockerfile可以完整构造出一个镜像。基于云函数所需运行环境精简的特点,我们对云函数的镜像做了超轻量处理,其仅包含函数运行所依赖的文件,同时提出了运行时环境描述模版,通过模版可以生成函数环境镜像。运行时环境描述模版指明了函数运行所需的基础运行环境(如Python、Java、Nodejs等)、函数依赖包、函数代码和配置等文件。云函数远程镜像仓库包含了各类语言的基础运行环境。当构造函数镜像时,会根据模版从云函数远程镜像仓库下载相应语言的基础运行环境,函数依赖包文件从当前主流的包管理平台(如PyPI、NPMjs、MvnRepository等)下载,为了保证用户的代码和配置的安全性,代码和配置文件会从指定的私有代码托管平台下载,下载完成后,再根据环境描述模版将这些文件的硬链接组织到指定的目录中,函数镜像即生成完成。
本节基于上一节提到的方法实现了面向云函数的超轻量运行时环境原型FRE (Function Runtime Environment)。FRE的整体架构如图6所示,其主要包含LRE Engine、FRE Local Registry、Remote Registry 3个部分。
图6. FRE超轻量运行时环境引擎架构图
FRE Engine是事件处理和超轻量运行时环境的管理核心,我们基于Golang编程语言实现。Engine中的Gateway以REST API的形式对外暴露服务,负责接收并解析所有外部的HTTP请求,然后再路由到具体的处理模块;Engine负责云函数运行时环境全生命周期的管理,具体包括运行时环境的创建、启动、删除等操作,这些操作均使用Linux内核提供的原生接口;Engine还负责运行时环境镜像的构建工作,包括解析运行时环境模版、从远程仓库拉取文件、生成运行时环境的文件系统等。
FRE Local Registry是主机本地函数依赖环境仓库,包含了运行时环境所依赖的二进制文件、共享库文件、依赖包文件和函数代码等文件,这些文件被所有的运行时环境所共享。FRET (Function Runtime Environment Template)类似于Docker镜像,根据运行时环境描述模版生成,其内部包含了函数运行所依赖的文件,可作为函数运行时环境的文件系统。当构建FRET时,Engine根据环境描述模版首先从本地仓库中查找文件,查找不到时再从远程仓库拉取到本地。运行时环境描述模版采用YAML格式,定义了FRET唯一标识、版本、基础环境、函数代码、附加共享库、依赖包、资源限制等基本属性。
Remote Registry作为远程仓库,负责依赖文件的存储和分发。其中Code Repository为租户私有代码仓库,存放函数代码和配置;FRE Remote Registry存放各类语言的基础环境,主要包含二进制文件和共享库文件;Third-Party Registry为各类第三方公共服务,例如pip、npm、mvn等仓库,公共仓库提供了完善的存储和分发能力,使用第三方公共仓库服务能够利用已有资源,显著降低系统的复杂度。
本节我们将FRE引擎与Docker引擎进行性能对比评估,主要从容器顺序和并发启动时间、容器内存占用情况、容器镜像文件大小四个方面进行测试,实验环境配置为:8核Intel Xeon E5 2.20 GHz处理器;16 GB LPDDR3内存;128GB PCIeNVMe高速固态硬盘;操作系统为Centos 7.6.1810 64位,内核版本为3.10.0-957.el7.x86_64;Docker Server为官方Community 19.03.1版本;FRE为0.1版本,基于go1.15.2构建。根据上述配置,我们准备了3台相同配置的主机用于对比实验。
本节对FRE和Docker容器引擎顺序和并发启动实例的时间进行了测试。我们使用Golang语言构建了专门的测试程序,该程序启动时输出当前纳秒级的时间戳,然后进入睡眠状态等待终止信号。通过记录容器启动时刻的时间戳和测试程序输出的时间戳可以计算出容器启动花费的时间。我们基于该测试程序分别构建了FRE和Docker镜像,作为容器启动时间基准测试镜像。
顺序启动:我们在相同的系统环境下分别使用FRE和Docker进行连续启动容器实例测试,容器基于前面构建的基准测试镜像启动,每组测试仅连续启动的实例数量不同,结果如图7所示。Docker容器的平均启动时间大于400 ms,且随着连续启动数量的增加,平均启动时间也有所上升(>10 ms)。FRE容器的平均启动时间在20 ms左右,且随着连续启动数量的增加,平均启动时间增幅较小(≤2 ms)。相比于Docker,FRE在连续启动不同数量的容器时,启动时间均下降了95%以上,启动速度得到大幅提升。
并发启动:云函数具有快速弹性伸缩的特性,因此容器的并发启动性能至关重要。我们对FRE和Docker的并发启动性能进行了测试,这里复用了顺序启动中的测试方法和测试环境,在创建容器时,将顺序启动改为并发启动。图8展示了FRE和Docker在不同并发量下实例启动时间的累积分布函数(CDF)图,纵坐标为实际值取以2为底的对数后的值。从图中可以看出,在不同的并发量下FRE实例的启动时间都远低于Docker容器实例的启动时间。在256并发下,Docker容器的最长启动时间达到了32,820 ms,而FRE实例仅为776ms。相比于Docker,FRE在不同并发数量下的最长启动时间均下降了97%以上。
图7. 容器顺序启动时间对比
图8. 容器并发启动时间对比
云函数计算粒度更小,有效的内存利用将显著提高单机函数实例的密度,本节针对FRE和Docker容器实例进行内存使用测试。对于FRE,我们构建了Nodejs8、Python2.7、Python3、Java8和PHP5.6这5种语言的基础运行环境;对于Docker,我们使用DockerHub提供的对应版本的镜像。同时针对每种语言的运行环境构建了web应用,然后将web应用放到对应的镜像中,最终每种语言运行环境的Docker镜像和FRE镜像中均包含相同的web应用。
图9. 单类型容器内存占用情况对比
图10. 多类型容器内存占用情况对比
针对以上5种类型的web应用,分别启动相同数量的FRE容器实例和Docker容器实例,然后记录内存使用情况。图9显示了每种类型的容器在单独运行时的平均内存占用情况。由于基于同源镜像的Docker容器可以共享二进制文件和库文件,因此FRE和Docker在单类型的容器内存占用方面差别不大。
为了验证多类型容器间内存共享的情况,我们在不同的时刻启动同类型的Docker和FRE容器实例,并保持容器实例运行,然后记录内存占用情况。同时为了能更全面的测量FRE容器的内存共享情况,我们还在相同配置的主机中直接启动上述5种应用来进行对比测试,结果如图10所示。从图中可以看出,由于非同源Docker容器间无法共享库文件和二进制文件,因此占用的内存与单独启动容器时的内存基本相同。而FRE实现了容器间共享库文件、二进制文件和其他依赖包文件,所以实际占用内存要低,和物理机直接运行的内存占用基本一致。基于FRE启动的容器总内存占用量比基于Docker的方式共减少了约15%,比直接主机启动的方式仅多占用了1%的内存。
较小的镜像体积将显著减少镜像构建和分发的时间,也能有效节约磁盘空间。FRE运行环境仅包含应用函数所需的文件,所以具有更轻量的优势,我们将前面构建的5种类型的FRE镜像和Docker镜像进行文件大小对比,结果如图11所示。从图中可以看出,由于FRE运行环境仅包含应用函数所需的文件,所以镜像体积更小。相比于Docker镜像,FRE镜像体积平均减少了60%。
图11. 基础镜像体积对比
综上所述,本文提出的面向云函数的超轻量运行时环境在本地和实际应用测试中,相比于Docker,其顺序和并发启动速度、内存利用率、镜像大小等方面均有显著的提升;相比于直接使用物理机的方式,性能损耗较低,验证了本文所提方法的有效性。
本文提出了一种面向云函数的超轻量运行时环境构建方法,该方法通过进一步抽取共享层来提高资源共享度,通过降低网络隔离和控制组操作的性能瓶颈来加快启动速度。基于上述方法构建了超轻量运行时环境引擎FRE,最后通过实验验证了FRE相较于主流的Docker容器引擎在顺序和并发启动速度、内存利用率、镜像体积等方面都有明显的提升。
本文实验未涉及到FRE引擎在网络、磁盘I/O、CPU利用率等方面的性能表现,这部分工作将在未来进行补充,同时,函数间通信、镜像安全性等问题也是将来的研究重点。
吴绍岭,田春岐,万 兴,王 伟. 一种面向云函数的超轻量运行时环境构建方法A Lightweight Runtime Environment Construction Method for Cloud Functions[J]. 计算机科学与应用, 2020, 10(11): 1993-2005. https://doi.org/10.12677/CSA.2020.1011211