多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

K8s接入CephFS全攻略:从选型部署到CSI配置与排错

K8s接入CephFS全攻略:从选型部署到CSI配置与排错 我见过不少团队把Ceph接进Kubernetes时第一个想到的就是RBD块存储直到有一天产品提了个需求五个Pod要同时读写同一份数据。到那一刻才发现块存储解决不了“多写多读”的问题而CephFS从一开始就是为这种场景准备的。这篇文章就把我最近用最新版Ceph主线代号tentacle搭CephFS文件存储、并把它作为K8s后端存储的全过程聊透从选型、部署、CSI接入到线上排错全程有命令和配置可以直接照着做。适合正在规划K8s存储后端、或者在RBD和CephFS之间纠结的运维和开发同学参考。1. 为什么“文件存储”这个定语才是重点——k8s存储选型里的隐藏前提1.1 三种存储接口在K8s里的真实定位Ceph一套集群能同时给K8s提供三种存储语义RBD块存储、CephFS文件系统、RGW对象存储。很多人默认“Ceph就是给K8s当硬盘用的”这个认知只对了一半。RBD在K8s里对应的是块设备CSI会把一个RBD image映射成节点上的块设备再格式化成文件系统挂载进Pod。块设备天然只能被一个节点独占读写所以PVC的accessModes只能声明ReadWriteOnceRWO。想跨Pod共享要么用RBD的共享块多挂载要么在应用层自己做分布式锁都不省心。CephFS对应的是POSIX文件系统多个节点可以同时挂载同一个文件系统。K8s侧就可以声明ReadWriteManyRWX多个Pod共享同一个PVC这在RBD上很难做到。RGW则完全是另一条路线S3协议的对象存储K8s原生的PV机制不直接支持它一般是通过SDK在应用层访问或者用s3fs这类工具绕一层再当文件系统用生产环境很少拿它当PV后端。三种接口的差异我用一张表总结接口K8s中语义典型accessModes适用场景不适合场景RBD块设备RWO数据库、单副本高IO负载多Pod共享读写CephFS文件系统RWX / RWO共享配置、批处理工作目录、多写场景超高性能随机小IORGWS3对象一般不用PV静态资源、大数据、备份归档需要POSIX语义的场景1.2 什么情况下你其实需要的是CephFS而不是RBD我自己的判断标准很简单如果PVC会被两个以上Pod同时挂载并且它们都要写文件直接上CephFS别在RBD上折腾。真实项目里这类场景非常多分布式训练的代码和模型共享目录多个Worker要同时读代码、写checkpoint批处理管道里多个任务读写同一个工作目录CMS、电商后台这类部署成多副本的应用上传的图片和附件要落到所有副本都能访问的地方日志采集器多Pod写同一个采集目录或者需要共享配置热更新。这些场景用RBD都会碰壁。RBD即使通过rbd-read-only或者共享块方式强行多挂载文件系统的一致性问题也会接踵而至。CephFS也不是万能钥匙。它本质是文件系统元数据操作要走MDS如果你的负载是海量小文件、每秒几十万次create/unlinkMDS会成为瓶颈。这时候RBD本地文件系统反而更合适。选型要辩证看不是“越高级越好”而是“什么负载配什么存储”。1.3 CephFS与NFS、GlusterFS的对比很多团队会拿NFS和CephFS比。NFS的最大优势是简单一个export一个挂载点就能用但单点问题始终绕不开NAS头挂了整个K8s集群的挂载目录全部变只读或超时。虽然可以用Keepalived之类的方案堆高可用但数据面还是集中式的。CephFS的数据面本身就是分布式的元数据由MDS集群负责数据打散到OSD上天然没有单点。加上配额、快照、多活MDS这些能力作为K8s后端存储比NFS省心得多。GlusterFS在K8s里也有不少历史用户但社区迭代节奏近几年明显慢下来和CSI的集成成熟度也不如CephFS。Ceph胜在一条路走到底同一套集群RBD、CephFS、RGW三样都能出后续业务扩展不用再引入新的存储系统。2. tentacle版本到底新在哪以及部署前必须想清楚的两条路线2.1 版本代号与主线tentacle和20.x的关系Ceph的版本命名一贯用海洋生物做代号Squid是19.x系列Squid之后的下一个主线版本在社区讨论里被习惯性地称为tentacle对应20.x系列。标题里说“tentacle版本”指的就是这条还在快速迭代的新主线。我在测试环境里用的就是这条主线。和更早的版本比最明显的变化不是某个炫技功能而是cephadm这套部署运维体系已经非常成熟一条bootstrap命令就能把MON、MGR、OSD拉起来后续加机器、加服务全部走编排配置这对我们做K8s后端存储的人来说是实打实的收益。需要提醒的是如果你要上生产不必追着“最新版”三个字跑。Ceph的稳定性和版本迭代策略大家心里都有数生产环境求稳的话用18.x或19.x的正式版也完全够用下面的命令和配置在这些版本上通用因为CephFS的APIs和ceph-csi的对接逻辑没有变。2.2 独立集群加CSI还是Rook我的选型逻辑把Ceph作为K8s后端存储有两条主流路线一是在K8s集群外面搭一套独立Ceph集群K8s只装ceph-csi去连接它二是在K8s集群内部用Rook来运行Ceph。我最终选了独立集群。理由说起来也是老生常谈第一故障域隔离。独立集群的MON、OSD、MDS跑在自己的机器上K8s节点出问题不会直接拖垮存储存储节点出问题也不会让Kubelet大面积异常。Rook把Ceph跑在K8s里等于把存储和控制绑在同一批Pod上叠加故障时排查问题的难度成倍上升。第二升级互不影响。Ceph集群版本升级选在业务低峰做K8s集群的升级节奏可以完全错开。Rook方案里升级Ceph等于升级Rook Operator还要考虑和K8s版本的兼容性链路长了很多。第三非K8s业务也能用同一套存储。公司内部可能还有虚拟机、物理机业务要挂CephFS独立集群一套存储两边共享资源利用率更高。Rook也不是没有优势。如果你的诉求是“一套K8s全搞定”不想另外管理和维护一批裸机Rook的声明式体验确实好。我见过不少中小团队用Rook跑测试环境跑得很顺。但有一个前提很重要K8s集群本身必须稳定。我自己的教训是在集群还没完全健康的时候就去部署Rook这类重负载控制器出了故障会把控制面和存储面的问题搅在一起。遇到过master初始化日志里反复出现“the api server is not healthy after 4m”这种情况节点网络或者容器运行时本身就没准备好这时候再多部署一个Operator只会雪上加霜。先把K8s基础健康度确认清楚再谈存储后端这个顺序不能乱。2.3 部署前必须完成的检查清单不管哪条路线环境准备是共同的。我这里整理一份清单都是踩过坑之后攒出来的至少三台存储节点MON建议就是这三台保证quorum每台节点操作系统选主流发行版Ubuntu 22.04或Rocky Linux 9都行别用太老的版本必须做时间同步chrony或ntp选一个Ceph对时钟偏移极度敏感MON之间时间偏差超过阈值会直接导致仲裁异常关闭swap至少把Ceph相关节点关掉避免内存颠簸影响OSD网络方面最好规划两个网段一个public network走客户端流量一个cluster network走OSD之间的数据同步如果只有一张网卡也不是不能用但大规模场景下性能会吃亏OSD所在节点确认磁盘列表生产环境不要用“所有可用设备”这种一键配置一定要用spec文件精确指定要格式化的盘防火墙策略提前想清楚MON的6789端口和msgr2的3300端口、OSD的6800-7100端口段必须放开集群内部通信端口千万别漏。这些检查做完再进入部署环节后面会少很多莫名其妙的故障。3. 从裸机到可用CephFScephadm落地的完整过程3.1 单命令bootstrap背后的原理和参数选择现在部署Ceph比老版本直观太多。老版本要先装一堆包、手写conf、逐个起daemon现在cephadm一条命令就能搞定。我们站在维护者角度还是要清楚这条命令到底做了什么。先安装cephadm。如果是Rocky 9直接通过发行版仓库装dnf install -y cephadmUbuntu系则是apt update apt install -y cephadm然后执行bootstrap指定第一个MON所在节点的IPcephadm bootstrap --mon-ip 10.0.0.10 --apply-spec /root/ceph/cluster.yamlbootstrap实际做的事情包括在目标节点上拉取Ceph容器镜像、生成初始MON、创建初始MGR、生成/etc/ceph/ceph.conf和ceph.client.admin.keyring、顺带把Ceph Dashboard也起起来。整个过程的日志在/var/log/ceph/下面卡住了先翻日志。--apply-spec参数是给集群打一份初始的编排声明文件。OSD这种服务一开始就可以写进spec里bootstrap完成之后自动应用。一个最简单的OSD spec长这样service_type: osd service_id: osd_spec placement: hosts: - node1 - node2 - node3 data_devices: paths: - /dev/sdb - /dev/sdc这么写的好处是只格式化我指定的磁盘不会把系统盘或者跑了业务的盘给卷进去。用ceph orch apply osd --all-available-devices虽然省事但风险也在这里机器上如果插了没用的旧盘会被直接量产数据全没。生产环境我不推荐这种方式。3.2 创建文件系统元数据池与数据池的分工CephFS和RBD共享同一个OSD集群但文件系统本身需要两块独立的池元数据池metadata pool存目录、文件名、inode这类信息数据池data pool干活儿存实际文件内容。先算池的PG数量。有个被验证过无数次的估算公式PG总数约等于OSD总数 × 100÷ 副本数。假设12个OSD、副本3那PG总数在400左右。元数据池不需要太多PG通常64个就够数据池按剩余容量分配比如256或512。执行命令ceph osd pool create cephfs_meta 64 ceph osd pool create cephfs_data 256 ceph fs new cephfs cephfs_meta cephfs_data新版本也可以用一句话创建ceph fs volume create cephfs它会自动帮你建好meta pool和data pool。但我更习惯手动建池的方式因为可以控制副本策略和PG数。创建完验证一下ceph fs ls ceph fs status cephfsfs status里能看到MDS的存活状态和文件系统的容量信息。一开始还没有MDS需要手动指定ceph orch apply mds cephfs --placement2 node1 node2这条命令会在node1和node2上各拉起一个MDS进程。想扩到更多也没什么负担MDS是软状态服务多活的目的就是分摊元数据负载。需要临时把MDS改成2个以上就执行ceph fs set cephfs max_mds 2注意这只是把文件系统允许的最大MDS数量改成2真正要拉起进程还是靠上面的ceph orch apply mds。3.3 为CephFS准备独立账号最小权限才是生产态度集群刚刚创建完手里有一把client.admin的钥匙足够操作一切。但把admin key塞给Ceph-CSI去挂载所有PVC这是典型的生产事故隐患。万一key泄露等于整个存储集群裸奔。按最小权限原则给K8s单独创建一个账号只授予CephFS需要的权限ceph auth get-or-create client.k8s mon allow r \ mds allow rws fsnamecephfs \ osd allow rw tag cephfs datacephfs这里面的含义逐条拆开看mon allow r只允许读MON信息因为客户端需要知道MON地址和文件系统拓扑mds allow rws允许在MDS上创建、读、写、删除文件和目录osd allow rw tag cephfs datacephfs允许读写CephFS数据池里的对象用tag限制作用域不让它去动RBD的池。生成之后查看并记录keyring内容后面配置CSI要用ceph auth get client.k8s拿到的是类似这样的结构[client.k8s] key QXm9dGVsbHlvdGhlcnVpbmFsc2VjcmV0Cg这个key后面要填进K8s的Secret里。同时把集群的FSID记下来用ceph fsid命令查StorageClass里要用。4. 把CephFS接进Kubernetesceph-csi驱动的接入与StorageClass设计4.1 ceph-csi部署与Secret配置谁在替你完成挂载动作CephFS和K8s之间的桥梁是ceph-csi它分成两组组件一组是Controller端的Provisioner负责创建和删除子卷另一组是每个节点上的NodePlugin负责把子卷实际挂载进Pod。部署最省事的方式是用官方Helm Charthelm repo add ceph-csi https://ceph.github.io/csi-charts helm install cephfs ceph-csi/ceph-csi-cephfs -n ceph-csi --create-namespace跑完以后确认下Pod起来了kubectl get pods -n ceph-csi -o wide配置阶段最核心的是一个Secret。它包含两部分K8s要用的账号ID和对应的key。注意adminID填的是client.后面那段不是完整的client.k8s。这个细节我见过好几个人填错包括我刚开始也栽过一次。apiVersion: v1 kind: Secret metadata: name: csi-cephfs-secret namespace: ceph-csi stringData: adminID: k8s adminKey: QXm9dGVsbG90aGVydWluYWxzZWNyZXQK把上面查出来的key值填到adminKey里然后applykubectl apply -f csi-cephfs-secret.yaml这里有个容易忽略的点如果你是用上面最小权限账号创建的key那就不需要再单独提供userID和userKey因为adminID本身只用来管理子卷和挂载权限正好覆盖CephFS操作。如果你是临时拿admin key测试后面一定要记得换回来。4.2 StorageClass参数逐个解释以及一个可以直接抄的YAMLSecret就绪之后创建StorageClass。先解释几个关键参数理解之后就不会因为填错而排查半天。clusterID必须和ceph fsid完全一致填错直接PVC PendingfsNameCephFS文件系统的名字对应ceph fs ls里的名字pool数据池的名称是cephfs_data而不是cephfs_meta这个我也踩过填成meta池会报错那串csi.storage.k8s.io/provisioner-secret-name和namespace告诉Provisioner去哪个命名空间找Secretcsi.storage.k8s.io/node-stage-secret-*NodePlugin在节点上挂载子卷时也要用到同一个Secret这个不能省mounter默认是kernel如果内核客户端有问题可以改成fuseallowVolumeExpansion设为true支持PVC扩容。一个可以直接抄的StorageClassapiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: cephfs-storage provisioner: cephfs.csi.ceph.com reclaimPolicy: Delete allowVolumeExpansion: true parameters: clusterID: 集群的fsid fsName: cephfs pool: cephfs_data mounter: kernel csi.storage.k8s.io/provisioner-secret-name: csi-cephfs-secret csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi csi.storage.k8s.io/controller-expand-secret-name: csi-cephfs-secret csi.storage.k8s.io/controller-expand-secret-namespace: ceph-csi csi.storage.k8s.io/node-stage-secret-name: csi-cephfs-secret csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi4.3 用PVC和Pod验证动态供给全链路配置完StorageClass用PVC验证一下。CephFS可以声明为RWX这是它对比RBD的核心优势apiVersion: v1 kind: PersistentVolumeClaim metadata: name: cephfs-share-pvc spec: accessModes: - ReadWriteMany storageClassName: cephfs-storage resources: requests: storage: 10Gi创建并观察状态kubectl get pvc cephfs-share-pvc过一会儿应该变成Bound。然后起两个Pod同时挂载这个PVCapiVersion: v1 kind: Pod metadata: name: cephfs-test-1 spec: containers: - name: busybox image: busybox command: [sleep, 3600] volumeMounts: - name: share mountPath: /data volumes: - name: share persistentVolumeClaim: claimName: cephfs-share-pvc --- apiVersion: v1 kind: Pod metadata: name: cephfs-test-2 spec: containers: - name: busybox image: busybox command: [sleep, 3600] volumeMounts: - name: share mountPath: /data volumes: - name: share persistentVolumeClaim: claimName: cephfs-share-pvc在两个Pod里分别执行kubectl exec -it cephfs-test-1 -- sh -c echo from-pod-1 /data/a.txt; sleep 5; cat /data/b.txt kubectl exec -it cephfs-test-2 -- sh -c echo from-pod-2 /data/b.txt; sleep 5; ls /data能看到双方写入的文件互相可见说明CephFS文件存储已经作为K8s后端跑通了。动态供给的原理也顺便说一下Ceph-CSI的Provisioner在收到PVC创建请求后会调用ceph fs subvolume create在CephFS里创建一个独立子卷并设置对应容量配额Pod调度到节点后NodePlugin把这个子卷挂载进宿主机的临时目录再bind mount进Pod。整个数据面通过CephFS的分布式能力走多个节点上是同一份数据。5. PVC卡住、挂载超时的完整排查链路实测复盘5.1 第一站永远是Ceph集群本身先看FS和MDS状态有一次线上PVC一直Pending同事第一反应去看CSI的日志翻了几百行没看出所以然。我让他先执行一条命令问题立刻暴露了ceph fs status cephfs输出里MDS状态是failed进程还在不停重启。原因是其中一台节点的内存紧张MDS被OOM杀掉fallback机制没有正常工作。CephFS挂载依赖MDSMDS不健康K8s侧再怎么折腾都没用。所以任何挂载问题出现第一站必须是存储侧的体检ceph -s ceph fs ls ceph fs status cephfs ceph osd tree这三条命令把集群健康、文件系统存在性、MDS状态、OSD分布都看一遍先确认“存储本身没问题”再进入K8s侧排查排查顺序永远是从下往上。5.2 Kubelet侧最容易踩的坑缺ceph-common、内核模块老、端口不通挂载类报错的经典症状是Pod一直ContainerCreating事件里写着FailedMount或者Timeout expired waiting for attach。反手先去看Kubelet日志journalctl -u kubelet | grep -i ceph日志里如果出现mount: wrong fs type, bad option, bad superblock十有八九是节点上没装Ceph客户端工具。内核类型的挂载需要ceph-common提供的mount.ceph辅助程序所有K8s节点都必须装dnf install -y ceph-common如果日志里出现libcephfs: error或者protocol version相关报错那就是内核的CEPH模块版本比服务端老。老内核挂新集群经常遇到协议不兼容。解决方案两条路一是升级内核到相对新的版本二是直接在StorageClass里切fuse模式parameters: mounter: fuse切fuse需要节点上装ceph-fuse包并且NodePlugin要以privileged模式运行默认部署是满足的。fuse模式对内核版本不再敏感兼容性更好代价是性能略逊于内核模式适合作为备选方案。还有一种情况Pod事件里没有明确报错但挂载操作异常慢甚至卡死考虑端口不通。Ceph的公共端口是6789和3300节点到MON、节点到OSD的数据端口6800-7100都必须放通。用下面的命令在Kubelet节点上测一下timeout 5 bash -c cat /dev/null /dev/tcp/10.0.0.10/6789 echo ok不通就去看宿主机防火墙和云安全组不要只顾着看K8s侧的NetworkPolicy。5.3 权限和子卷问题adminID写错的典型症状有一类报错藏在Provisioner日志里rpc error: code Internal desc error getting metadata for subvolume这种一般不是文件系统坏了而是Provisioner调用Ceph的管理API时权限不足或者找不到对应的子卷。排查按两步走第一步确认Secret里的adminID填的是k8s而不是client.k8sadminKey必须是base64明文key不带key 前缀。填错了Provisioner在调用ceph fs subvolume create时会直接权限拒绝。第二步确认CSI账号授权的fsname和你实际创建的文件系统一致。比如用client.k8s的mds授权写的是fsnamecephfs那StorageClass里的fsName必须一字不差地填cephfs。不匹配就会在元数据操作时报错。还有个隐蔽点如果PVC已经正常用过后来手贱删了PV或者直接把CephFS下的子卷删了再重建PVC时就会出现“subvolume不存在”的错误。因为ceph-csi默认会尝试复用或有残留的volume记录。遇到这种在Ceph侧把残留的subvolume信息清掉ceph fs subvolumegroup ls cephfs ceph fs subvolume ls cephfs csi --group csi然后把PVC、PV一起删掉重建即可。注意reclaimPolicy: Delete会在PV删除时自动清理子卷反过来也意味着如果Ceph侧因为某些原因清理失败K8s侧PV会一直卡在Terminating。这时只能手动登到Ceph集群里把对应子卷处理掉之后强制删PV。5.4 NodePlugin异常与fuse模式的连锁问题如果切换成fuse模式后挂载仍然失败先看NodePlugin Pod本身kubectl get pods -n ceph-csi -o wide关注每一台节点上是否有csi-cephfsplugin实例并且状态是Running。fuse挂载依赖宿主机的/dev/fuse设备如果节点没开放这个设备或者NodePlugin没以特权模式运行就会报设备不存在。这种情况通常是部署ceph-csi时用的YAML被裁剪过去掉了privileged: true和hostPID等关键字段。Helm默认配置没问题手工定制时最容易翻车。我的经验是如果不是有特殊诉求直接用官方Helm Chart别自己拼YAML。6. 生产环境我给CephFS的调优参数与运维清单6.1 MDS层面缓存、多活和均衡CephFS的性能瓶颈大概率出现在MDS因为所有文件系统元数据操作都要经过它。多MDS不是银弹它解决的是并发规模问题不是单条元数据操作的延迟问题。比较实用的两个调优点缓存上限。MDS会缓存目录项和inode缓存越大命中率越高但内存占用随之增加。默认的缓存上限偏保守在内存充足的节点上把MDS的缓存内存上限调大ceph config set mds mds_cache_memory_limit 8G这个值根据MDS节点内存调整一般给节点总内存的1/4到1/3比较稳。如果不设上限MDS吃光内存被OOM的可能性很大我遇到过不止一次。多MDS均衡。当目录数量多、并发客户端多时把MDS数量加到2个以上ceph fs set cephfs max_mds 2MDS之间会通过动态子树迁移来自动平衡负载。如果你发现某个MDS的负载长期压满、其他MDS闲置可以检查数据分布把热点目录pin到特定MDS上。生产里最省事的还是依赖默认的负载均衡器人为干预反而容易弄巧成拙。6.2 客户端挂载模式、Pool策略与容量规划内核模式和fuse模式的选择我再说细一点。内核模式走的是内核的ceph模块路径更短吞吐量通常优于fuse。但内核版本太老时协议兼容性差而且一旦内核模块异常处理起来要么升级内核要么重启节点。fuse模式多了一层用户态转发性能略有损耗但胜在版本依赖小、兼容性好。生产环境的建议是如果K8s节点内核都是相对较新的版本优先内核模式如果节点系统较老或者看到内核挂载后出现奇怪的IO错误切fuse模式。两种模式可以在不同StorageClass里共存也可以随时切换代价不大。数据池的副本策略一般生产用3副本min_size设为2保证坏掉一块盘时集群还能提供读写服务ceph osd pool set cephfs_data size 3 ceph osd pool set cephfs_data min_size 2CephFS的元数据池不要用纠删码EC元数据操作对延迟极其敏感EC带来的计算开销和编码延迟会直接放大所有文件操作时延。数据池原则上可以用EC但考虑到CephFS的IO特征初期直接上副本策略最省事等规模大了再针对冷数据池考虑EC。容量规划方面CephFS的动态供给会为每个PVC分配独立子卷并打配额。allowVolumeExpansion: true之后PVC扩容可以直接编辑PVC的resources.requests.storage后端会自动调整子卷配额。但CephFS整体容量取决于OSD集群建议容量水位控制在80%以下超过这个阈值OSD的rebalance会明显变慢整个集群的性能也会跟着下滑。6.3 监控与备份把集群状态关进笼子里没有监控的存储集群等于裸奔。cephadm默认会把Prometheus模块打开但你要确认这个模块是否真的启用ceph mgr module enable prometheus启用后MGR会暴露/metrics端点配合Prometheus采集和Grafana的Ceph Dashboard面板可以很直观地看到集群健康、OSD延迟、MDS负载这些核心指标。告警规则至少要覆盖几类事件集群状态变成HEALTH_WARN以上、OSD down、PG状态异常、MDS down、容量超过告警线。生产事故里“存储慢”比“存储挂”更隐蔽也更难受所以对象延迟指标OSD commit latency、MDS op latency最好都盯上。数据保护方面CephFS自带快照能力。kubectl创建的PVC背后是subvolume快照命令可以直接操作ceph fs subvolume snapshot create cephfs subvolume名 snap-时间戳 --group csi恢复的时候使用snapshot protect和snapshot clone组合操作。更省心的方式是写一个定时脚本对每个PVC的subvolume做快照并定期把快照拷贝到其他存储或对象存储里防止整个Ceph集群遭遇毁灭性故障。备份这件事我的观点一直没变过CephFS的分布式特性解决的是可用性问题不是数据容灾问题。集群级别的故障依然可能让你丢数据定期异地备份永远不该省。最后分享一点个人体会。做K8s和Ceph的对接最让人头疼的往往不是那些高大上的调优参数而是“客户端节点必须装ceph-common”这种看起来最基本却总被忽略的依赖。我排过最久的故障查到最后就是某个新扩容的节点没装包。所以我的顺序永远是先确认基础依赖和集群健康再翻CSI日志最后才去调参数。CephFS成熟、稳定但前提是你真的愿意按它的规矩来。
返回列表