APP下载

龙芯平台1.13版本Docker移植研究

2022-07-12吴平凡刘显德吴少刚

计算机应用与软件 2022年6期
关键词:龙芯容器架构

吴平凡 刘显德 吴少刚

1(东北石油大学计算机与信息技术学院 黑龙江 大庆 163318) 2(江苏航天龙梦信息技术有限公司 江苏 苏州 215500)

0 引 言

虚拟化技术是一种被广泛应用的硬件资源共享技术,不仅包括了以KVM、Xen、Hyper-V、VMware ESXi为代表的传统硬件级虚拟化技术,近年来以Docker为代表的新一代容器技术,在克服了传统容器技术难以标准化的困难后,凭借轻量级的特性在虚拟化领域形成了颠覆性的影响[1]。

事实上,传统的容器技术数十年前就已经出现,比如常见的Chroot[2]命令在1979年就已经出现,被认为是最早的容器化技术之一,用来隔离一个进程的文件系统。还有BSD jails、LXC、OpenVZ、Linux VServer等技术都可以称为传统容器技术[3-4]。

由于传统容器技术未能提供应用标准化的运行环境,因此在许多应用领域中随着虚拟机技术的广泛运用而逐渐销声匿迹。新一代的容器技术则从一开始就以提供标准化的运行时环境为目标,实现了底层操作系统与物理主机的解耦。因此Docker这一类新容器技术往往被称作应用程序容器,旨在为单个进程进行打包和运行服务。

X86平台上关于Docker技术的相关研究[5-7]很早便已经展开。龙芯[8-10]是我国第一个自主研发的通用微处理器,基于龙芯3A/3B 3000系列的计算机和服务器已经在国家核心部门的关键信息系统中得到了批量应用。随着云计算模式的普及,对支持虚拟化技术的龙芯服务器系统的需求日益迫切。在龙芯平台上的虚拟化技术研究方面,相关文献进行了关于MIPS架构下的内存虚拟化研究[11-12],以及支持跨指令集二进制代码兼容性的虚拟机方案研究[13]。而在容器技术支持方面,龙芯平台Fedora21系统支持1.6.0版本Docker方案,而Docker技术随着Linux内核升级有了较大的架构级更新,本文工作源自龙芯平台Fedora28系统对1.13版本Docker的需求,对国产龙芯服务器在云模式下的应用推广具有重要的工程应用价值。

鉴于X86的Fedora28系统安装源初始默认Docker1.13.1方案,所以本文针对该版本进行了面向龙芯平台的移植优化,添加了NETNS相关的MIPS架构参数,变换了设备号存储变量格式,去除了冗余的信号变量定义,支持新的存储驱动模式,利用OVERLAY2存储驱动重新构建了龙芯Fedora28系统的镜像仓库和常用镜像。评测结果表明,移植优化后的1.13版本Docker方案在龙芯平台上表现良好,相比老版本Docker方案系统调用消耗降低了10%左右,进程通信相关性能提升了50%左右。

1 Docker技术

1.1 Docker架构演化

从发布至今,Docker这个基于Go语言开发,利用Linux Container技术的核心管理引擎受到越来越多的关注和应用。在2017年3月,Docker分为两种版本:提供给个人开发者和小型团队使用的社区版CE,以及提供收费服务的企业版EE。同时分离了Github上托管的源代码仓库,将原Docker项目改名为Moby,由Moby这个新的用于推进软件容器化运动的开源项目组织与社区开发者共同维护[14]。虽然版本号的变化十分剧烈,但是Docker 17.03+版本的接口仍然沿用1.13+并完全兼容。

Docker早期的架构是简单的Client-Server架构,通过TCP或UNIX套接字向在Host上运行的Daemon守护进程发送请求命令获取服务。

之后在1.10版本时推出了Libcontainer模块,将底层实现都抽象化到Libcontainer的接口上,使得底层容器的实现变为一种可控方案。2015年6月,OCI(Open Container Initiative)组织成立以实现开放容器的标准化,主要包括容器运行时标准(Runtime Spec)和容器景象标准(Image Spec)。因此为适应OCI开放容器标准,Docker容器的运行不再是简单地由容器守护进程Daemon来启动,而是通过Dockerd集成Containerd、Runc等多个组件来实现容器的启动,Daemon进程在不断地重构过程中功能渐渐拆分细化,被分配到了各个单独的模块中,如图1所示。

