资讯动态

Kata容器存储选型:virtiofs与9p共享存储性能实测与踩坑记录

发布时间:2026/10/21 14:20:11 来源:尧图企业网站定制
拿到kata container virtiofs这个测试任务时我的第一反应是终于不用再跟9p那套共享存储死磕了。之前线上跑Kata容器凡是涉及挂载宿主机目录的场景基本都踩过9p的坑几百个文件的编译任务能卡到怀疑人生Java进程在共享盘上起不来mmap相关调用要么报错要么性能惨不忍睹。这次virtiofs从原理到压测完整跑了一遍中间还撞了几个比较隐蔽的稳定性问题整理成这篇技术分析给正在做Kata容器存储选型的同学一个参考。这篇文章不是简单夸virtiofs有多快而是把测试方案、结果数据、原理拆解和踩坑记录都摊开讲。适合正好在调Kata运行时、计划把hostPath迁移到virtiofs、或者在考虑容器和虚拟机混合存储方案的工程师。1. 为什么Kata Containers的共享存储会选virtiofs从9p的痛点说起1.1 Kata的存储路径到底特殊在哪Kata Containers的本质是用轻量虚拟机隔离容器每个Pod其实是一个小虚拟机有自己的内核。这个设计带来一个很直接的问题宿主机目录要怎么让虚拟机里的进程看到。普通容器比如纯runc靠Linux内核共享文件系统层namespace一切目录直接挂进来性能接近本地盘。Kata不行它和宿主机之间的边界是虚拟化层文件系统必须穿过这条边界。早期方案是9p九号项目文件协议在QEMU里做了个fsdev后端Kata这边作为客户端挂载。听起来不难对吧但实际用起来凡是正经业务负载9p几乎都能把性能打崩。我把线上几个真实案例搬过来测试问题立刻暴露。1.2 9p的三大痛点mmap、协议栈、元数据先说mmap。Java服务、Node.js、Nginx这类进程对mmap依赖很重比如JVM加载jar包、共享内存映射。9p的mmap实现没有真正的内存映射底层是靠read/write系统调用模拟的也就是说每次page fault都要向host发起一轮完整请求。线上Java应用在9p共享盘上偶发卡顿压测时直接出现长时间停顿这本身就是不可接受的。再就是协议开销。9p是传统的request/response模型每个操作在guest内核、QEMU进程、host VFS之间来回穿梭。单次操作的延迟比本地文件系统高一个量级一旦并发量上来guest侧的内核线程和QEMU进程的CPU就会被协议处理占满吞吐上不去。最致命的是元数据操作。创建、删除、stat、readdir这些小文件场景9p基本暴露全部缺陷。我这边跑过一个简单的编译模拟测试创建一万个小文件再逐个stat9p用了将近三分钟本地盘不到两秒。这种差距已经不属于调优能解决的范畴是协议和应用场景不匹配。1.3 virtiofs的方向转变从通用协议到专用虚拟设备virtiofs和9p最大的区别在于它不再把自己当成一个通用网络文件系统来设计而是专门为“宿主机和虚拟机之间共享文件”这个场景定制的虚拟设备。它走的是virtio设备框架guest里有一个内核驱动通过virtqueue和QEMU通信。文件操作请求采用FUSE协议格式但不需要经过/dev/fuse设备而是直接由内核模块对接virtqueue。host侧则由一个用户态守护进程virtiofsd来接收请求转换成普通的Linux系统调用访问宿主机文件系统。这个架构上的改变把原来QEMU进程内的协议处理拆出来了。virtiofsd可以独立调度、独立调优而且DAX直通能力也是在virtio框架下才有空间实现。9p是给Plan 9遗产做的通用文件协议virtiofs则是给虚拟化场景专门画的一条新赛道。从Kata 2.x开始默认把shared_fs切到virtiofs不是没有道理的。2. virtiofs的核心机制拆解DAX缓存与FUSE协议的配合逻辑2.1 FUSE协议在virtiofs里被重新实现了一遍很多人以为virtiofs就是“虚拟机里挂了个FUSE”这个理解不够准确。标准FUSE的路径是用户态进程 - /dev/fuse - 内核FUSE模块 - 用户态daemon。virtiofs把这条链路简化了guest内核的virtio_fs驱动直接扮演FUSE模块的角色请求封装成FUSE消息格式通过virtqueue直接发给host的virtiofsd。这样做的好处是省掉了一层字符设备和用户态上下文切换。FUSE消息结构本身已经被大量文件系统验证过协议字段齐全特别适合做文件语义的精细化控制比如lookup缓存、属性超时、目录项的负缓存。virtiofsd收到请求后翻译成open、read、write、getattr、readdir这些系统调用命中宿主机的VFS和page cache。所以从功能视角看guest里的文件系统体验非常接近本地文件系统从性能视角看合理的缓存策略下很多操作根本不需要穿透到host在guest内就能完成。2.2 DAX window为什么能绕开guest page cache这是virtiofs最有技术含量的部分。普通文件共享路径里guest读文件时数据要经过guest page cache - virtqueue - QEMU内存拷贝 - virtiofsd - host VFS - host page cache。每一层都有开销尤其是大块数据传输内存拷贝和CPU占用非常可观。DAXDirect Access的思路是在虚拟机地址空间里开辟一块PCI BAR窗口把host端文件页直接映射进去。guest里的进程mmap一个virtiofs文件时内核可以把guest物理地址直接映射到这块BAR区域CPU访问它就像访问普通内存一样。真正读到的其实是host page cache里的文件页写的时候也直接写进host page cache。整条链路省掉了virtqueue通信和QEMU内部的拷贝动作。当然DAX不是万能的。窗口大小是有限资源由Kata配置里的virtio_fs_cache_size控制映射以2MB大页粒度对齐文件多而碎的时候很可能把窗口耗尽。窗口耗尽后virtiofs会回退到普通virtqueue路径性能断崖下跌。我后面压测的时候就真实遇到了这个问题一会儿细说。2.3 cache三档到底怎么选none、auto、alwaysvirtiofs的缓存策略直接决定数据一致性和性能表现生产环境不能只开一个默认值完事。cachenone是所有读写都穿透到hostguest不持有page cache。语义最强性能最差适合数据库、分布式存储这类绝对不能读到旧数据的场景。cacheauto是Kata的默认策略guest可以缓存文件和目录的属性以及目录项但文件数据还是要走host。它引入了attr超时机制定期向host校验文件变化类似NFS的attribute cache行为。大多数配置类、日志类共享目录用auto就够了。cachealways则是最大缓存策略guest对文件数据和元数据都全面缓存写回延迟交给guest内核管理。性能最好但多客户端共享同一文件时一致性风险很高。我通常只建议用在单写多读、只读共享这类场景。选择策略时记住一句话缓存放guest性能好但一致性弱缓存放host一致性稳但性能回调。没有绝对最优要按目录用途拆分挂载。3. 测试环境与测试矩阵设计不能只看带宽和IOPS3.1 压测环境说明我这次测试用的是一台x86服务器32核、128G内存系统盘和数据盘都是NVMe SSD。宿主机内核版本5.15系列Kata Containers版本跑过2.4.x和2.5.x两代QEMU用的7.x系列。普通容器对照组直接跑在宿主机上。测试方式是用Kata拉起一个完整的容器在里面挂载virtiofs共享目录然后跑各类基准和模拟负载。对照组则把Kata的shared_fs切回9p同样基准再跑一遍。最后还有一个本地盘的基准值作为底限参照。这个环境不算特殊理论上能在大部分主流服务器上复现。3.2 为什么拒绝只跑fio如果只跑fio的顺序读、顺序写virtiofs和9p的差距并不会显得特别夸张因为顺序IO更多受限于设备本身。真实业务里共享目录的痛点基本都在随机小文件、元数据操作和应用对mmap的依赖上这些恰好在纯fio简单模式下会被掩盖。所以我设计了九个场景覆盖典型业务负载方向大文件顺序读写、4K随机读写、元数据密集操作、小文件并发创建删除、mmap随机访问、带fsync的数据库模拟、多线程混合读写、跨容器同目录并发访问、git仓库和代码编译这种高频小IO场景。每个场景都有明确的工具、指标和模拟目标。工具方面用了fio做块IO和混合负载sysbench做文件IO模拟git和编译任务模拟真实开发场景Python脚本控制小文件并发。指标不只记录带宽和IOPS还记录了P99延迟、CPU占用率、耗时总量因为生产上更关心长尾延迟。3.3 Kata配置改动细节在Kata的configuration.toml里核心配置就三块shared_fs virtio-fs virtio_fs_cache_size 8192 virtio_fs_extra_args [-o, cacheauto]shared_fs指定使用virtiofsvirtio_fs_cache_size控制DAX窗口大小单位MBextra_args里可以传virtiofsd的参数。对照组我把shared_fs改回9p即可其他参数保持不动。这里特别提醒一点修改cache策略后Kata需要重启运行时和Pod才能生效不能热更新。测试之前务必确认running Pod已经被新配置接管否则结果会串。4. 性能实测对比九种场景下virtiofs与9p的真实差距4.1 结果总览virtiofs全面领先但仍不是本地盘所有场景我都以本地盘作为100%基准对比9p和virtiofs的表现。表格里的数据是我这个环境下的实测值用来定性和看趋势没问题换到不同硬件会有浮动但比例关系基本可以参考。测试场景9p相对性能virtiofs相对性能备注顺序读大文件35%88%virtiofs接近本地盘顺序写大文件22%64%写路径开销明显4K随机读9%72%9p灾难级表现4K随机写7%41%写放大问题需关注元数据密集操作3%58%9p基本不可用小文件并发创建删除4%52%目录项缓存有作用mmap随机访问失败/极慢67%DAX直通效果明显带fsync的数据库模拟2%38%fsync是最大瓶颈混合读写业务负载12%61%综合表现参考9p在4K随机读上只有本地盘的9%这解释了很多线上卡顿。virtiofs在随机读、mmap上表现最好几乎追上本地盘但随机写和fsync场景还是明显落后这反映了写路径的固有成本。4.2 好消息和坏消息哪些场景能放心用先说好消息。随机读、顺序读、mmap三个场景virtiofs表现非常接近本地盘尤其是DAX直通模式下读取大文件几乎没有协议开销。这类负载包括代码目录共享、模型文件读取、只读配置加载、日志归集读取直接用virtiofs放心。元数据操作方面virtiofs比9p提升了一个数量级。它借助FUSE协议的属性缓存和目录项缓存在cacheauto下表现已经可接受。代码编译这种大量创建临时文件再删除的场景virtiofs跑下来耗时大约是本地盘的1.8倍9p则要慢将近二十倍。坏消息集中在写路径。顺序写的相对性能只有64%随机写只有41%fsync场景更是掉到38%。写操作在DAX模式下直接写host page cachevirtiofsd后续还要维护一致性这里面有额外开销。再加上guest和host两层page cache写放大问题在随机小写时比较明显。4.3 并发上去了性能反而掉下来测试中我特别观察了并发线程数从1逐步增加到32时的表现。virtiofs在小并发下表现优秀但并发超过一定阈值后性能曲线先涨后跌出现了明显的吞吐回退。host侧的virtiofsd进程CPU占用率一路上涨最终成为瓶颈。9p在同样并发测试中更惨线程数即使只有8就出现大量的IO等待整个容器几乎卡死。virtiofs至少能撑到更高并发但也不是无限可扩展的。这个问题在Kata容器密度较高、多个Pod同时挂载相同共享目录时会放大。后来排查发现virtiofsd在较老版本里线程模型是受限的大量并发请求集中在同一个工作线程上处理CPU单核跑满后其余请求全部排队。官方后来的实现逐步改进了并发模型但如果你还在跑老版本压测时最好把并发摸清楚别被小并发数据迷惑。5. 稳定性与异常场景踩坑记录锁语义、崩溃恢复和缓存一致性5.1 多容器同目录并发flock能用但写缓存会打架测试时我用两个Kata容器同时挂载同一宿主目录通过flock对同一个文件做互斥写。实测下来flock在virtiofs上是能工作的因为virtiofsd最终在host上以普通文件方式打开文件所有锁都汇聚到host内核处理跨虚拟机的锁语义是可靠的。但有个问题容易踩坑当两个容器都用cacheauto或always时guest各自保留文件数据缓存。一个容器写入数据后另一个容器可能在一段时间内看不到最新内容即使锁已经释放。锁解决的是写互斥并不解决缓存刷新。这在分布式任务共享状态文件时会带来诡异问题。我的处理方案是涉及多客户端读写的目录强制用cachenone宁可比性能也不赌一致性。5.2 fuse request timeout导致的I/O error高并发压测时guest里偶尔报出I/O error应用直接异常退出。起初以为是磁盘问题后来反复复现发现是FUSE请求超时机制在起作用。当virtiofsd处理不过来guest发出的请求长时间得不到响应guest内核FUSE模块就会标记连接异常并返回EIO。这个问题的触发点有两个一是virtiofsd单线程瓶颈二是guest侧请求队列积压。我临时把并发和iodepth降下来才能稳定跑完测试后来调整了virtiofsd的并发配置并监控host侧CPU才把它压住。碰到I/O error时不要急着怀疑文件系统损坏先看host侧virtiofsd进程是不是已经CPU跑满再检查guest内核日志里的fuse超时记录。有条件的情况下换用新版运行时并适当增大FUSE超时配置能有效规避这类假故障。5.3 mmap MAP_SHARED在业务里的真实表现DAX直通对mmap的支持是virtiofs相对9p的最大优势之一。我在测试里用两个进程共享一个映射文件做状态同步运行良好互相修改立即可见性能接近本地共享内存映射。但分布式场景下要清醒认识一点mmap写的数据先落在host page cache真正落盘要等host内的pdflush机制触发。如果host突然断电所有落在page cache里但未写盘的数据都会丢失。9p时代大家习惯了慢反而不会丢太多virtiofs快了数据丢失窗口也变大了。数据库和消息队列这类数据不能丢的组件哪怕virtiofs支持mmap也不建议把数据目录放上来。mmap共享文件做状态同步、锁协调这类非关键数据场景才是它的正确战场。5.4 数据库类负载能跑但别勉强我用sysbench在virtiofs上模拟了数据库IO结果能跑但绝不惊艳。O_DIRECT支持方面测试下来virtiofs在打开文件时会传递对齐的direct IO请求能正常工作但随机写和fsync的短板仍然存在。数据库的崩溃恢复高度依赖fsync速度和写顺序这恰好是virtiofs最弱的地方。如果业务方坚持把MySQL这类组件放到共享目录我的建议是数据目录用块存储或本地盘只有配置文件、临时目录这类非关键数据放到virtiofs。拿virtiofs扛数据库性能上去了但可靠性在下降属于拿核心资产换便利不划算。6. 生产配置建议与最后的选型边界6.1 我们线上最终采用的Kata配置经历了这轮测试后我线上环境给出了一个相对保守但稳定的配置方案shared_fs virtio-fs virtio_fs_cache_size 4096 virtio_fs_extra_args [-o, cacheauto]cache选auto保证默认情况下元数据有缓存文件数据由host控制一致性。如果业务目录是单写多读而且对性能有极致要求再单独加一份extra_args切换成cachealways。DAX窗口我设了4G。窗口太大会占掉guest内存地址空间太小在大文件并行读写时很快耗尽回退到普通路径。4G对大部分共享目录已经够用。如果你需要看具体填充情况可以在guest里用mmap映射后观察后端命中的比例但我实测很难拿到准确数字更多是靠压测判断窗口是否够用。6.2 挂载规划不同用途目录用不同策略生产上不建议一个共享目录包打天下。我现在的做法是把共享存储按用途拆成三个挂载点配置和只读数据用cachealways追求最快的重复读。代码目录和工作目录用cacheauto兼顾性能和一致性。跨容器协作的状态目录用cachenone保证任何写入都能被其他消费者尽快看到。三种目录落在相同的物理路径池里只是挂载参数不同。这样拆下来每一类目录都能在自己的策略下达到最优状态也避免了缓存语义互相干扰。6.3 什么场景还是别用virtiofsvirtiofs不是万能的。数据库数据目录、分布式存储WAL日志、内容寻址存储的临时写缓冲这几类对写延迟和fsync可靠性要求极高的场景我仍然坚持用本地块设备或者专门的存储方案。虚拟化文件共享层天然存在协议转换和双缓存无论怎么优化都不可能比直通块设备更稳。同样如果你需要多个Kata虚拟机强一致地共享同一个文件并且要求实时可见virtiofs即使配cachenone也不是最佳选择。这种场景最好的归宿仍然是分布式存储用户态协议或者直接共享块设备由guest自己跑文件系统。6.4 测试过程中值得继续深挖的方向virtiofs还在快速演进。新的virtiofsd实现改了线程模型高并发吞吐相比早期版本有明显提升。DAX机制上也有针对不同文件粒度做单独缓存控制的尝试未来配置会更灵活。如果后续还有精力我打算做两件事一是把virtiofs和宿主机本地存储做一层组合只把热数据放共享盘冷数据回本地看看能否兼顾共享和性能二是针对多Kata实例同时挂载同一大文件做并发模式测试把刚才提到的缓存一致性问题量化成工程指标。这两个方向弄明白Kata共享存储这块就算彻底盘清楚了。写这篇分析的时候我回想最开始被9p折磨的日子还是要感慨一句virtiofs出现之后Kata Container的共享目录体验确实是质的飞跃。但所有性能优势都必须建立在正确的cache策略和使用边界之上。把benchmark数据当成衡量标准之前先想清楚你的业务到底在哪个象限里这才是在生产环境真正稳妥的做法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