安全容器文件服务 Virtio-fs 的高可用实践

原创
2022/11/29 11:55
阅读数 655

Kata Containers 等基于虚拟机的安全容器得到了越来越广泛的应用。由于容器存储对共享目录功能的依赖,物理机与虚拟机之间的文件目录技术也成为一种广泛的需求。Virtio-fs 是一种比 Virtio-9P 更先进的目录共享技术,它对本地文件系统 POSIX 语义兼容性、读写性能也更好。本文将重述字节跳动STE团队在 KVM Forum 2021 会议中的一场主题演讲,介绍我们如何为 Virtio-fs 实现崩溃重连机制来弥补 Virtio-fs 导致的安全容器高可用性短板。

关注【字节跳动SYS Tech】公众号,后台回复“Virtio-fs”获取 PPT。

1. Virtio-fs 背景介绍

Virtio-fs 是 Red Hat 在 2018 年提出的一套用于将宿主机上的文件系统目录共享给虚拟机的方案。Virtio-fs 的设计初衷是为了解决 Kata Containers 等安全容器在容器存储领域遇到的问题:Kata Containers 本质是在一个轻量级虚拟机里面运行的 runc 容器,它利用虚拟机的强隔离性构建了安全的容器运行时。但这种虚拟机里面运行容器的做法,直接导致虚拟机里面的容器无法像通常那样直接挂载主机上的某个目录(如:容器 rootfs、数据 volume 等)来使用。因此,需要有一种方法能够把宿主机上的目录共享给虚拟机。 在 Virtio-fs 之前,业界曾尝试采用基于 9p 协议的 Virtio-9P 方案来实现宿主机-虚拟机的目录共享。但 Virtio-9P 这个方案存在几个比较大的问题:

  • 社区活跃度不高,性能问题突出;

  • 基于网络文件系统定制的协议,较差的 POSIX 语义兼容性。

图1. 安全容器场景中的Virtio-fs服务

由于 FUSE 协议是为本地实现用户态文件系统而设计的,Red Hat 基于 FUSE 协议和 Virtio 半虚拟化设备协议重新设计了虚拟机目录共享方案,即 Virtio-fs。相对 Virtio-9P 和 NFS,Virtio-fs 对 POSIX 语义支持及性能都更好。如图1,在安全容器场景,容器运行于虚拟机内,通过 Virtio-fs 提供的虚拟机文件系统目录透传技术,虚拟机中的安全容器也可以直接访问宿主机中各种创建容器所需的文件,避免了文件拷贝。

1.1 Virtio-fs 架构

如图2,Virtio-fs 方案主要基于 FUSE 协议来实现对远端文件系统的调用。通过在内核 FUSE 模块中增加 Virtio 传输层使得虚拟机内核产生的 FUSE 请求能够被宿主机上的 Virtiofs-Daemon(virtiofsd)进程接收和响应。宿主机上的 virtiofsd 进程会解析这些来自虚拟机的 FUSE 请求并在宿主机上的文件系统进行相应操作。这样,从虚拟机的角度看到的就是通过 FUSE 挂载了一个宿主机上的目录,并可以进行相应操作。

图2. Virtio-fs整体架构

1.2 Virtio-fs 高可用特性

对于 Virtio-fs 来说,高可用特性包括崩溃恢复、热升级、热迁移等保证其自身及其所服务虚拟机持续服务的特性。但当前,开源版本的 Virtio-fs 不支持任何高可用特性。由于 virtiofsd 是一个独立于 VMM 进程(如 QEMU、Cloud Hypervisor)的后台进程,当 virtiofsd 进程崩溃时,虚拟机中的应用程序操作 Virtio-fs 所透传的目录时就会发生文件 I/O 阻塞,使得整个虚拟机变得不可用。为了解决这一问题,我们实现了 Virtio-fs 的崩溃恢复,同时,基于对崩溃恢复的实践,我们也正在对 Virtio-fs 的热升级、动态迁移等特性进行积极探索。

2. Virtio-fs 崩溃恢复

2.1 崩溃模型