图1 容器运行时各组件间关系

Dockerd以Engine API(REST)的方式对外提供服务,将所有对于容器的创建和运行等操作都通过GRPC协议发送给Containerd来完成。Containerd收到容器发送的请求后,进行初始化然后启动Containerd-Shim进程,并传送相关的配置参数给它。Containerd负责宿主机上所有运行的容器,而相应的一个Containerd-Shim则只管理一个运行的容器,使整个容器管理过程更为稳定高效。

1.2 Docker容器原理

Docker的底层实现机制是通过Namespaces(命名空间)、Control Groups(控制组)等技术来实现操作系统级的虚拟化机制。同时Docker需要一种文件系统作为Docker镜像的存储方式。

OVERLAY2是所有Linux发行版的首选存储驱动程序。但以前的Ubuntu内核不支持OVERLAY2,因此使用AUFS作为存储驱动,而龙芯平台以前的Fedora21系统上是使用DEVICEMAPPER作为存储驱动的,性能差且稳定性不好,因此Fedora28上支持了OVERLAY2存储驱动。

OVERLAYFS其实是一种与AUFS类似的联合文件系统UFS[15](Union Filesystem),它将单个Linux主机上的两个目录(lowerdir和upperdir)合并成一个目录(merged)。这些目录称之为层(layer),而这个统一过程则被称为联合挂载(Union Mount)。其中,OVERLAYFS的底层目录称为Lowerdir,可以将其看作是只读的镜像层,用户无法直接进行操作。而高层目录称为Upperdir,可以看作是可读写的容器层,当需要修改一个文件时,需要将文件从只读的底层目录拷贝到可写的顶层目录中进行修改,同时将修改结果保存在顶层目录中。而合并统一视图则称为Merged,如图2所示。

图2 Docker构造映射到OVERLAYFS结构

当文件系统挂载后,用户就可以在Merge目录下看到来自不同底层目录中的内容,也无须感知这些文件来自具体的哪一个目录,并保证上层目录屏蔽下层目录的同名文件。

2 Docker方案移植与优化

由于MIPS体系结构与X86间的差异性,为了完成1.13版本Docker的移植过程,首先需要调整Docker的底层模块结构以解决架构差异性问题,主要移植过程和优化内容包括:

(1) 对Docker的一些底层模块添加MIPS架构入口。

(2) 针对Docker部分结构代码,添加MIPS下的特有参数以实现Docker方案对MIPS的支持。

(3) 根据Fedora28系统新的4.19.5版本内核变化,重构Docker关于信号机制部分的结构。

(4) 采用OVERLAY2存储驱动用以镜像存储。

然后设计评测方案对优化效果进行评测,硬件环境的龙芯计算机参数配置如表1所示。

表1 龙芯计算机参数表

2.1 Docker移植过程

不同架构平台,移植过程中会出现很多差异性问题,所以首先需要解决Docker方案的平台差异性问题,Docker底层的模块函数针对不同的架构需要不同参数以便识别。

2.1.1添加SETNS调用的识别

Docker方案中引用SETNS调用以允许进程更改命名空间,但是调用参数SYS_SETNS并没有MIPS架构的相关标识,因此针对SETNS调用部分的代码需要添加MIPS架构识别入口。

Docker利用命名空间机制(Namespace)将内核的全局资源封装,使各容器在各自命名空间中对同一种资源的应用不会互相干扰,其中的Netns被用于实现网络、计算等资源的隔离。

在Linux内核3.0版本之后引入了一个新的系统调用SETNS以更好地处理命名空间的相关操作。Linux为其句柄中的许多资源支持不同的命名空间,例如Docker方案向虚拟化进程展示了一个不同于真实PID的虚拟PID,还有文件系统目录结构、网络资源、IPC等也可以这样做。设置不同命名空间配置的唯一方法是在CLONE系统调用中使用不同的标识,但该系统调用无法执行允许一个进程访问另一个进程的命名空间之类的操作,而SETNS调用解决了这个问题。因此移植过程中需要在netns_linux.go文件中添加MIPS架构识别入口:

var SYS_SETNS=map[string]uintptr{

"386": 346,

"amd64": 308,

……

"mips": 5303,

}[runtime.GOARCH]

2.1.2添加BoltDB的MIPS参数

