如何提高容器的性能

冰岩作坊 May 4, 2024

引言

大家好,我是来自程序组的南笙。今天给大家带来一篇关于容器与虚拟化的论文的导读,论文的链接地址是Container-aware I/O Stack: Bridging the Gap between Container Storage Drivers and Solid State Devices。

我们都知道在日常的Web应用中,服务是运行在后端的容器中,但是容器启动和运行的开销是较大的 ,因此论文的作者针对以SSD作为存储媒介的服务器,对容器的运行与操作做出一些优化,以期待更好的性能表现。现在让我们一起走入这篇论文,看看作者对容器做出了哪些“神秘的”优化。

容器不能充分利用 Solid State Devices 的两个原因

“Unfortunately, the performance benefit of SSDs is still not fully exploited when applications run in containerized environments. On the one hand, the additional tier introduced by image management (i.e. , OverlayFS, AUFS, Device Mapper, and BtrFS) results in an extra latency due to the file redirect. On the other hand, operations from different containers show intense competition for shared resources because the resource allocation of the native file system is centralized and serialized.”

在上述的论文节选中,作者提出造成容器性能瓶颈的两个原因:1、镜像管理引入的附加层在文件重定向时导致额外的延迟;2、现有的集中式和序列化的文件系统下,不同容器对共享资源的竞争。

针对这些问题,作者提出了CAST来优化容器的操作与运行。下面将由我带大家看看作者是怎么解决这些问题得到优化的。

Overview of CAST

• Reduce search overhead: While opening a file in a container, OverlayFS must redirect the view file to its real file which exists in upper or lower, thus consuming extra dentry search compared to the native file system. CAST aims to reduce search overhead by directly locating the real file based on layer information.

• Avoid Copy-on-Write wait: Modifications in lowersare redirected to upper by CoW operations which are serial due to the global lock. CAST tries to eliminate the impact of the global lock by enabling simultaneous CoWs.

• Prevent block group sharing: Centralized resource allocation of the native file system makes data from different containers share BGs. To reduce resource competition which are caused by BG sharing, CAST groups BGs based on containers.

• Reduce journal contention: Shared journal service makes updates from different containers need to compete for one running transaction. In order to reduce journal competition among containers, CAST provides journal service to each container individually, based on its needs.

• Identify layers and containers: Original VFS and the native file system cannot distinguish layers and containers. CAST maintains this information to enable container-aware scheduling.

上述论文内容介绍了 CAST 的设计目标:减少搜索开销、避免写时复制的等待、放置存储块组的共享、减少日志竞争、对镜像层和容器的感知。通过这样的设计,我们期望容器的操作在 SSDs 作为存储媒介时有更高效的性能。下面是论文中对于 CAST 工作方式的介绍。

不难从图中看出 LaVFS对于操作类型做出了分类,分别交由自己和 CaFS 完成。下面将对这两个模块分别展开详细的描述,并在最后的部分,介绍如何对操作做分类。

Layer-aware VFS (LaVFS)

1、Layer-Aware Path Switch

在论文的 “Section 3.2.1 Layer-Aware Path Switch” 中,作者详细介绍了Layer-aware VFS (LaVFS) 的一个核心特性——层级感知路径切换。这个特性旨在解决传统VFS在容器化环境中处理文件操作时的性能问题,特别是在涉及多层文件系统结构时的效率问题。

问题:

• 在容器化环境中,文件操作需要在多个层级之间进行,例如在Docker镜像中,文件数据分布在多个只读层和一个可写层(上层)之间。

• 传统的VFS在处理容器文件操作时,需要搜索所有层级以定位文件,这导致了额外的开销和性能延迟。

LaVFS的解决方案:

• LaVFS通过引入层级感知路径切换来优化文件操作。它能够根据文件所在的层级信息直接定位到文件的实际路径,从而避免了在所有层级中搜索文件的需要。

• 这种方法显著减少了打开文件时的dentry(目录项)搜索次数,因为它直接跳过了不存在的层级,只搜索那些实际包含所需文件的层级。

实现细节:

LaVFS首先从OverlayFS获取容器的层级结构信息,包括各层的dentries(目录项)和层级顺序。当需要打开一个文件时,LaVFS会根据文件的实际位置,将视图路径(例如,合并层路径)切换到对应的实际层级路径。例如,如果文件位于容器的上层,LaVFS将直接在上层打开文件,而不需要搜索下层,这样就减少了搜索次数和延迟。为了支持这种路径切换,LaVFS和OverlayFS需要协同工作。OverlayFS负责构建和填充视图dentries,而LaVFS负责根据层级信息执行路径切换。

2、Simultaneous Copy-on-Write Operations

在论文的 “Section 3.2.2 Simultaneous Copy-on-Write Operations” 中,作者详细介绍了Layer-aware VFS (LaVFS) 的另一个关键特性——同时的Copy-on-Write(CoW)操作。这个特性旨在解决容器化环境中由于CoW机制导致的性能瓶颈问题。

问题:

在容器化环境中,写操作通常通过CoW机制来实现,这涉及到将数据从只读层复制到可写层(上层),然后对复制的数据进行修改。传统的CoW操作基于重命名操作,这在Linux文件系统中通常需要获取文件系统范围的锁,以避免死锁的发生。当多个容器并发执行CoW操作时,由于都需要获取全局锁,这会导致严重的锁竞争和性能下降。

LaVFS的解决方案:

• LaVFS通过引入无锁的重命名操作来允许多个容器同时执行CoW操作,从而避免了全局锁竞争。

• 由于容器有自己的工作目录(workdir)和上层,CoW操作的重命名在容器之间是相互独立的,不会导致死锁。

• LaVFS实现了一种机制,使得CoW操作不需要获取文件系统全局锁,而是仅在需要时获取目录的i_mutex(inode mutex)。

实现细节:

LaVFS中的CoW操作与原始的重命名操作类似,但不尝试在获取目录i_mutex之前获取文件系统全局锁。这种设计不会修改原始的锁定机制,因此不会影响需要重命名操作的其他文件系统操作。通过这种方式,LaVFS能够在不同容器之间并行执行CoW操作,提高了写操作的效率。

Container-aware native File System (CaFS)

1、Container-Aware Micro Journals

在论文的 “Section 3.3.1 Container-Aware Micro Journals” 中,作者提出了一种新的容器感知微日志系统,以解决在容器化环境中由于共享日志服务导致的性能瓶颈问题。

问题:

• 在传统的文件系统中,日志服务(如JBD2)通常是一个集中式的服务,用于记录文件系统更新操作,以保证数据的一致性。

• 当多个容器并发执行文件操作时,它们需要共享日志服务来记录事务,这会导致日志资源竞争,从而影响性能。

CaFS的解决方案:

• 为了减少日志服务的竞争,CaFS实现了容器感知微日志系统,将传统的集中式日志服务分解为多个微日志,每个微日志可以独立地绑定到特定的容器并处理事务。

• 这种设计允许不同容器的更新操作被独立地记录和提交,从而减少了容器间的竞争和等待时间。

实现细节:

• 微日志系统通过在内存中构建新的数据结构 m_journal_t 来管理更新,它具有自己的事务列表、锁和磁盘区域。

• 磁盘上的日志区域被划分为多个子区域,每个微日志使用一个子区域来存储其更新。

• CaFS通过哈希表 mjournal_table 来组织微日志,使得系统能够通过 journal_t 访问到 mjournal_t。

• 微日志的数量可以根据系统的需求动态调整,提供了灵活性和可扩展性。

性能提升:

• 通过容器感知微日志系统,CaFS能够为每个容器提供独立的日志服务,减少了日志资源的竞争。

• 实验结果表明,使用微日志系统可以显著提高文件系统操作的性能,尤其是在高并发的写入操作场景中。

一致性保证:

• 微日志系统通过确保同一容器的更新操作被同一个微日志收集,来保证数据的一致性。

• 即使在系统崩溃的情况下,微日志也能够独立地恢复各自的数据,从而保证了整个文件系统的一致性。

2、Container-Aware Block Allocation

在论文的 “Section 3.3.2 Container-Aware Block Allocation” 中,作者详细介绍了CaFS(Container-aware Native File System)的另一个核心特性——容器感知块分配。这个特性旨在解决容器环境中由于共享块组(Block Groups, BGs)导致的资源竞争问题。