崩溃是大型系统中几乎不可避免的问题,谈到崩溃恢复(crash recovery),我们首先需要明确我们要支持的崩溃模型。对于服务于虚拟机和安全容器的 virtiofsd,由于它实际上是一个运行于宿主机用户态的进程,因此在运行过程中,可能由于以下三种原因导致崩溃:

  • 被同宿主机的其他进程意外终止;

  • 由于宿主机内存用量紧张,被内核 OOM(out-of-memory)Killer 所终止;

  • 由于 virtiofsd 本身的程序 bug,被 SIGSEGV 等错误信号所终止。

上述三种原因所导致的崩溃都属于“失败-停止”错误模型(fail-stop),即服务进程内部或外部发生错误时,会令程序马上停止运行并退出,而非再以错误状态运行一段时间之后再退出。不同于另一类拜占庭错误模型(Byzantine failure),fail-stop 错误会让系统马上停止运行,而不会有后续对系统内部状态持续破坏的行为发生。包括 Virtio-fs 在内的大多数系统都符合 fail-stop 模型,这就意味着,在 virtiofsd 崩溃恢复之后,我们只要将崩溃时刻的系统状态恢复到重新启动的 virtiofs 进程,就可以继续正常的服务。

2.2 状态的保存

Virtio-fs 并非无状态服务,因此,要让重启的进程可以恢复到崩溃时刻的状态,我们需要在 virtiofsd 正常运行过程中就对其内部状态进行实时保存。

状态1:重新处理未完成的请求

Virtiofsd 崩溃后,虚拟机内应用之所以会发生 I/O 阻塞,是因为虚拟机 Virtio-fs 内核模块再也无法等到宿主机 virtiofsd 进程的 FUSE 请求完成回复,导致虚拟机内应用程序阻塞于VFS相关系统调用的内核态无法返回。因此,在 virtiofsd 重新连接时,一定要恢复 virtiofsd 崩溃时还未处理完成的(inflight)请求,重新执行这些请求,并将执行结果返回给等待中的虚拟机 virtiofs 内核模块。

由于每个FUSE请求对应一个 virtio 请求,所以我们需要记录和恢复的也是未完成的virtio请求。因此,我们基于 vhost-user 协议现有的 Virtqueue Inflight I/O Tracking 特性,实现了FUSE请求的保存和恢复。Inflight I/O Tracking 特性的本质是在 virtiofsd 与虚拟机管理器 QEMU 之间共享一个内存日志区域,这一日志区域被作为 inflight 请求日志记录区域,在开始请求的时候将请求记录到 inflight 日志中,在请求完成时将请求从 inflight 日志中清除,在重启的 virtiofsd 进程恢复过程中,inflight 日志区域所记录的就是未完成的请求,这批请求会首先被重新执行。

状态2:Guest inode 和 host 文件的映射关系

图3的例子展示了一个虚拟机内应用程序希望 open 和 read 共享目录中文件"/A/B/C"时,所涉及的 Virtio-fs 请求流程,虚拟机内应用到虚拟机内核的2次文件系统调用,会产生最少4次virtiofsd内需要处理的FUSE请求。其中,用户程序的 open 请求会通过多次的 fuse lookup 请求最终得到 inode_uC;随后的用户 read 请求,虚拟机 Virtio-fs 内核模块则依赖之前open请求得到的 inode_uC 继续对 virtiofsd 发起 fuse read 请求。对于这一简单的例子,一共存在两层映射关系,分别是 fd_k 和 inode_u 之间的映射和 inode_u 和 fd_u 的映射,前者由虚拟机内核处理,后者则必须由 virtiofsd 处理。

需要注意的是,虚拟 inode(inode_u)及其宿主机文件(fd_u)之间的映射关系是在 virtiofsd 运行过程中产生的,即映射是一种随着运行动态增减变化的,而非预先定义好的 virtiofsd 启动初始状态。因此,这种映射关系就是我们需要进行保存的 virtiofsd 内部状态的主要部分。

图3. 虚拟机内应用通过Virtio-fs找到并打开共享路径/A/B/C的例子

具体实现时,我们基于一个平坦的映射结构(下文称为 flat-map)来存储 virtiofsd 的内部映射状态。如下图4,flat-map 基于 virtiofsd 内部原有的数据结构改进而来,最大程度地减少对指针的依赖,因此可以方便地基于共享映射文件线性扩展及保存,不需要序列化和反序列化的流程。