Bolt是一个基于Go语言编写的纯粹Key/Value模型的项目。Bolt项目的目标是为那些不需要完整数据库服务器的项目提供便捷、快速、可靠的数据库。由于BoltDB的设计是源自LMDB,因此主要具有可以直接使用API进行数据的存取的特点,没有像SQL一样的查询语句,而且没有线程压缩、垃圾回收和wal,而是直接将数据保存在内存映射的文件里。并且BoltDB支持完全可序列化的ACID事务,具有比LevelDB更强的特性,同时通过COW技术实现了无锁的读写并发,但不能实现无锁的写写并发,因此BoltDB更适合于读多写少的应用场景。

在Docker的底层结构里,BoltDB被应用于Containerd等组件的ID信息存取,由于BoltDB在Github上不再维护,因此目前Docker项目已经将BoltDB模块合并到了Libnetwork当中,但是由于BoltDB在Docker的底层实现结构里与许多组件耦合度很强故无法删除。Docker方案里BoltDB的函数文件需要为不同的架构平台单独定义两个变量maxMapSize和maxAllocSize,分别表示Bolt支持的最大MMAP大小和创建数组指针时使用的大小两个参数,1.6版本Docker方案添加了bolt_mips64le.go后设置的是:

const maxMapSize=0x3FFFFFFF

const maxAllocSize=0x7FFFFFFF

经过实际应用状况的反馈并结合新的Feodra28系统我们需要设置一个合理的参数:

const maxMapSize=0x8000000000

const maxAllocSize=0x7FFFFFFF

2.1.3设备号存储变量格式转换

由于X86和龙芯Fedora28系统中使用了不同的设备号存储变量格式,需要针对涉及相应变量引用部分的模块代码进行修改和重构。

Linux下的数据成员st_dev的值记录了文件主次设备号的信息,而一些字符特殊设备和块特殊设备还含有st_rdev的值,st_rdev包含了实际设备的设备号。主设备号可以区分拥有相同驱动程序的同一类设备,而次设备号则用于具体指向某一个设备以区分同一类型的不同设备。文件的主次设备号虽然是两个信息,但全部包含于st_dev的值里,而st_dev的基本数据类型是dev_t,内核用dev_t类型来保存设备编号。在不同的架构下其实际占用的位数可能不尽相同,如X86下的dev_t是64位,默认编码是MMMM Mmmm mmmM MMmm,8位表示主设备号,8位表示次设备号。而龙芯的Fedora28系统中使用了32位的dev_t,编码为mmmM MMmm,3位表示主设备号,5位表示次设备号。

由于Docker方案默认使用64位无符号整型格式的变量rdev读取设备号信息,所以为解决MIPS架构下的差异性,需要对rdev变量的格式进行转换,在stat_linux.go文件中修改rdev:

func fromStatT(s*syscall.Stat_t)(*StatT,error){

return &StatT{size:s.Size,

……

rdev:uint64(s.Rdev),

……

}}

2.2 Docker信号机制优化

每一个信号都有一个以SIG开始的独特名字,但MIPS架构下的Fedora28系统与X86架构下存在大量的差异,即使是信号名字相同的信号在不同操作系统中的编号ID也不一致,而且许多信号在新的Fedora28系统里已不存在。因此在移植过程中结合新系统的特性优化Docker方案对信号机制的设计,去除在MIPS下冗余的信号。

首先是协处理器堆栈错误信号SIGSTKFLT,这个信号在新的Fedora28系统被去除。协处理器是一种协助中央处理器完成其无法执行或执行效率低下的处理工作而应用的处理器,在MIPS体系结构里能够最多支持4个协处理器。在X86系统中有14个16位寄存器,这14个寄存器主要划分为四类:通用寄存器、指令指针寄存器、标志寄存器和段寄存器。而在MIPS体系结构[16]里,寄存器要比X86多,达到了35个,其中有32个通用寄存器,两个用于存储整数乘除和累加操作的特殊功能寄存器和一个由特定指令直接操作的特殊功能程序计数器PC(program counter),以及一个FPU寄存器。同时,在MIPS体系结构中协处理器CP0有32个寄存器,主要用于更好地控制CPU,实现MMU、乘除法或者异常处理等功能。