问题:

• 在传统的文件系统中,如EXT4,存储空间被分割成多个块组,每个块组包含inode位图、inode表和数据块。不同容器的操作可能会在同一个块组内进行,导致容器之间在进行数据修改时发生资源竞争。

• 由于块分配是串行化的,这导致了在高并发场景下,容器性能受到严重影响。

CaFS的解决方案:

• CaFS通过容器感知块分配机制,为每个容器分配独立的块组集合,称为容器区域(Container Regions, CRs),从而减少了容器之间的资源竞争。

• 每个CR由多个块组组成,并且CaFS为每个容器独立地在分配给它的CR内执行块分配,确保不同容器的块分配操作不会相互干扰。

实现细节:

• CaFS设计了一个两级映射表来维护容器、CRs和BGs之间的关系。第一级是区域哈希表,记录容器和CRs之间的映射关系。当创建容器时,容器会被绑定到一个CR。

• 第二级是容器哈希表,记录BGs和容器之间的映射关系。CaFS在CR内为容器选择合适的BG,并在这个BG上为容器分配块。

• CaFS还维护了每个CR自己的孤儿列表(orphan list),以减少锁竞争。

性能提升:

• 容器感知块分配通过减少块组共享,显著降低了容器间的资源竞争,提高了文件系统操作的性能。

• 实验结果表明,CaFS在处理文件系统操作,如删除(unlink)和同步(fsync)时,相比于原始I/O堆栈,能够实现显著的性能提升。

扩展性和动态调整:

• CaFS允许动态地向CR添加新的BG,以适应容器的存储需求变化。

• 当CR中的已使用BG数量达到一定比例时,CaFS会自动扩展CR,分配新的BGs。

Identification of Containers and Layers

在论文的 “Section 3.4 Identification of Containers and Layers” 中,作者讨论了如何在CAST系统中识别容器和层级,这对于实现容器感知的I/O堆栈至关重要。

目的和重要性:

容器和层级的识别对于LaVFS和CaFS来说是基础性的。LaVFS需要知道文件操作请求来自哪个容器和层级,以便执行基于层级的路径切换和无锁重命名操作。

CaFS需要识别操作的来源(容器或主机),以便在原生文件系统中进行细粒度的资源分配。

实现方法:

为了识别容器和层级,CAST引入了一个新的标签 container_id,将其添加到OverlayFS、CaFS和进程中。

container_id 是基于SHA256算法从层级信息生成的,确保了每个容器的唯一性。

关键步骤和组件:

OverlayFS中的 container_id:在OverlayFS的超级块信息(superblock_info)中添加了 container_id 来标记OverlayFS属于哪个容器。

当OverlayFS被挂载时,它会被绑定到一个容器,并且设置相应的 container_id。

CaFS中的进程管理:在CaFS中,操作是由进程执行的,因此将 container_id 添加到 task_struct 中,以识别进程属于哪个容器。当进程向LaVFS发起操作时,LaVFS将 container_id 从超级块信息复制到 task_struct 中,从而将进程和容器绑定。

CaFS中的块分配:CaFS在两级映射表中记录CR和 container_id 之间的关系,以便在块分配时能够识别和处理来自特定容器的请求。

LaVFS中的层级结构信息:LaVFS需要获取容器的层级结构信息,以便进行基于层级的路径切换和无锁重命名。实现了相关函数,以便为LaVFS提供层级的dentry和层级顺序信息。

操作请求的处理:当操作请求到达LaVFS时,LaVFS会检查 task_struct 中的 container_id。

如果操作来自容器,LaVFS会进一步检查操作属于哪个层级,并基于层级顺序执行相应的操作。

参考文献:

1、Song Wu, Zhuo Huang, Pengfei Chen, Hao Fan, Shadi Ibrahim, Hai Jin, “Container-aware I/O Stack: Bridging the Gap between Container Storage Drivers and Solid State Devices”, in Proceedings of the 18th ACM SIGPLAN/SIGOPS International Conference on Virtual Execution Environments (VEE 2022), March, 1, 2022, Virtual Event, Switzerland, pp.18-30