图4. 将virtiofsd中状态保存的内存布局结构平坦化

状态3:文件描述符

Virtiofsd 还保持了一些文件的打开状态。因此,在新的 virtiofsd 恢复时,我们需要恢复这些打开着的文件描述符(fd)。我们基于宿主机文件系统所提供的的 file handle 特性来实现fd的保存和恢复:将运行过程中产生的 fd 通过 name_to_handle_at(2) 转换为一个全局唯一的 file handle 保存在 flat-map 中,在崩溃恢复时,通过 open_by_handle_at(2) 从 fd 从 file handle 中重新打开。

2.3 崩溃恢复流程

前两节说明了崩溃模型及 Virtio-fs 状态保存原理之后,让我们再看一下崩溃恢复的总体方案和具体流程。从图5可以看到,除了支撑虚拟机(VM)运行的 QEMU 进程和服务虚拟机文件目录共享的 virtiofsd 进程,我们还需要一个监督进程(Virtiofsd Supervisor)来监视 virtiofsd 的存活状态,持久化状态存储模块(Perisistent Store)包括了保存映射关系的 flat-map 和持久化 fd 的 file handle。由于持久化状态存储的生命周期仅依赖于宿主机内核,因此可以用于新启动进程的状态恢复。

图5. Virtiofsd崩溃恢复整体流程

对应图5的顺序号,当一个崩溃发生时,Virtio-fs服务将以如下流程恢复到正常服务状态:

① Virtiofsd 监督进程检测到virtiofsd崩溃并重启一个新的virtiofsd进程;

② 新的 virtiofsd 重启后马上从持久化状态存储中恢复其内部状态数据,然后监听 vhost-user socket。

③ QEMU重新连接到新 virtiofsd 的 vhost-user socket 连接,并按 vhost-user 协议重新协商虚拟机内存布局、virtqueue 地址等配置,然后发送 inflight 日志到新的virtiofsd;

④ 新的 virtiofsd 重新处理来自 inflight 日志所记录的未完成请求;

⑤ 最后,新的 virtiofsd 开始继续处理来自虚拟机内应用程序的正常文件请求,崩溃恢复完成。

2.4 请求幂等和状态一致性

不同于 vhost-user-blk 等无状态虚拟化设备服务,Virtio-fs由于存在大量内部状态更新,重新处理未完成的请求就会存在请求幂等(idempotent)和状态一致性(consistency)等问题。这是因为未完成请求在崩溃时可能已经改变了某些Virtio-fs内部状态,恢复后重新执行就可能会导致状态的重复更改,导致返回结果异常(非幂等)甚至内部状态破坏(不一致)。 下图中,当一个请求处理函数执行过程中crash时,operation_1 和 operation_2 可能已经导致一些系统内部状态的变化;服务恢复时,这个请求将再次从 operation_1 被重新执行,所以我们需要确保重新执行的 operation_1 和 operation_2 不会破坏系统状态的一致性并保证请求处理结果的幂等。

为了保证重发请求的幂等,我们逐一分析了每种 FUSE 请求所需的特殊处理。所需的特殊处理大概分为几类,如表1。首先,read / write / sync 等大部分请求类型本来就满足幂等性,无需特殊处理的;一些请求类型需要放松开源实现中的错误处理条件来达到幂等要求;而有少数请求则较为复杂,需要引入轻量的日志回滚机制。对于 flat-map,我们也考虑和保证了其更新和扩展操作中的崩溃一致性问题。

表1. 保证重发FUSE请求 幂等 性的几种方式

关于保证幂等和一致性的具体说明和例子本文将不再展开,欢迎大家持续关注 QEMU 及 Virtio-fs 社区邮件列表,我们将会把 Virtio-fs 相关新特性逐步推向开源社区。

2.5 停机时间优化

恢复过程中,Virtio-fs 会停止服务一小段时间,这个时间我们称为停机时间(downtime),为了优化崩溃恢复的停机时间, 我们也做了一些优化。例如,上游版本QEMU在尝试重连 vhost-user 服务 socket 时,重试间隔最少为1秒, 我们进行更改支持了毫秒级重连尝试(优化1);另外,我们以按需恢复的形式从 file handle 恢复 fd,这将恢复时的停机时间平摊到了后续运行中的第一次文件访问(优化2)。