这个信号变量是一个在早期的Linux系统中存在的信号,用于数字协处理器的堆栈错误,在内核中并不会产生,但是为了系统的兼容性考虑在X86的系统中仍然被保留了下来。而在龙芯平台的Fedora28系统中,由于架构的差异并出于标准化和稳定性的考量便去除了那些没有价值的冗余信号。如今在MIPS中取代SIGSTKFLT信号作用的是SIGEMT信号。同时,由于平台的差异性,在MIPS平台下SIGUNUSED信号也变为了SIGSYS来表示系统的无效调用,Docker方案中使用SignalMap数组对应Linux信号的映射:

SignalMap=map[string]syscall.Signal

所以在移植过程中,根据不同平台信号机制的不同,我们需要修改signal_linux.go文件中的SignalMap数组,删除SIGSTKFLT和SIGUNUSED信号的映射"STKFLT"和"UNUSED",同时设置64位MIPS系统下的SIGRTMAX参数为127。

2.3 Docker存储驱动优化

Docker镜像是由一系列的层(Layer)组成的,同时支持多种Graphdriver,如AUFS、DEVICEMAPPER、OVERLAY、OVERLAY2,而1.6版本的Docker方案由于Fedora21系统内核原因,无法支持当时主流的AUFS系统,因此采用了DEVICEMAPPER用作镜像的存储模式,而Fedora28系统内核满足支持目前主流的OVERLAY2存储驱动的要求。

在经过详细的性能比较之后,确定OVERLAY和OVERLAY2两种驱动的性能表现都比传统的AUFS和DEVICEMAPPER强很多,因此Docker在移植方案中选择OVERLAY2作为镜像存储的首选。

尽管OVERLAYFS十分优秀但当前仍不够完美。由于OVERLAY是使用硬链接在层之间共享文件的,每一层都构建了一个完整的镜像而增加了磁盘的Inode负担,并且会由于系统对硬链接的限制在容器数量多至一定程度时出现问题,而增加文件系统可用Inode数量的唯一方法就是重新格式化。所以,OVERLAY2在镜像层之间共享数据的方法改成了通过每层的lower文件,每一层都是完全独立通过多层Lowerdir进行挂载。这种方法会消耗更少的磁盘Inode,但Inode的限制并未发生根本性改善,但是大量使用IF语句能够对OVERLAY2有一定程度优化。同时由于OVERLAYFS的兼容性限制,OVERLAYFS仅能实现POSIX标准的子集,所以,如COPY操作等将违反POSIX标准,目前的解决方法是迁移到Upperdir层进行操作。还有关于系统调用Rename的问题,因为OVERLAYFS不完全支持此调用的缘故,暂时无法使用。

3 Docker评测方案设计

移植过后的1.13版本Docker方案需要一套便捷全面的评测方法来验证新存储驱动和信号机制的优化效果。故设计了两个测试方案,设计方案一对比具体优化效果,设计方案二评测多容器数量下的稳定性。测试方案一,结合Unixbench工具在龙芯平台上制作相关测试镜像,然后分别对优化前后的Docker方案进行性能测试以对比优化的效果;测试方案二,利用方案一中的测试镜像,测试容器数量对系统性能的影响。

3.1 Docker移植优化效果评测

首先对Docker方案的移植优化效果进行评测,因此设计测试方案一如下。

先分别在搭载Fedora21系统的龙芯单路主机上制作1.6版本Docker的测试镜像和搭载Fedora28系统的龙芯单路主机上制作优化后的Docker方案的测试镜像,镜像中嵌入Unixbench测试工具,并设置好相关参数以便测试启动。然后分别在搭载Fedora21系统和Fedora28系统的龙芯单路主机上利用测试工具进行基准性能测试,确认系统环境的状态,各记为F21′和F28′;之后分别在Fedora21系统和Fedora28系统中启动测试镜像进行性能评测,测试结果分数分别记为F21和F28。为了减少误差影响,两个Docker方案的测试至少重复执行三次,性能数据结果取平均值为最终值,即每个测试项最终的测试数据结果Datai计算如下:

(1)

式中:Datai表示第i项性能测试数据的分数;xij表示第i项性能测试项第j次的数据结果。

最终测试数据如表2所示。

表2 Docker方案优化前后的性能测试数据表

续表2

然后为了验证优化后的Docker方案对系统性能的影响,需要在不同容器的数量下进行测试。测试方案二如下。

记(Containers Numbers)容器编号为N时表示同时运行的N个测试容器进行测试,对每次测试至少重复执行三次以减少误差带来的影响,每项测试结果取所有数据结果的平均值为最终值,即每次最终的测试分数DataN计算如下:

(2)

式中:uij表示在N个容器同时运行情况下第j个容器第i次的测试数据。

最终测试数据如表3所示。

表3 不同容器数量下的性能测试数据表

3.2 性能测试数据分析

首先结合图3(a),能清晰地看见优化后的Docker方案整体性能分数F28是960.3,而优化前的Docker方案性能分数F21是920.3,综合性能提高了5%左右。而且各个测试项数据来看,容器内的系统性能与宿主机操作系统不相上下,较传统的虚拟化技术而言有明显的优势。

(a) 测试方案总分

(b) 进程通信相关测试分数1

(c) 进程通信相关测试分数2图3 测试方案总分和进程通信相关测试分数

值得注意的是Pipe-based Context Switching(基于管道的上下文交互)测试,这个测试项测试了两个进程每秒钟通过一个管道交换一个不断增长的整数的次数。从图3(b)可以看到1.6版本Docker方案中容器内测试分数是374.4,而主机下的基准测试值是479.2,容器内测试程序运行效率约是主机环境下的78.13%,而优化后的Docker方案中,容器内测试分数为703.4,而主机环境下测试项分数是778.2,容器内测试程序运行效率约是主机环境下的90.39%,证明了优化后的Docker方案在进程间通信中的性能损失更少,同时可以看出优化后的Docker方案较之前的1.6版本性能提升较为明显,在进程间通信测试项上性能比1.6版本提升了53.0%。

再根据System Call Overhead测试项分数变化,如图3(c)所示。这一项测试通过反复调用getpid函数测试了进入和离开系统内核的代价,即一次系统调用的代价,测试分数越高说明系统调用消耗越小。可以看出来优化后的Docker方案较1.6版本Docker在系统调用测试项上性能更为优越。1.6版本Docker方案系统调用代价测试项的分数F21是1 073.2,而优化后的Docker方案测试项分数是1 201.5,优化后的Docker方案系统调用消耗测试项是1.6版本的89.32%,在系统调用的效率上提升了10%左右。

Dhrystone和Whetstone两项测试是测试处理器运算能力的基准测试程序,可以看到优化后的Docker方案依然保证了容器内优秀的计算性能,CPU计算性能并没有因为容器虚拟化方案而产生损耗。而且容器中的测试分数抖动更小,各项测试结果都比主机环境下更为稳定,由此表明容器的系统运行环境对于测试程序来说稳定性更高。

从表3可以发现,在同时运行N个容器的系统环境下,CPU计算性能测试项分数是线性下降而非曲线下降的,在同时运行四个测试容器时测试项分数是753.5,是单容器时测试分数的25.4%,由于单路主机是四核机器,所以这是符合预期的,说明优化后的Docker方案稳定可靠,不会出现由于资源抢占产生的额外性能损失。结合Whetstone测试分数的抖动频率,更能验证移植优化后的Docker方案不会对CPU计算性能产生太大影响。

4 结 语

本文完成了龙芯Fedora28系统上1.13版本Docker方案的移植,解决了一些平台差异性问题,并优化了Docker方案映射的系统信号机制,对于镜像文件应用了新的OVERLAY2存储驱动,并设计了评测方案分析移植优化的效果。经过评测发现优化后的Docker方案在进程间通信的相关测试项中性能比1.6版本提升了53%,并且容器内测试程序的性能损耗更小,同时发现优化后的Docker方案系统调用消耗更小,降低了10%左右,而且优化后的Docker更加稳定。但是容器技术深深依赖内核特性的特点并未根本性改善,可以预测在高数量级下容器集群系统性能会产生很大损失,仅依赖内核特性进行资源管理是远远不够的,所以下一步的研究将集中在MIPS架构下的容器集群模式构建和相关负载均衡策略与调度算法的设计上,并结合龙芯平台的特殊性进一步研究MIPS架构下Docker方案的各个模块的优化策略。

猜你喜欢

龙芯容器架构
基于国产化龙芯的动环数据采集系统
基于云控平台雾计算架构的网联汽车路径控制
难以置信的事情
构建富有活力和效率的社会治理架构
液体对容器底及容器对桌面的压力和压强
取米
VIE:从何而来,去向何方
企业架构的最佳实践
“龙芯1号”:电脑中国“芯”
“龙芯1号”:电脑中国“芯”