表2. 崩溃恢复停机时间优化测试

表2是我们利用了 fio 工具测试了 virtiofs 在没有优化、仅有优化1和优化 1 加优化 2 的停机时间。可以看到,如果没有两个优化,停机时间至少为 1 秒。 优化 1 将停机时间降到了 1 秒以内,优化 2 进一步将停机时间减少到了 100 毫秒以内。

3.热升级与热迁移

热升级

实际上,基于上述 Virtio-fs 的崩溃恢复特性,只需要将旧进程杀掉然后重启升级后的进程,就实现了一种形式的热升级。但另外实现无需状态恢复过程的热升级可以显著减少热升级的停机时间。而且,热升级的专门实现也无需考虑崩溃恢复中请求重新处理带来的幂等、一致性等问题,使得问题变得更纯粹直接。

为了实现这种更高效的 Virtio-fs 热升级,我们在 virtiofsd 进程及其监督进程之间建立起了一个控制信道用来发送实时升级的命令,形式类似 QEMU 的 qmp 功能。同时,在内部状态方面,与崩溃恢复相比,新 virtiofsd 进程的状态会尽可能多地继承于旧 virtiofsd 进程;另外,vhost-user 的重新协商流程也因为新 virtiofsd 无需与 QEMU 重新建立 vhost-user socket 连接而被避免。这些差异都能显著降低热升级的停机时间。

热迁移

虚拟机热迁移对于大规模虚拟机的管理来说是一个重要特性,但要想让使用 Virtio-fs 的虚拟机支持热迁移,Virtio-fs 本身必须支持热迁移。由于开源版本的 Virtio-fs 并不支持热迁移,我们也对基于共享文件后端的 virtiofsd 之间的热迁移进行了探索。具体实现中,除了目的端的 virtiofsd 需要做类似于崩溃恢复特性中的请求重做、状态恢复外,虚拟机管理器 QEMU 也需要支持 Virtio-fs 虚拟设备 vhost-user-fs 的设备状态迁移。

4.展望

目前,崩溃恢复特性已经以 RFC patchset 的形式发送到 QEMU 和 Virtio-fs 开源社区上游,我们已经从社区得到了很多宝贵的反馈。我们也在 KVM Forum 2021 会议中对本文所述方案进行过详细阐述。未来,我们还将在虚拟化文件系统 Virtio-fs 和用户态文件系统 FUSE 相关技术领域进行更深入的研究与探索。


引用链接:

[1] Virtio-fs, https://virtio-fs.gitlab.io/

[2] Kata Containers, https://katacontainers.io/

[3] 9P (protocol), https://en.wikipedia.org/wiki/9P_(protocol)

[4] KVM Forum 2008 - Paravirtualized File Systems, https://www.linux-kvm.org/images/4/41/KvmForum2008%24kdf2008_16.pdf

[5] KVM Forum 2019 - virtio-fs: A Shared File System for Virtual Machines - Stefan Hajnoczi, Red Hat, https://sched.co/TmvA

[6] Fred B. Schneider. 1990. Implementing fault-tolerant services using the state machine approach: a tutorial. ACM Comput. Surv. 22, 4 (Dec. 1990), 299–319.

[7] Vhost-user Protocol - Inflight I/O tracking, https://qemu-project.gitlab.io/qemu/interop/vhost-user.html#inflight-i-o-tracking

[8] Thanumalayan Sankaranarayana Pillai, Vijay Chidambaram, Ramnatthan Alagappan, Samer Al-Kiswany, Andrea C. Arpaci-Dusseau, and Remzi H. Arpaci-Dusseau. 2015. Crash Consistency: Rethinking the Fundamental Abstractions of the File System. Queue 13, 7 (July 2015), 20–28.

[9] The virtio-fs Mailing List Archives, https://listman.redhat.com/archives/virtio-fs/

[10] Support for Virtio-fs daemon crash reconnection, https://patchwork.kernel.org/project/qemu-devel/cover/20201215162119.27360-1-zhangjiachen.jaycee@bytedance.com/

[11] KVM Forum 2021 - Towards High-availability for Virtio-fs - Jiachen Zhang & Yongji Xie, ByteDance, https://sched.co/ke3s

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部