# 08 · CephFS 文件存储篇 > 基于 Ceph 官方文档整理 | 文档版本:**Tentacle v20.2.4** | 抓取日期:2026-09-17 | 源文:ceph/ceph@v20.2.4 `doc/cephfs/` > 官网入口:https://docs.ceph.com/en/tentacle/cephfs/ --- ## 本册导读 本册覆盖 Ceph 官方文档 `doc/cephfs/` 目录下的**全部 49 个页面**(`index` 总览 + 48 个子页面),从"CephFS 是什么"讲到"元数据损坏后怎么用 `cephfs-data-scan` 重建",力求一册读完即可独立部署、调优与救火 CephFS。 **本册内容地图** | 部分 | 主题 | 对应源页面 | |---|---|---| | 第一部分 | 基础与概念 | `index`、`posix`、`cephfs-io-path`、`mdcache`、`dynamic-metadata-management` | | 第二部分 | 部署与挂载 | `createfs`、`multifs`、`add-remove-mds`、`multimds`、`standby`、`mount-prerequisites`、`mount-using-kernel-driver`、`mount-using-fuse`、`kernel-features`、`client-config-ref` | | 第三部分 | 权限与配额 | `client-auth`、`capabilities`、`quota`、`charmap` | | 第四部分 | 数据与元数据管理 | `file-layouts`、`fs-volumes`、`snapshots`、`snap-schedule`、`cache-configuration`、`scrub`、`full`、`lazyio`、`dirfrags`、`mds-journaling`、`journaler`、`purge-queue`、`metrics`、`cephfs-top` | | 第五部分 | 运维与排障 | `administration`、`mds-states`、`mds-config-ref`、`health-messages`、`troubleshooting`、`eviction`、`upgrading`、`experimental-features` | | 第六部分 | 灾备与镜像 | `cephfs-mirroring`、`disaster-recovery`、`disaster-recovery-experts`、`cephfs-journal-tool`、`recover-fs-after-mon-store-loss` | | 第七部分 | 生态 | `nfs`、`ceph-dokan`、`app-best-practices`、`mantle` | **建议阅读顺序**(先概念 → 再挂载 → 再运维 → 最后内部机制与灾备) 1. **第一次接触**:1.1 → 1.2 → 1.5 → 2.1 → 2.6 → 2.7 → 3.1 → 3.3,即可完成"建一个 CephFS、挂上、限流"的最小闭环。 2. **要上生产**:补 2.3 / 2.5(MDS 高可用)、2.4(多活 MDS)、4.2(volumes/subvolume,OpenStack Manila 与 CSI 都依赖它)、4.5(MDS 缓存调优)。 3. **要扛故障**:5.2(MDS 状态机)、5.3(MDS 配置项)、5.4(告警码)、5.5(排障)、5.6(客户端驱逐)。 4. **要救数据**:6.3 与 6.4 必须提前通读——真出事时没时间现学。 5. **要调优到骨头里**:4.9(目录分片)、4.11(分布式元数据缓存)、4.12/4.13(journal 与 journaler)、4.14(purge queue)。 **关于本册的默认值标注** Ceph 官方文档的 `doc/cephfs/*.rst` 里,配置项是用 `.. confval:: xxx` 指令引用的,**默认值在构建期才从源码注入,rst 源文本身不含默认值**。因此本册所有配置项的「类型 / 默认值」一栏,均取自同版本源码树 `src/common/options/*.yaml.in`(主要是 `mds.yaml.in`、`mds-client.yaml.in`、`cephfs-mirror.yaml.in`、`global.yaml.in`),并在表格下方统一标注: > 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4 少数选项在这些 yaml 里查不到(多为已被移除或改由 mgr 模块管理的旧选项),本册一律写「文档未明确」,集中在文末《文档未明确项》一节列出。 --- ## 第一部分 基础与概念 ### 1.1 CephFS 是什么 CephFS(Ceph File System)是一个构建在 Ceph 分布式对象存储 **RADOS** 之上的 POSIX 兼容文件系统。它的目标是提供一个先进的、多用途的、高可用且高性能的文件存储,覆盖从传统的共享 home 目录、HPC 临时空间(scratch space),到分布式工作流共享存储等各类场景。 CephFS 是 Ceph 中最古老的存储接口,曾经是 RADOS 最主要的用例。现在它与 RBD(块存储)、RGW(对象网关)一起构成 Ceph 的统一存储体系。 **最小上手路径**(大多数部署只需要一条命令): ```bash # 创建一个名为 cephfs 的 CephFS volume ceph fs volume create cephfs ``` Orchestrator 会自动创建并配置 MDS(前提是后端部署技术支持,见 Orchestrator 支持矩阵);否则需要手工部署 MDS(见 2.3 节)。挂载方式见 2.6~2.8 节。另外还提供了一个命令行交互工具 `cephfs-shell` 用于交互式访问或脚本化操作。 > 引自:[Ceph File System](https://docs.ceph.com/en/tentacle/cephfs/) ### 1.2 架构:数据 / 元数据分离 CephFS 通过几项关键的架构选择达成其目标: | 架构要点 | 说明 | |---|---| | **元数据与数据分离** | 文件元数据存放在**独立于文件数据的 RADOS 池**中 | | **MDS 集群** | 元数据由一组可扩缩的 **Metadata Server(MDS)** 提供服务,可随负载扩展 | | **客户端直连 RADOS** | 客户端**直接访问 RADOS** 读写文件数据块,工作负载得以随底层 RADOS 规模线性扩展 | | **无网关** | 没有任何网关或代理(broker)为客户端中转数据 I/O | | **分布式元数据缓存** | 对数据的访问由 MDS 集群协调,MDS 是"客户端与 MDS 共同维护的分布式元数据缓存"状态的权威 | | **元数据只落 RADOS** | 每个 MDS 把元数据变更聚合成一系列高效写入 RADOS 上 journal 的操作,**MDS 本地不持久化任何元数据状态** | 这一模型使得多个客户端可以在 POSIX 文件系统的语义下一致且快速地协作。 > 引自:[Ceph File System](https://docs.ceph.com/en/tentacle/cephfs/) ### 1.3 与 NFS / GlusterFS / Lustre 的横向对比 > 💡 补充:Ceph 官方文档没有提供 CephFS 与其他文件系统的直接对比表,本节由本册基于官方文档中关于 CephFS 架构与 POSIX 语义的陈述(`index`、`posix`)整理而成,用于选型参考。表中 CephFS 行的特性均有官方源文支撑;其他系统的描述属于通用的业界共识,未在本册源文范围内,请以此为参考而非权威依据。 | 维度 | **CephFS** | **NFS(v3/v4)** | **GlusterFS** | **Lustre** | |---|---|---|---|---| | 数据通路 | 客户端**直连 RADOS**,无网关(`index`) | 所有 I/O 经 NFS server 中转 | 客户端直连 brick(无元数据服务,靠弹性哈希) | 客户端直连 OST,经 MDS/MGS 做元数据 | | 元数据服务 | 独立 **MDS 集群**,可多活(`multimds`) | 无(由后端本地文件系统承担) | 无独立元数据服务 | 专用 MDS(历史上长期单活) | | 元数据持久化 | 只在 RADOS,MDS 本地无状态(`index`) | 依赖后端本地 FS | 分布在 brick 上 | 分布在 MDT 上 | | 多客户端一致性 | **强缓存一致性**(`posix` 明确称 "maintains strong cache coherency across clients") | 极弱,采用 close-to-open 语义 | 较弱,依赖自愈与锁 | 较强(有 DLM 锁机制) | | 写原子性 | 跨 object 边界的并发写可能被撕裂(`posix`) | 更差(数据可能到 close 才 flush) | 依 translator 而定 | 较好 | | POSIX 严格程度(官方排序) | `HDFS < NFS < CephFS < {XFS, ext4}`(`posix`) | — | — | — | | 扩容方式 | 加 OSD 扩容量、加 MDS 扩元数据吞吐 | 加 NFS server(需拆分命名空间) | 加 brick | 加 OST / MDT | | 快照 | 目录级 `.snap`,支持调度(`snapshots`/`snap-schedule`) | 依赖后端 FS | 卷级快照 | 依赖后端 | | 灾备镜像 | 内建 `cephfs-mirror` 快照异步复制(`cephfs-mirroring`) | 无内建 | geo-replication | 无内建 | | 典型场景 | 统一存储、云平台共享文件(OpenStack Manila / CSI) | 通用小文件共享 | 大文件顺序读写 | HPC 高带宽 | ### 1.4 适用场景与不适用场景 > 💡 补充:官方文档未直接给出"CephFS 适用场景清单",以下结论由本册依据 `index` 中列举的典型用途(shared home directories、HPC scratch space、distributed workflow shared storage)以及 `posix`、`app-best-practices` 中的限制说明归纳而成。 **适合** - 共享 home 目录、需要多客户端强一致缓存的场景(`posix` 明确说明 CephFS 相对 NFS 的优势正在于此)。 - HPC scratch space:需要高聚合带宽、数据可直接落 RADOS 的临时空间。 - 分布式工作流的共享存储(多机协同读写同一命名空间)。 - 云平台:OpenStack Manila shares、Kubernetes CSI volumes(由 `volumes` mgr 模块提供抽象,见 4.2)。 - 需要内建快照调度 + 跨集群快照镜像做灾备的场景。 **不适合 / 需谨慎** - **完全不可信客户端环境下的配额强约束**:CephFS 配额是"协作式、非对抗式"的(`quota`),被改造过的客户端可以绕过。 - **共享可写 mmap 的多机并发写**:跨主机的 `mmap` 写不会一致地传播到其他客户端缓存(`posix`)。 - **依赖 `atime` 做分级/备份决策**:CephFS 目前不维护 `atime` 字段(`posix`)。 - **海量小目录 + 频繁 `ls -l` / `stat`**:元数据开销集中在 MDS,见 `app-best-practices`。 - **单个超大目录**:需要靠目录分片(4.9)与多活 MDS(2.4)缓解。 - **依赖 `st_blocks` 精确统计**:稀疏文件的 `st_blocks` 传播不正确,`du(1)` 会高估占用(`posix`)。 ### 1.5 POSIX 兼容性说明 CephFS 的目标是尽可能遵循 POSIX 语义。与 NFS 等常见网络文件系统不同,**CephFS 在客户端之间维持强缓存一致性**——目标是让通过文件系统通信的进程,无论在同一主机还是不同主机上,行为都一致。 但在以下位置 CephFS 有意偏离了严格的 POSIX 语义: | 偏离点 | 具体行为 | |---|---| | **写失败不保证原子** | 客户端以 `O_SYNC` 打开一个文件、用 8MB buffer 调用 `write(2)` 后崩溃,写可能只被部分应用。(几乎所有文件系统,包括本地文件系统,都有此行为。) | | **跨对象边界的并发写不原子** | 写入者 A 写 `"aa|aa"`、写入者 B 同时写 `"bb|bb"`(`|` 为对象边界),结果可能是 `"aa|bb"` 而不是完整的 `"aa|aa"` 或 `"bb|bb"`。 | | **稀疏文件的 `st_blocks` 不正确** | CephFS 不显式追踪文件哪些部分被分配/写入,`st_blocks` 恒等于「文件大小 ÷ 块大小」,导致 `du(1)` 高估占用。(CephFS 维护的 recursive size 字段同样包含文件「空洞」。) | | **共享可写 mmap 不失效其他客户端缓存** | 文件在多个主机上通过 `mmap(2)` 映射后,写不会一致地传播到其他客户端的缓存:A 上缓存的 page 在 B 上被更新后,A 的 page 不会被一致地作废。(共享可写 mmap 非常罕见,官方称尚未收到相关抱怨。) | | **隐藏的 `.snap` 目录** | 客户端呈现一个隐藏的 `.snap` 目录用于访问/创建/删除/重命名快照。虽然该虚拟目录被 `readdir(2)` 排除,但任何试图创建同名文件或目录的进程会得到错误码。该名称可在挂载时用 `-o snapdirname=.somethingelse`(Linux 内核客户端)或配置项 `client_snapdir`(libcephfs、ceph-fuse)修改。 | | **不维护 `atime`** | CephFS 目前不维护 `atime` 字段。多数应用不在意,但这会影响某些依赖"长期未访问"来判断是否把数据迁移到二级存储的备份与分级应用。变通方法:CephFS 支持通过 `setattr` 操作设置 `atime`。 | #### 换个角度看 "POSIX 合规" 现实中大多数文件系统实现(包括 ext4、XFS 这类本地 Linux 文件系统)都不严格遵守 POSIX 规范。例如出于性能考虑,读的原子性要求被放宽:从一个正在被写的文件中读,可能看到撕裂的结果。 类似地,当多个客户端操作相同的文件或目录时,NFS 的一致性语义极弱,它选择的是 "close-to-open"。在网络附加存储的世界里(多数环境用 NFS),服务器侧文件系统是否"完全 POSIX"可能并不重要,客户端应用是否察觉取决于数据是否在客户端之间共享。NFS 也可能撕裂并发写者的结果——客户端数据可能直到文件关闭才刷到服务端。 很多其他存储系统(如 HDFS)自称 "POSIX-like",但因为砍掉了原地修改文件、truncate、目录 rename 等支持而与标准显著背离。 **结论:** ``` CephFS 比本地 Linux 内核文件系统(XFS/ext4)放松得更多(例如跨对象边界的写可能撕裂), 但在多客户端一致性上比 NFS 放松得更少,在写原子性上一般也比 NFS 少。 就 POSIX 而言:HDFS < NFS < CephFS < {XFS, ext4} ``` #### fsync() 与错误报告 POSIX 对 `fsync` 报错后 inode 处于什么状态描述得比较含糊。CephFS 通常使用客户端内核中的标准错误报告机制,因此遵循与其他文件系统相同的约定。 在较新的 Linux 内核(v4.17 及以后)中,回写错误**只向错误发生时处于打开状态的每个文件描述符报告一次**;此外,在文件描述符打开之前发生的、尚未报告过的错误,也会在 `fsync` 时返回。 > 引自:[Differences from POSIX](https://docs.ceph.com/en/tentacle/cephfs/posix/) ### 1.6 MDS 与分布式元数据缓存 #### 1.6.1 分布式元数据缓存(mdcache) CephFS 的元数据由 MDS 集群管理,MDS 之间是**分布式元数据缓存**协作关系: - 每个 MDS 把自己负责的元数据变更聚合成对 RADOS 上 journal 的高效写入,**本地不存任何元数据状态**(`index`)。 - 客户端会持有 MDS 授予的 **capabilities(caps)**,相当于锁,允许客户端在本地缓存并操作 inode(详见 3.2 节)。 - 当 MDS 需要收回某个客户端的 caps(例如另一个客户端需要访问)时,会向客户端发 revoke 消息;若客户端不响应,MDS 会把它 **blocklist(拉黑)** 并驱逐(详见 5.6 节)。 - 每个 inode 有且仅有一个 **auth MDS**(权威 MDS),其他 MDS 上可以有副本。 - 通过 **pinning**(包括静态 `ceph.dir.pin` 与 ephemeral pinning)可以把子树固定到指定 rank(详见 2.4 节)。 > 引自:[Distributed Metadata Cache](https://docs.ceph.com/en/tentacle/cephfs/mdcache/) #### 1.6.2 动态元数据管理(子树迁移) 当存在多个 active MDS 时,CephFS 通过**动态子树分区(dynamic subtree partitioning)**把元数据负载分散到各 rank。一次子树 export(迁移)的消息流程如下: | 阶段 | 消息 | 说明 | |---|---|---| | 1 | `MExportDiscover` / `MDiscoverAck` | 导入方探查待迁移子树的边界 | | 2 | `MExportWarning` | 导出方通知相关方即将迁移 | | 3 | `MExport` / `MExportAck` | 导出方把子树内容发给导入方 | | 4 | `EImportStart` | 导入方在 journal 中记录 import 开始 | | 5 | `EExport` | 导出方在 journal 中记录 export | | 6 | `MExportNotify` | 通知其他 MDS 权威变更 | | 7 | `MExportFinish` / `EImportFinish` | 双方各自完成收尾 | > 💡 补充:官方源文对这几个消息类型的介绍较为简略,上表的"阶段"划分是基于源文中列举的消息名与其英文语义(Discover/Warning/Export/ImportStart/Notify/Finish)整理的顺序,便于理解迁移流程。 > 引自:[Dynamic Metadata Management in CephFS](https://docs.ceph.com/en/tentacle/cephfs/dynamic-metadata-management/) ### 1.7 CephFS I/O 路径 CephFS 的 I/O 分成两条完全不同的通路: - **元数据 I/O**:客户端 → MDS(走 MDS 会话),MDS 把变更写入元数据池里的 journal(经 `journaler` 抽象层)。 - **数据 I/O**:客户端 → 直接对数据池中的 OSD 发 RADOS 操作(不经过 MDS)。客户端需要先向 MDS 拿到 layout 与 caps,之后的数据读写完全绕过 MDS。 这就是 CephFS 能做到"数据吞吐随 OSD 规模线性扩展"的根本原因:**没有网关或代理为客户端中转数据 I/O**。 > 引自:[CephFS IO Path](https://docs.ceph.com/en/tentacle/cephfs/cephfs-io-path/) --- ## 第二部分 部署与挂载 ### 2.1 创建文件系统(createfs) #### 2.1.1 池规划:这是最容易后悔的一步 CephFS 至少需要两个 RADOS 池:一个数据池、一个元数据池。规划时的关键考量: | 考量 | 官方建议 | |---|---| | 元数据池副本数 | **至少 3 副本**;该池丢数据会导致整个文件系统不可访问。配 4 副本也不算过分——元数据池容量需求本身很 modest | | 元数据池介质 | **尽可能快的低延迟设备**(NVMe、Optane,至少 SAS/SATA SSD),直接决定客户端文件系统操作的延迟 | | 元数据池独占 OSD | **强烈建议**把 CephFS 元数据池部署在**专用 SSD/NVMe OSD** 上,避免高客户端负载反过来拖慢元数据操作(用 device class 实现) | | 默认数据池的特殊性 | 传给 `fs new` 的数据池是"默认数据池",也是**所有 inode backtrace 信息的存放地**,backtrace 用于硬链接管理与灾难恢复。因此**所有 CephFS inode 在默认数据池中至少有一个对象**。若打算用 EC 池存文件数据,**默认数据池最好配成副本池**以提升 backtrace 小对象读写性能,再单独加一个 EC 数据池给目录树用(见 4.1) | | PG 数 | 元数据池通常最多只有几 GB 数据,建议用较小的 PG 数;大型集群常用 **64 或 128** | ```bash # 用默认设置创建两个池(示例) ceph osd pool create cephfs_data ceph osd pool create cephfs_metadata ``` > 注意:文件系统名、元数据池名、数据池名**只能**包含 `[a-zA-Z0-9_-.]` 字符集中的字符。 #### 2.1.2 `ceph fs new` 完整语法 ```bash ceph fs new [--force] [--allow-dangerous-metadata-overlay] [] [--recover] [--yes-i-really-really-mean-it] [...] ``` | 参数/选项 | 含义 | |---|---| | `` | 文件系统名 | | `` | 元数据池名 | | `` | **默认**数据池名,一旦设定**不可更改** | | `--force` | 用于三类操作之一:①把 EC 池设为默认数据池(不推荐);②元数据池非空(已有对象);③配合 `--fscid` 创建指定 fscid 的文件系统(`--fscid` 必须与 `--force` 同用) | | `--allow-dangerous-metadata-overlay` | 允许复用已在使用的元数据/数据池。仅在紧急情况下、且仔细阅读文档后使用 | | `--fscid ` | 用指定 fscid 创建文件系统。适用于希望文件系统 ID 在恢复后保持稳定的应用(例如 mon 数据库丢失重建后)。因此文件系统 ID 不一定随新文件系统单调递增 | | `--recover` | 把文件系统 rank 0 的状态设为"存在但 failed"。这样当 MDS 认领 rank 0 时,会读取 RADOS 中已有的元数据而不是覆盖它。该 flag 同时阻止 standby MDS 加入文件系统 | | `--yes-i-really-really-mean-it` | 某些 `fs set` 类操作也会用到此确认串 | | `...` | 允许在创建文件系统的同时**原子地**设置多个 `fs set` 支持的选项 | 示例(创建时顺带设 `max_mds` 与 `allow_standby_replay`): ```bash ceph fs new cephfs cephfs_metadata cephfs_data set max_mds 2 allow_standby_replay true ceph fs ls # name: cephfs, metadata pool: cephfs_metadata, data pools: [cephfs_data ] ``` 创建后 MDS 就能进入 *active* 状态: ```bash ceph mds stat # cephfs-1/1/1 up {0=a=up:active} ``` 如果创建了多个文件系统,客户端挂载时若不指定,默认选哪个文件系统可用 `ceph fs set-default` 控制。 #### 2.1.3 在 CephFS 中使用纠删码(EC)池 - EC 池**可以**作为 CephFS **数据池**,前提是开启 overwrites: ```bash ceph osd pool set my_ec_pool allow_ec_overwrites true ``` > 注意:EC overwrites 仅在使用 **BlueStore** 后端的 OSD 上支持。 - 若存储大量小文件或频繁修改文件,可开启 EC 优化提升性能: ```bash ceph osd pool set my_ec_pool allow_ec_optimizations true ``` - EC 池**不能**用作 CephFS **元数据池**:CephFS 元数据使用 RADOS **OMAP** 数据结构存储,而 EC 池无法存 OMAP。 > 引自:[Create a Ceph file system](https://docs.ceph.com/en/tentacle/cephfs/createfs/) ### 2.2 多个文件系统(multifs) 默认情况下一个 Ceph 集群只允许一个 CephFS。启用多文件系统: ```bash ceph fs flag set enable_multiple true ``` 然后可以用 `ceph fs volume create` 创建额外的文件系统。 **关键约束:** - 多个文件系统之间**不共享池**(各有自己的元数据池与数据池)。 - 每个文件系统有**各自独立的 MDS rank 集合**。 - 每个文件系统需要独立的 standby 守护进程来保障高可用。 > 引自:[Creating Multiple File Systems](https://docs.ceph.com/en/tentacle/cephfs/multifs/) ### 2.3 部署、增加、移除 MDS(add-remove-mds) #### 2.3.1 硬件建议 | 项目 | 建议 | |---|---| | CPU | MDS 是**单线程**的,2~3 核足够 | | 内存 | 缓存按 4GB 配置时,建议 **8GB RAM**;大型集群可能需要 **64GB** | | 磁盘 | MDS 本地不持久化元数据,本地盘需求小,但 journal 与 cache 相关内存开销大 | #### 2.3.2 手工部署一个 MDS ```bash # 创建 MDS 的 cephx 身份 ceph auth get-or-create mds.${id} mon 'profile mds' mgr 'profile mds' mds 'allow *' osd 'allow *' > /var/lib/ceph/mds/ceph-${id}/keyring # 启动 systemctl start ceph-mds@${id} ``` #### 2.3.3 MDS 文件系统亲和性 用配置项 `mds_join_fs` 让某个 MDS 偏好加入指定文件系统(详见 2.5.3): ```bash ceph config set mds.b mds_join_fs cephfs ``` #### 2.3.4 移除 MDS 移除 MDS 前,官方文档提示需要留意两个健康告警作为"确认标志": - `MDS_TRIM`:journal 修剪落后(见 5.4) - `MDS_CACHE_OVERSIZED`:MDS 缓存超限(见 5.4) 在移除过程中若出现这两个告警,说明 MDS 还在回写/trim 元数据,需等待或排查,不要直接停进程。 > 引自:[Provision/Add/Remove MDS(s)](https://docs.ceph.com/en/tentacle/cephfs/add-remove-mds/) ### 2.4 配置多个 active MDS(multimds / multi-mds / active-active MDS) #### 2.4.1 什么时候需要多活 MDS 默认情况下每个 CephFS 只有**一个** active MDS。当元数据性能瓶颈在这一个 MDS 上时,才应该启用多活。 **注意**:加守护进程不一定提升所有负载的性能。典型地,**单个客户端上跑的单个应用**不会从增加 MDS 数量中获益,除非该应用在大量并行地做元数据操作。**受益的负载**通常是:客户端数量很多、且可能各自在不同的目录中工作。 #### 2.4.2 扩容:增大 max_mds 每个 CephFS 有 `max_mds` 设置,控制会创建多少个 rank。**只有在有空闲守护进程可以接手新 rank 时,rank 数才会真正增加**。例如只有一个 MDS 运行时把 `max_mds` 设为 2,并不会创建第二个 rank。(这种配置不是高可用的,因为没有 standby 接管失败的 rank,集群会通过健康告警提示。) ```bash # 扩容前:fsmap e5: 1/1/1 up {0=a=up:active}, 2 up:standby ceph fs set max_mds 2 # fsmap e8: 2/2/2 up {0=a=up:active,1=c=up:creating}, 1 up:standby # fsmap e9: 2/2/2 up {0=a=up:active,1=c=up:active}, 1 up:standby ``` 新创建的 rank(1) 会先经过 `creating` 状态,再进入 `active`。 #### 2.4.3 standby 与 max_mds 的关系 即使开了多活,**高可用依然需要 standby 守护进程**。因此高可用系统里 `max_mds` 的实际上限**最多比 MDS 服务器总数少 1**。想扛住 N 台服务器故障,就要把 standby 数量加到 N。 #### 2.4.4 缩容:减小 max_mds ```bash # fsmap e9: 2/2/2 up {0=a=up:active,1=c=up:active}, 1 up:standby ceph fs set max_mds 1 # fsmap e10: 2/2/1 up {0=a=up:active,1=c=up:stopping}, 1 up:standby # ... # fsmap e10: 1/1/1 up {0=a=up:active}, 2 up:standby ``` - 集群会自动增量停止多余的 rank,直到达到 `max_mds`。 - 被停止的 rank 会先进入 `stopping` 状态一段时间,把它负责的元数据交接给其余 active 守护进程。**这个阶段可能持续几秒到几分钟**。如果 MDS 看起来卡在 `stopping`,应当作为潜在 bug 排查。 - 若 MDS 在 `up:stopping` 期间崩溃或被杀,standby 会接管,mon 会再次尝试停止它。 - 停止完成后,该守护进程会自我重启并回到 standby。 #### 2.4.5 静态 pin:把目录树固定到某个 rank 在多 active MDS 配置中,默认有一个 balancer 会把元数据负载均摊。但有时需要用显式映射覆盖动态 balancer。这个机制叫 **export pin**,是一个目录扩展属性,名为 `ceph.dir.pin`: ```bash setfattr -n ceph.dir.pin -v 2 path/to/dir ``` | 规则 | 说明 | |---|---| | 取值 | 要把该目录子树分配到的 **rank**。**默认值 `-1` 表示该目录未被 pin** | | 继承 | 一个目录的 export pin 继承自**最近的、设置了 export pin 的父目录**。因此在目录上设置 pin 会影响其所有子目录 | | 覆盖 | 子目录可以显式设置 pin 来**覆盖**父目录的 pin | ```bash mkdir -p a/b # "a" 和 "a/b" 都没有 export pin setfattr -n ceph.dir.pin -v 1 a/ # a 和 a/b 现在都 pin 到 rank 1 setfattr -n ceph.dir.pin -v 0 a/b # a/b 现在 pin 到 rank 0;a/ 及其余子目录仍 pin 到 rank 1 ``` #### 2.4.6 Ephemeral pinning(自动静态分区策略) CephFS 还支持通过**策略**做**自动**静态分区,称为 **ephemeral pinning**。被 ephemerally pin 的目录会依据其 **inode 号的 consistent hash** 自动分配到某个 rank;全部 ephemerally pinned 目录应当均匀分布在所有 rank 上。 之所以叫 "ephemeral"(短暂),是因为**当目录 inode 从缓存中被丢弃后 pin 可能不再保留**;但 **MDS failover 不影响**这一点——MDS 会把"哪些子树被 ephemerally pinned"记录在自己的 journal 中,因此 failover 不会丢掉这些信息。 consistent hash 的好处:MDS 集群扩缩容时重分布最小。**所以扩大 MDS 集群可能自动提升你的元数据吞吐,无需额外管理动作。** 目前有两种 ephemeral pinning: | 策略 | xattr | 取值含义 | 典型用例 | |---|---|---|---| | **Distributed** | `ceph.dir.pin.distributed` | `1` 开启:使目录发生分片(**远低于正常分片阈值也会分**),并把各分片作为 ephemerally pinned 子树分布出去 | `/home`:希望每个用户的 home 目录散布在整个 MDS 集群上 | | **Random** | `ceph.dir.pin.random` | 子目录被 pin 的**百分比** | `0.5` 表示 `/tmp` 下任何载入缓存或新建的目录有 50% 概率被 ephemerally pin | ```bash # 分布式:让 /cephfs/home 下的直接子项分散到各 rank setfattr -n ceph.dir.pin.distributed -v 1 /cephfs/home # 随机:/cephfs/tmp 下有 50% 的目录被 pin setfattr -n ceph.dir.pin.random -v 0.5 /cephfs/tmp ``` 官方**建议只设很小的值**,如 `.001`(0.1%)。子树太多反而降低性能。配置项 `mds_export_ephemeral_random_max` 对这个百分比设了上限(默认 `.01`),试图设置超过该值时 MDS 返回 `EINVAL`。 `mds_export_ephemeral_random` 与 `mds_export_ephemeral_distributed` 两个配置项用于开关这两种策略(Octopus 起默认关闭)。 **冲突解决规则——最近父目录优先**(closest parent wins):ephemeral pin 可以覆盖父的 export pin,反之亦然。 ```bash mkdir -p foo/bar1/baz foo/bar2 setfattr -n ceph.dir.pin -v 0 foo setfattr -n ceph.dir.pin.distributed -v 1 foo/bar1 # → foo/bar1/baz 会被 ephemerally pin(因为 foo/bar1 的策略覆盖了 foo 的 export pin) # → foo/bar2 遵循 foo 的 pin(pin 到 rank 0) mkdir -p home/{patrick,john} setfattr -n ceph.dir.pin.distributed -v 1 home setfattr -n ceph.dir.pin -v 2 home/patrick # → home/patrick 及其子目录 pin 到 rank 2(因为它的 export pin 覆盖了 home 上的策略) ``` **移除策略**:删除相应 xattr 或把值设为 0。 ```bash setfattr -n ceph.dir.pin.distributed -v 0 home # 或 setfattr -x ceph.dir.pin.distributed home # 对 export pin:删除 xattr 或把值设为 -1 setfattr -n ceph.dir.pin -v -1 home ``` #### 2.4.7 动态子树分区(balancer) CephFS 长期提供一个动态元数据 balancer(有时称 "default balancer"),可以拆分或合并子树并放置到"较冷"的 MDS rank 上,从而提升整体吞吐与缓存容量。 但 balancer 有时效率低或反应慢,因此**默认是关闭的**——目的是避免管理员仅仅调大 `max_mds` 开启 multimds 后,发现 balancer 把集群性能搞乱(从这种混乱状态回滚虽然可行但要花时间)。 **开启 balancer:** ```bash ceph fs set balance_automate true ``` > 官方建议:只在配合了合适配置(例如 `bal_rank_mask`)的情况下开启,并密切监控文件系统与 MDS 性能。 #### 2.4.8 只在特定 rank 上跑 balancer(`bal_rank_mask`) `bal_rank_mask` 让 balancer 在**指定的 active MDS rank 集合内**动态再平衡子树,从而同时混用"动态子树分区"与"静态 pinning"。典型场景:云环境中大量 subvolume 分配给多个计算节点,少数高性能 subvolume 用静态分区管理,多数中等负载的 subvolume 交给 balancer。 ```bash ceph fs set bal_rank_mask ``` | `` 取值 | 含义 | |---|---| | `0x3` | balancer 只在 rank 0 和 rank 1 上运行(每个 bit 代表一个 rank) | | `-1` 或 `all` | 所有 active rank 都被 mask,即全部参与 balancer | | `0x0` | 关闭 balancer | ```bash ceph fs set bal_rank_mask 0x3 ceph fs set bal_rank_mask -1 ceph fs set bal_rank_mask 0x0 ``` > 引自:[Configuring multiple active MDS daemons](https://docs.ceph.com/en/tentacle/cephfs/multimds/) ### 2.5 MDS failover 与 standby 配置(standby) #### 2.5.1 术语 | 术语 | 含义 | |---|---| | **CephFS file system** | 一个集群可有零个或多个 CephFS。每个 CephFS 有一个人类可读的 name(`fs new` 时设置)和一个整数 ID,ID 叫 **FSCID**(file system cluster ID) | | **rank** | 每个 CephFS 有若干 rank,从 0 开始编号。默认每个文件系统 1 个 rank。rank 可理解为一个**元数据分片** | | **daemon 与 rank** | 每个 `ceph-mds` 守护进程启动时**没有** rank,由 mon 分配。一个守护进程同时只能持有一个 rank,且只有 `ceph-mds` 进程停止时才会放弃 rank | | **failed rank** | 若某个 rank 没有关联任何守护进程,该 rank 视为 `failed`。一旦 rank 被分配给某个守护进程,就视为 `up` | | **name** | 每个守护进程的 name 由管理员在首次配置时静态指定,通常就是运行该进程的主机名 | | **GID** | `ceph-mds` 启动时还会被分配一个整数 GID,对**当前进程**唯一。也就是说守护进程重启后是新进程,会拿到**新的、与之前不同**的 GID | #### 2.5.2 引用 MDS 守护进程的几种写法 多数管理命令接受灵活的格式:`rank`、`GID` 或 `name`。`rank` 前可加文件系统 name 或 FSCID 限定。若守护进程是 standby(当前没有 rank),则**只能用 GID 或 name 引用**。 假设有守护进程 name = `myhost`、GID = 5446,被分配给文件系统 `myfs`(FSCID 3)的 rank 0,则以下命令等价: ```bash ceph mds fail 5446 # GID ceph mds fail myhost # Daemon name ceph mds fail 0 # Unqualified rank ceph mds fail 3:0 # FSCID and rank ceph mds fail myfs:0 # File System name and rank ``` #### 2.5.3 管理 failover - 若 MDS 守护进程停止与 mon 通信,mon 会等待 `mds_beacon_grace` 秒(默认 15)后把该守护进程标记为 **laggy**。若有 standby MDS 可用,mon 会**立即**替换 laggy 守护进程。 - 每个文件系统可以指定一个**最小 standby 守护进程数**,用于健康判定: ```bash ceph fs set standby_count_wanted ``` 这个计数**包含**处于 `standby-replay` 状态、等待某个 rank 失败的守护进程。(注意:mon 不会派 `standby-replay` 守护进程去接管另一个 rank 的失败,也不会接管另一个 CephFS 的失败。)**不在 replay 的 standby 池计入任何文件系统的计数**。把 `count` 设为 0 即禁用该健康检查。 #### 2.5.4 配置 standby-replay `standby-replay` 守护进程会**跟随 active MDS 的元数据 journal**,从而在 active MDS 不可用时**缩短 failover 时间**。**每个 active MDS 只能有一个 `standby-replay` 守护进程跟随它。** ```bash ceph fs set allow_standby_replay ``` 设置后,mon 会分配可用的 standby 守护进程去跟随该文件系统中的 active MDS。 **重要限制**:一旦 MDS 进入 `standby-replay` 状态,它**只能作为它所跟随的那个 rank 的 standby**。如果另一个 rank 失败,这个 `standby-replay` 守护进程**不会**被用作替补,**即使没有其他 standby 可用**。因此官方建议:如果使用 `standby-replay`,那么**每一个 active MDS 都应该配一个 `standby-replay` 守护进程**。 #### 2.5.5 与 standby 的区别小结(`standby` vs `standby-replay`) | 维度 | `up:standby` | `up:standby_replay` | |---|---|---| | 是否读 journal | 否 | **是**,持续重放 active MDS 的 journal | | failover 速度 | 需要 replay 完整 journal,慢 | 已预热,**切换更快** | | 能否接管任意 rank | **能**,mon 可分配给任意失败 rank | **不能**,只能接管它所跟随的那个 rank | | 是否计入 `standby_count_wanted` | 计入 | 计入 | | 数量限制 | 无 | 每个 active MDS 最多 1 个 | #### 2.5.6 配置 MDS 文件系统亲和性(`mds_join_fs`) 配置项 `mds_join_fs` 用于强制 MDS 对某个文件系统的亲和性。failover 时 mon 的选取优先级: 1. `mds_join_fs` **等于**失败 rank 所在文件系统 name 的 standby; 2. 没有设置 `mds_join_fs` 的**无限制 standby**; 3. 最后才考虑"属于另一个文件系统的 standby"——该行为可以禁用: ```bash ceph fs set refuse_standby_for_another_fs true ``` 其他要点: - 配置亲和性**不改变**"`standby-replay` 守护进程总是优先于其他 standby 被选中"这一行为。 - 即使系统稳定,mon 也会定期检查是否有亲和性更强的 standby 可替换亲和性较低的 MDS;`standby-replay` 守护进程同样适用此规则(若某个普通 standby 亲和性更强,会替换掉 `standby-replay` MDS)。 - 若文件系统处于 degraded 或 undersized 状态,**不会**为了强制 `mds_join_fs` 而发生 failover。 示例(`'cephfs'` FSCID 为 27,当前 `mds.a` 是 rank 0,`mds.b` 是 standby): ```bash $ ceph fs dump ... [mds.a{0:20384} state up:active seq 239 ...] Standby daemons: [mds.b{-1:10420} state up:standby seq 2 ...] $ ceph config set mds.b mds_join_fs cephfs # 自动 failover 后: $ ceph fs dump ... [mds.b{0:10420} state up:active seq 274 join_fscid=27 ...] Standby daemons: [mds.a{-1:10720} state up:standby seq 2 ...] ``` 注意 `mds.b` 现在带 `join_fscid=27`:输出中 `mds_join_fs` 的文件系统**名**被换成了文件系统**标识符**(27)。如果用同名重建文件系统,standby 会如预期跟随新的文件系统。 > 引自:[MDS failover and standby configuration](https://docs.ceph.com/en/tentacle/cephfs/standby/) ### 2.6 挂载前置条件(mount-prerequisites) 挂载 CephFS(无论内核还是 FUSE)之前,客户端需要: 1. **一份最小化的 ceph 配置文件**与 **keyring**。生成最小配置: ```bash ceph config generate-minimal-conf > /etc/ceph/ceph.conf chmod 644 /etc/ceph/ceph.conf ``` 2. **一个有 CephFS 访问权限的 cephx 用户**: ```bash ceph fs authorize cephfs client.foo / rw ``` 3. **keyring 权限收紧**: ```bash chmod 600 /etc/ceph/ceph.client.foo.keyring ``` > 引自:[Mount CephFS: Prerequisites](https://docs.ceph.com/en/tentacle/cephfs/mount-prerequisites/) ### 2.7 使用内核驱动挂载 CephFS 内核驱动是 Linux 内核的一部分,可以以原生内核性能把 CephFS 挂成常规文件系统。**它是大多数用例的首选客户端。** > CephFS 挂载设备串现在使用新语法("v2")。mount helper 向后兼容旧语法,内核也向后兼容旧语法——也就是说用更新的 mount helper 和内核时,旧语法依然可用。 #### 2.7.1 前置检查 ```bash # mount.ceph helper 是否存在(由 Ceph 包安装) stat /sbin/mount.ceph ``` 若客户端机器上没有 helper,仍然可以用内核驱动挂载,但必须**显式**向 `mount` 命令传 monitor 地址和 keyring 信息。 **内核版本**:内核客户端随 Linux 内核发布(而非随 Ceph 发布包),因此要考虑客户端节点用哪个内核。粗略准则:从 Ceph 10.x (Jewel) 起,**至少用 4.x 内核**。如果不得不用更老的内核,应当改用 fuse 客户端。(若所用发行版自带 CephFS 支持,则由发行商负责向稳定内核 backport 修复,请咨询供应商。) #### 2.7.2 通用语法 ```bash mount -t ceph {device-string}={path-to-mounted} {mount-point} -o {key-value-args} {other-args} ``` #### 2.7.3 挂载示例(v2 语法) ```bash mkdir /mnt/mycephfs mount -t ceph @.=/ /mnt/mycephfs ``` - `name`:用于挂载的 CephX 用户名。 - `fsid`:集群 FSID,可用 `ceph fsid` 查;`fs_name`:要挂载的文件系统。 - 内核驱动需要 monitor 地址与 CephX 用户的 secret key: ```bash mount -t ceph cephuser@b3acfc0d-575f-41d3-9c91-0e7ed3dbb3fa.cephfs=/ -o mon_addr=192.168.0.1:6789,secret=AQATSKdNGBnwLhAAnNDKnH65FmVKpXZJVasUeQ== ``` **使用 mount helper 时,monitor 主机和 FSID 是可选的**——helper 会通过查找并读取 ceph conf 来发现这些信息: ```bash mount -t ceph cephuser@.cephfs=/ -o secret=AQATSKdNGBnwLhAAnNDKnH65FmVKpXZJVasUeQ== ``` > 注意:`cephuser@.cephfs` 中的点(`.`)**必须**是设备串的一部分。 上面这种写法会把 secret 留在 shell 命令历史里。改用文件传密钥: ```bash mount -t ceph cephuser@.cephfs=/ /mnt/mycephfs -o secretfile=/etc/ceph/cephuser.secret ``` 密钥文件权限建议设为 `600`。 **多个 monitor 地址用 `/` 分隔:** ```bash mount -t ceph cephuser@.cephfs=/ /mnt/mycephfs -o mon_addr=192.168.0.1:6789/192.168.0.2:6789,secretfile=/etc/ceph/cephuser.secret ``` **CephX 关闭时**省略所有凭据相关选项: ```bash mount -t ceph cephuser@.cephfs=/ /mnt/mycephfs ``` > 注意:**Ceph 用户名必须作为设备串的一部分传入**。 **挂载子树**:把路径追加到设备串末尾: ```bash mount -t ceph cephuser@.cephfs=/subvolume/dir1/dir2 /mnt/mycephfs -o secretfile=/etc/ceph/cephuser.secret ``` #### 2.7.4 旧语法(向后兼容) ```bash mkdir /mnt/mycephfs mount -t ceph :/ /mnt/mycephfs -o name=admin ``` 挂载非默认文件系统(例如 `cephfs2`): ```bash mount -t ceph :/ /mnt/mycephfs -o name=admin,fs=cephfs2 # 或 mount -t ceph :/ /mnt/mycephfs -o name=admin,mds_namespace=cephfs2 ``` > 注意:`mds_namespace` 选项**已废弃**,旧语法下请改用 `fs=`。 #### 2.7.5 卸载 ```bash umount /mnt/mycephfs ``` > 提示:执行前确保你**不在**该文件系统目录中。 #### 2.7.6 持久挂载(/etc/fstab) ``` {name}@.{fs_name}=/ {mount}/{mountpoint} ceph [mon_addr={ipaddress},secret=secretkey|secretfile=/path/to/secretfile],[{mount.options}] {fs_freq} {fs_passno} ``` 示例: ``` cephuser@.cephfs=/ /mnt/ceph ceph mon_addr=192.168.0.1:6789,noatime,_netdev 0 0 ``` 若未指定 `secret` 或 `secretfile`,mount helper 会尝试在已配置的 keyring 中为给定 `name` 寻找 secret。 > 引自:[Mount CephFS using Kernel Driver](https://docs.ceph.com/en/tentacle/cephfs/mount-using-kernel-driver/) ### 2.8 使用 FUSE 挂载 `ceph-fuse` 是内核驱动的替代方案,挂载发生在**用户态**。因此 `ceph-fuse` 挂载**性能低于**内核驱动,但**更容易管理、更容易升级**。 > 注意:用 FUSE 挂载 CephFS **需要超级用户权限**(sudo/root)。libfuse 接口没有提供裁剪内核中缓存项的机制,因此需要一个 remount(`mount(2)`)系统调用来强制内核丢弃缓存的元数据;`ceph-fuse` 会周期性发出这些 remount 调用,以响应 MDS 的缓存压力或元数据缓存回收。 #### 2.8.1 语法与示例 ```bash ceph-fuse {mount point} {options} ``` ```bash mkdir /mnt/mycephfs ceph-fuse --id foo /mnt/mycephfs ``` | 选项 | 作用 | |---|---| | `--id foo` / `-n client.foo` | 指定使用哪个 CephX 用户的 keyring | | `-k /path/to/keyring` | keyring 不在标准位置时指定路径 | | `-m 192.168.0.1:6789` | 命令行传 monitor 地址(非必需) | | `-r /path/to/dir` | 挂载 CephFS 中的某个目录而非根 | | `--client_fs mycephfs2` | 挂载非默认文件系统(集群有多个 FS 时用) | ```bash ceph-fuse --id foo -k /path/to/keyring /mnt/mycephfs ceph-fuse --id foo -m 192.168.0.1:6789 /mnt/mycephfs ceph-fuse --id foo -r /path/to/dir /mnt/mycephfs ceph-fuse --id foo --client_fs mycephfs2 /mnt/mycephfs2 ``` 也可以在 `ceph.conf` 中设置 `client_fs`。向后兼容还支持 `--client_mds_namespace` 选项。 #### 2.8.2 卸载 ```bash umount /mnt/mycephfs ``` > 提示:确保没有 shell 或其他进程在该文件系统下打开着文件,**包括 shell 的当前工作目录**。 #### 2.8.3 持久挂载 `/etc/fstab`: ``` #DEVICE PATH TYPE OPTIONS none /mnt/mycephfs fuse.ceph ceph.id={user-ID}[,ceph.conf={path/to/conf.conf}],_netdev,defaults 0 0 ``` 示例: ``` none /mnt/mycephfs fuse.ceph ceph.id=myuser,_netdev,defaults 0 0 none /mnt/mycephfs fuse.ceph ceph.id=myuser,ceph.conf=/etc/ceph/foo.conf,_netdev,defaults 0 0 ``` > 请确保使用 **ID**(例如 `myuser`,而不是 `client.myuser`)。任何合法的 `ceph-fuse` 选项都可以通过这种方式传入。 挂载子目录: ``` none /mnt/mycephfs fuse.ceph ceph.id=myuser,ceph.client_mountpoint=/path/to/dir,_netdev,defaults 0 0 ``` `ceph-fuse@.service` 与 `ceph-fuse.target` systemd unit 是可用的。写好上面的 fstab 条目后执行: ```bash systemctl start ceph-fuse@/mnt/mycephfs.service systemctl enable ceph-fuse.target systemctl enable ceph-fuse@-mnt-mycephfs.service ``` > 引自:[Mount CephFS using FUSE](https://docs.ceph.com/en/tentacle/cephfs/mount-using-fuse/) ### 2.9 内核驱动支持的特性(kernel-features) | 内核特性 | 最低内核版本 | 说明 | |---|---|---| | inline data | ≥ 3.19 | 小文件数据内联到 inode | | quota | ≥ 4.17 | CephFS 配额支持 | | 多个文件系统(multi-FS) | ≥ 4.7 | 挂载非默认 FS | | 多活 MDS(multi-mds) | ≥ 4.14 | 多 active MDS 客户端支持 | | snapshot | ≥ 4.17 | `.snap` 目录快照 | > 💡 补充:官方 `kernel-features` 页面以表格形式列出上述特性与内核版本对应关系,但未对每个特性做更多展开。 > 引自:[Supported Features of Kernel Driver](https://docs.ceph.com/en/tentacle/cephfs/kernel-features/) ### 2.10 客户端配置项参考(client-config-ref)逐项 下表逐项列出 `client-config-ref` 页面中出现的**全部**客户端配置项。类型与默认值取自 `src/common/options/*.yaml.in` @ v20.2.4。 #### 2.10.1 常规选项(28 项) | 配置项 | 类型 | 默认值 | 含义 | |---|---|---|---| | `client_acl_type` | `str` | (空) | 要强制执行的 ACL 类型(`none` 或 `"posix_acl"`) | | `client_cache_mid` | `float` | `0.75` | 客户端缓存 LRU 的中点 | | `client_cache_size` | `size` | `16_K` | 客户端缓存中目录项的软上限数量 | | `client_caps_release_delay` | `secs` | `5` | 释放 capability 之间的延迟秒数:客户端等待这么久才释放不再需要的 caps,以防随后的用户态操作马上又需要它们 | | `client_debug_force_sync_read` | `bool` | `false` | 若为 `true`,客户端直接从 OSD 读数据而不使用本地 page cache | | `client_dirsize_rbytes` | `bool` | `true` | 把目录大小设为递归使用的文件字节数 | | `client_max_inline_size` | `size` | `4_K` | 内联到文件 inode(而非 RADOS 中独立数据对象)的数据的最大大小。仅当 MDS map 上设置了 `inline_data` flag 时生效 | | `client_metadata` | `str` | (空) | 以逗号分隔的 `key=value` 元数据对,附加到 session metadata 上 | | `client_mount_gid` | `int` | `-1` | 挂载时使用的 gid | | `client_mount_timeout` | `secs` | `5_min` | 挂载 CephFS 的超时(秒) | | `client_mount_uid` | `int` | `-1` | 挂载时使用的 uid | | `client_mountpoint` | `str` | `/` | 默认挂载点 | | `client_oc` | `bool` | `true` | 启用对象缓存 | | `client_oc_max_dirty` | `size` | `100_M` | 对象缓存中脏页的最大大小 | | `client_oc_max_dirty_age` | `float` | `5` | 对象缓存中脏页的最大存活时间(秒) | | `client_oc_max_objects` | `int` | `1000` | 缓存中对象的最大数量 | | `client_oc_size` | `size` | `200_M` | 对象缓存的最大大小 | | `client_oc_target_dirty` | `size` | `8_M` | 对象缓存中脏页的目标大小 | | `client_permissions` | `bool` | `true` | 客户端侧强制权限检查 | | `client_quota_df` | `bool` | `true` | `statfs`(`df`)时显示配额用量 | | `client_readahead_max_bytes` | `size` | `0` | 单文件预读的最大字节数(0 表示无限制) | | `client_readahead_max_periods` | `int` | `4` | 单文件预读的最大 stripe period 数 | | `client_readahead_min` | `size` | `128_K` | 单文件预读的最小字节数 | | `client_reconnect_stale` | `bool` | `false` | session 变 stale 时重新连接 | | `client_snapdir` | `str` | `.snap` | 用于访问目录快照的伪目录名 | | `client_tick_interval` | `secs` | `1` | 客户端维护 tick 之间的秒数 | | `client_use_random_mds` | `bool` | `false` | 向随机的 active MDS 发新请求 | | `fuse_default_permissions` | `bool` | `false` | 挂载时把 `default_permisions` 传给 FUSE | | `fuse_max_write` | `size` | `0` | 单次写操作的最大字节数 | | `fuse_disable_pagecache` | `bool` | `false` | 对该 FUSE 挂载禁用内核 page cache | > 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4 #### 2.10.2 Developer 选项(5 项) | 配置项 | 类型 | 默认值 | 含义 | |---|---|---|---| | `client_debug_getattr_caps` | `bool` | `false` | 文档未明确(developer 选项,源文与 yaml 中均无描述) | | `client_debug_inject_tick_delay` | `secs` | `0` | 文档未明确(developer 选项,源文与 yaml 中均无描述) | | `client_inject_fixed_oldest_tid` | `bool` | `false` | 文档未明确(developer 选项,源文与 yaml 中均无描述) | | `client_inject_release_failure` | `bool` | `false` | 文档未明确(developer 选项,源文与 yaml 中均无描述) | | `client_trace` | `str` | (空) | 保存客户端操作 trace 的文件 | > 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4 > 💡 补充:`client_trace` 的 yaml 描述为 "file containing trace of client operations";其余 4 项在 `mds-client.yaml.in` 中只有 `type` 与 `default`,没有 `desc`/`long_desc`/`fmt_desc`,官方渲染页面同样只显示选项名与默认值。 > 引自:[Client Configuration Settings](https://docs.ceph.com/en/tentacle/cephfs/client-config-ref/) --- ## 第三部分 权限与配额 ### 3.1 CephFS 客户端能力(client-auth) Ceph 的认证能力(authentication capabilities)用于把 CephFS 客户端限制在**必要的最小权限**上。 > 注意:path 限制与 layout 修改限制是在 Ceph 的 Jewel 版本引入的。 > 注意:CephFS 使用纠删码(EC)池**仅**在使用 BlueStore 时受支持。纠删码池**不能**用作元数据池。用作数据池的纠删码池**必须**开启 overwrites。 #### 3.1.1 路径限制(Path restriction) 默认情况下客户端**不限制**可挂载的路径。客户端挂载子目录(如 `/home/user`)时,MDS **默认不校验**后续操作是否"锁定"在该目录内。 要限制客户端只能挂载并在某个目录内工作,用**基于路径的 MDS 认证能力**。 **重要**:该限制**只**影响文件系统层次(即 MDS 管理的元数据树)。**客户端依然可以直接访问 RADOS 中的底层文件数据**。要完全隔离客户端,需把不可信客户端隔离到各自的 RADOS namespace 中:可以用 file layout 把客户端的文件系统子树放到特定 namespace(见 4.1),再用 OSD capabilities 把它们的 RADOS 访问限制在该 namespace。 **授权语法:** ```bash ceph fs authorize client. rw ``` 示例:限制客户端 `foo` 只能在文件系统 `cephfs_a` 的 `bar` 目录中写: ```bash ceph fs authorize cephfs_a client.foo / r /bar rw ``` 结果: ```text client.foo key: *key* caps: [mds] allow r, allow rw path=/bar caps: [mon] allow r caps: [osd] allow rw tag cephfs data=cephfs_a ``` 要**完全**限制在 `bar` 目录,省略根目录: ```bash ceph fs authorize cephfs_a client.foo /bar rw ``` 若客户端的读权限被限制在某个路径,客户端**只能**在挂载命令中指定一个可读路径来挂载文件系统。 用 `all` 或 `*` 作为文件系统名表示授权访问**每一个**文件系统。通常需要对 `*` 加引号以避免被 shell 展开。 **挂载侧限制子目录:** ```bash ceph-fuse -n client. -r *directory_to_be_mounted* # 例如:把 client.foo 限制在 mnt/bar ceph-fuse -n client.foo mnt -r /bar ``` #### 3.1.2 报告剩余空间 当客户端挂载了子目录时,已用空间(`df`)是根据**该子目录上的配额**计算的,而不是整个 CephFS 文件系统的用量。 要让客户端报告文件系统的整体用量(而非挂载子目录上的配额用量),在客户端设置: ```ini client quota df = false ``` 如果配额没启用,或者挂载的子目录上没有配额,那么无论该设置取何值,都会报告文件系统的整体用量。 #### 3.1.3 Layout 与 Quota 限制(`p` flag) 要设置 layout 或 quota,客户端除 `rw` 外还需要 **`p` flag**。把 `p` 与 `rw` 一起使用,会限制所有通过带 `ceph.` 前缀的特殊扩展属性设置的属性,以及设置这些字段的其他途径(例如带 layout 的 `openc` 操作)。 下面例子中 `client.0` 可以修改文件系统 `cephfs_a` 上的 layout 与 quota,`client.1` 则不行: ```text client.0 key: AQAz7EVWygILFRAAdIcuJ12opU/JKyfFmxhuaw== caps: [mds] allow rwp caps: [mon] allow r caps: [osd] allow rw tag cephfs data=cephfs_a client.1 key: AQAz7EVWygILFRAAdIcuJ12opU/JKyfFmxhuaw== caps: [mds] allow rw caps: [mon] allow r caps: [osd] allow rw tag cephfs data=cephfs_a ``` #### 3.1.4 快照限制(`s` flag) 要创建或删除快照,客户端除 `rw` 外还需要 **`s` flag**。注意:当能力串中同时包含 `p` flag 时,**`s` 必须出现在 `p` 之后**(除 `rw` 外的所有 flag 必须按**字母顺序**指定)。 ```text client.0 key: AQAz7EVWygILFRAAdIcuJ12opU/JKyfFmxhuaw== caps: [mds] allow rw, allow rws path=/bar caps: [mon] allow r caps: [osd] allow rw tag cephfs data=cephfs_a ``` #### 3.1.5 网络限制(Network restriction) ```text client.foo key: *key* caps: [mds] allow r network 10.0.0.0/8, allow rw path=/bar network 10.0.0.0/8 caps: [mon] allow r network 10.0.0.0/8 caps: [osd] allow rw tag cephfs data=cephfs_a network 10.0.0.0/8 ``` 可选的 `{network/prefix}` 是标准的 CIDR 记法(例如 `10.3.0.0/16`)。若存在该字段,则该能力的使用被限制在**从该网络连接的客户端**。 #### 3.1.6 文件系统信息限制(多 FS 视图隔离) mon 集群可以只呈现可用文件系统的**有限视图**:此时 mon 只告知管理员指定的文件系统,其他文件系统不会被上报,针对它们的命令会失败,就像这些文件系统不存在一样。 假设集群有 2 个文件系统: ```bash $ ceph fs ls # name: cephfs, metadata pool: cephfs_metadata, data pools: [cephfs_data ] # name: cephfs2, metadata pool: cephfs2_metadata, data pools: [cephfs2_data ] $ ceph fs authorize cephfs client.someuser / rw # [client.someuser] # key = AQAmthpf89M+JhAAiHDYQkMiCq3x+J0n9e8REQ== $ cat ceph.client.someuser.keyring # [client.someuser] # key = AQAmthpf89M+JhAAiHDYQkMiCq3x+J0n9e8REQ== # caps mds = "allow rw fsname=cephfs" # caps mon = "allow r fsname=cephfs" # caps osd = "allow rw tag cephfs data=cephfs" ``` 该客户端只能看到它被授权的文件系统: ```bash $ ceph fs ls -n client.someuser -k ceph.client.someuser.keyring # name: cephfs, metadata pool: cephfs_metadata, data pools: [cephfs_data ] ``` **Standby MDS 守护进程总是会被显示。** 受限 MDS 守护进程与文件系统的信息可能通过其他途径(例如 `ceph health detail`)泄露。 #### 3.1.7 MDS 通信限制 默认情况下,用户应用可以与**任何** MDS 通信,无论它们是否被允许修改相关文件系统的数据(见上文"路径限制")。可以通过为特定文件系统添加 MDS caps,把客户端通信限制在与该文件系统关联的 MDS 守护进程上。 集群有两个文件系统 `cephfs`、`cephfs2`,客户端 `someuser` 只被授权 `cephfs`: ```bash $ ceph fs authorize cephfs client.someuser / rw $ ceph auth get client.someuser > ceph.client.someuser.keyring $ cat ceph.client.someuser.keyring # [client.someuser] # key = AQBPSARfg8hCJRAAEegIxjlm7VkHuiuntm6wsA== # caps mds = "allow rw fsname=cephfs" # caps mon = "allow r" # caps osd = "allow rw tag cephfs data=cephfs" ``` 挂载 `cephfs1`(已建好挂载点 `/mnt/cephfs1`)**成功**: ```bash $ sudo ceph-fuse /mnt/cephfs1 -n client.someuser -k ceph.client.someuser.keyring --client-fs=cephfs # ceph-fuse[96634]: starting ceph client # ceph-fuse[96634]: starting fuse $ mount | grep ceph-fuse # ceph-fuse on /mnt/cephfs1 type fuse.ceph-fuse (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other) ``` > 若 `/mnt/cephfs` 在运行上述命令前不存在,请先 `mkdir /mnt/cephfs1`。 用 `someuser` 挂载 `cephfs2` **失败**: ```bash $ sudo ceph-fuse /mnt/cephfs2 -n client.someuser -k ceph.client.someuser.keyring --client-fs=cephfs2 # ceph-fuse[96599]: starting ceph client # ceph-fuse[96599]: ceph mount failed with (1) Operation not permitted ``` #### 3.1.8 Root squash `root squash` 是一项安全措施,用于防止意外强制删除路径(例如 `sudo rm -rf /path`)之类的场景。在 MDS caps 中启用 `root_squash` 模式,可以**禁止 `uid=0` 或 `gid=0` 的客户端执行写访问操作**(例如 `rm`、`rmdir`、`rmsnap`、`mkdir`、`mksnap`)。与其他文件系统不同,该模式**允许** root 客户端的读操作。 示例:在整个文件系统上启用 `root_squash`,但 `/volumes` 目录树内除外: ```bash ceph fs authorize a client.test_a / rw root_squash /volumes rw ceph auth get client.test_a ``` ```text [client.test_a] key = AQBZcDpfEbEUKxAADk14VflBXt71rL9D966mYA== caps mds = "allow rw fsname=a root_squash, allow rw fsname=a path=/volumes" caps mon = "allow r fsname=a" caps osd = "allow rw tag cephfs data=a" ``` #### 3.1.9 用 `fs authorize` 更新能力 从 Ceph 的 **Reef** 版本起,`fs authorize` 可用于向**已存在的客户端**添加新 caps(同一个 CephFS 的另一个路径,或另一个 CephFS)。 **重复执行同一条命令的行为:** ```bash $ ceph fs authorize a client.x / rw # [client.x] # key = AQAOtSVk9WWtIhAAJ3gSpsjwfIQ0gQ6vfSx/0w== $ ceph auth get client.x # [client.x] # key = AQAOtSVk9WWtIhAAJ3gSpsjwfIQ0gQ6vfSx/0w== # caps mds = "allow rw fsname=a" # caps mon = "allow r fsname=a" # caps osd = "allow rw tag cephfs data=a" $ ./bin/ceph fs authorize a client.x / rw # no update for caps of client.x ``` (Reef 之前,第二次运行会打印一条错误消息;Reef 及以后打印 "capabilities 未更新" 的信息。) **为同一 CephFS 的另一个路径添加 caps:** ```bash $ ceph fs authorize a client.x /dir1 rw # updated caps for client.x $ ceph auth get client.x # caps mds = "allow r fsname=a, allow rw fsname=a path=some/dir" ``` **为集群上的另一个 CephFS 添加 caps:** ```bash $ ceph fs authorize b client.x / rw # updated caps for client.x $ ceph auth get client.x # caps mds = "allow rw fsname=a, allow rw fsname=b" # caps mon = "allow r fsname=a, allow r fsname=b" # caps osd = "allow rw tag cephfs data=a, allow rw tag cephfs data=b" ``` **只能改 `rw` 权限:** `fs authorize` 只能在必须**改变读/写权限**的情况下修改能力。原因是 `fs authorize` 在其他情形下是有歧义的:例如先执行 `fs authorize cephfs1 client.x /dir1 rw` 创建客户端,再执行 `fs authorize cephfs1 client.x /dir2 rw`——第二条命令既可以被理解为"把 `/dir1` 改成 `/dir2` 并保持现有能力",也可以被理解为"为该客户端新增 `/dir2` 的能力"。官方选择的是**后一种**解释,因此**除了 `rw` 权限之外,无法更新已授予能力的任何一部分**。 修改 `rw` 权限示例: ```bash $ ceph fs authorize a client.x / r $ ceph auth get client.x # caps mds = "allow r fsname=a" # caps mon = "allow r fsname=a" # caps osd = "allow r tag cephfs data=a" ``` **`fs authorize` 永远不会削减(deduct)任何能力:** ```bash $ ceph fs authorize a client.x / rw root_squash $ ceph auth get client.x # caps mds = "allow rw fsname=a root_squash" $ ceph fs authorize a client.x / rw # no update was performed for caps of client.x. caps of client.x remains unchanged. ``` 同理,如果客户端已经对 FS `a` 的路径 `dir1` 有 cap,再次对 FS `a` 但路径 `dir2` 运行 `fs authorize`,**不会**修改已有能力,而是**新增** `dir2` 的 cap: ```bash $ ceph fs authorize a client.x /dir1 rw # caps mds = "allow rw fsname=a path=/dir1" $ ceph fs authorize a client.x /dir2 rw # updated caps for client.x # caps mds = "allow rw fsname=a path=dir1, allow rw fsname=a path=dir2" ``` > 引自:[CephFS Client Capabilities](https://docs.ceph.com/en/tentacle/cephfs/client-auth/) ### 3.2 CephFS 中的 capabilities(capabilities) 当客户端想操作一个 inode 时,会以多种方式查询 MDS,MDS 随后授予客户端一组 **capabilities(能力)**,赋予客户端以各种方式操作该 inode 的权限。与 NFS、SMB 等其他网络文件系统的一个主要差别是:**CephFS 授予的能力相当细粒度,多个客户端可以对同一个 inode 持有不同的能力。** #### 3.2.1 Generic cap bits(通用能力位) ```c /* generic cap bits */ #define CEPH_CAP_GSHARED 1 /* (metadata) client can read (s) */ #define CEPH_CAP_GEXCL 2 /* (metadata) client can read and update (x) */ #define CEPH_CAP_GCACHE 4 /* (file) client can cache reads (c) */ #define CEPH_CAP_GRD 8 /* (file) client can read (r) */ #define CEPH_CAP_GWR 16 /* (file) client can write (w) */ #define CEPH_CAP_GBUFFER 32 /* (file) client can buffer writes (b) */ #define CEPH_CAP_GWREXTEND 64 /* (file) client can extend EOF (a) */ #define CEPH_CAP_GLAZYIO 128 /* (file) client can perform lazy io (l) */ ``` | 位值 | 宏 | 单字母 | 含义 | |---|---|---|---| | 1 | `CEPH_CAP_GSHARED` | `s` | (元数据)客户端可以读 | | 2 | `CEPH_CAP_GEXCL` | `x` | (元数据)客户端可以读并更新 | | 4 | `CEPH_CAP_GCACHE` | `c` | (文件)客户端可以缓存读 | | 8 | `CEPH_CAP_GRD` | `r` | (文件)客户端可以读 | | 16 | `CEPH_CAP_GWR` | `w` | (文件)客户端可以写 | | 32 | `CEPH_CAP_GBUFFER` | `b` | (文件)客户端可以缓冲写 | | 64 | `CEPH_CAP_GWREXTEND` | `a` | (文件)客户端可以扩展 EOF | | 128 | `CEPH_CAP_GLAZYIO` | `l` | (文件)客户端可以执行 lazy io | #### 3.2.2 Per-lock shift(按锁的位移) 这些位再被**左移**特定的位数,位移量表示"能力授予的是 inode 的哪一部分数据或元数据": ```c /* per-lock shift */ #define CEPH_CAP_SAUTH 2 /* A */ #define CEPH_CAP_SLINK 4 /* L */ #define CEPH_CAP_SXATTR 6 /* X */ #define CEPH_CAP_SFILE 8 /* F */ ``` | 位移宏 | 位移量 | 字母 | 对应 inode 部分 | |---|---|---|---| | `CEPH_CAP_SAUTH` | 2 | `A` | 认证相关元数据 | | `CEPH_CAP_SLINK` | 4 | `L` | 链接计数 | | `CEPH_CAP_SXATTR` | 6 | `X` | 扩展属性 | | `CEPH_CAP_SFILE` | 8 | `F` | 文件数据与相关数据元数据 | ``` | AUTH | LINK | XATTR | FILE 2 4 6 8 ``` **只有某些 generic cap 类型会被授予某些位移**。特别地,**只有 FILE 位移**会用到前两位以外的位。 由每个位值左移到正确的位,就得到一批常量: ```c #define CEPH_CAP_AUTH_SHARED (CEPH_CAP_GSHARED << CEPH_CAP_SAUTH) ``` 这些位可以 **or** 在一起,构成表示一组能力的位掩码。 **唯一的例外**——"pin": ```c #define CEPH_CAP_PIN 1 /* no specific capabilities beyond the pin */ ``` "pin" 只是把 inode 固定在内存中,**不授予任何其他 caps**。 #### 3.2.3 图形化表示 ``` +---+---+---+---+---+---+---+---+ | p | _ |As x |Ls x |Xs x | +---+---+---+---+---+---+---+---+ |Fs x c r w b a l | +---+---+---+---+---+---+---+---+ ``` 第二位(bit 1)当前未使用。 #### 3.2.4 每种 cap 实际授予了什么能力 | cap | 实际允许客户端做什么 | |---|---| | **PIN** | 仅把 inode 固定在内存中。足以让客户端取到 inode 号,以及其他不可变的东西,如设备 inode 的 major/minor 号,或符号链接内容 | | **AUTH** | 获取认证相关元数据,特别是 **owner、group、mode**。注意:做完整的权限检查可能还需要 ACL,而 ACL 存在 xattr 中 | | **LINK** | inode 的链接计数 | | **XATTR** | 访问或操作 xattr。注意:因为 ACL 存在 xattr 中,检查权限时有时也需要访问它们 | | **FILE** | 最重要的一项。允许客户端访问和操作**文件数据**,也涵盖与文件数据相关的某些元数据——特别是 **size、mtime、atime、ctime** | #### 3.2.5 简写(Shorthand) 客户端日志中也会用紧凑形式表示能力,例如: ``` pAsLsXsFs ``` - `p` 代表 pin。 - 每个大写字母对应一个 shift 值(A/L/X/F)。 - 每个 shift 后面的小写字母是该 shift 下实际授予的能力。 #### 3.2.6 锁状态与能力的关系 MDS 中每个 inode 有四种不同的锁:**simplelock、scatterlock、filelock、locallock**。每种锁有若干不同的锁状态,MDS 会基于锁状态向客户端发放能力。 在每种状态下,MDS Locker **总是**尽量把允许的所有能力发放给客户端——即使有些能力客户端并不需要或并不想要,因为预发能力在某些情况下能降低延迟。 - 如果**只有一个客户端**,通常它会是所有 inode 的 **loner client**。 - 在**多客户端**情形下,MDS 会尝试依据各客户端(needed | wanted)的能力为每个 inode 计算一个 loner client,但**通常会失败**。**loner client 总是拿到全部能力。** **filelock** 控制文件部分元数据和文件内容的访问权限,元数据包括 **mtime、atime、size** 等。 | FILE cap | 规则 | |---|---| | **Fs** | 一旦某客户端持有,其他所有客户端都被拒绝 **Fw** | | **Fx** | 只有 loner client 才能获得。一旦锁状态迁移到 `LOCK_EXCL`,loner client 会被授予该能力以及除 **Fl** 之外的所有其他文件能力 | | **Fr** | 一旦某客户端持有,**Fb** 能力会从其他所有客户端被 revoke 掉。若客户端只请求读文件,锁状态会直接转到 `LOCK_SYNC` 稳定态:所有客户端可从 auth MDS 获得 **Fscrl**,从副本 MDS 获得 **Fscr**。若多客户端同时读写同一文件,锁状态最终会转到 `LOCK_MIX` 稳定态:所有客户端可从 auth MDS 拿到 **Frwl**,从副本 MDS 拿到 **Fr**;**Fcb** 不会授予任何客户端,客户端做同步读写 | | **Fw** | 若没有 loner client,一旦某客户端持有该能力,其他客户端就不会被授予 **Fsxcb**。多客户端同时读写同一文件时同样转到 `LOCK_MIX`:auth MDS 给 **Frwl**,副本 MDS 给 **Fr**,**Fcb** 不授予,客户端做同步读写 | | **Fc** | 表示客户端可以**缓存文件读**,应当与 **Fr** 一起发放,只有在这种用例下才有意义。但在某些稳定态或中间过渡态,即使 **Fr** 未授予,也倾向于保持 **Fc** 允许,以避免强制客户端丢弃整个缓存(例如简单的文件扩展或截断场景) | | **Fb** | 表示客户端可以**缓冲文件写**,应当与 **Fw** 一起发放,只有在这种用例下才有意义。类似地,某些状态下即使 **Fw** 未授予也保持 **Fb** 允许,以避免强制丢弃脏缓冲 | | **Fl** | 表示客户端可以执行 **lazy io**。LazyIO 放宽 POSIX 语义:即使文件被多个客户端上的多个应用打开,也允许缓冲读写。**由应用自己负责管理缓存一致性** | > 引自:[Capabilities in CephFS](https://docs.ceph.com/en/tentacle/cephfs/capabilities/) ### 3.3 CephFS 配额(quota) CephFS 允许在文件系统的**任何目录**上设置配额。配额可以限制该目录层级之下存储的**字节数**或**文件数**。 与 CephFS 中大多数其他东西一样,配额通过**虚拟扩展属性**配置: | 虚拟 xattr | 含义 | |---|---| | `ceph.quota.max_files` | 文件数限制 | | `ceph.quota.max_bytes` | 字节数限制 | 如果这些扩展属性出现在某个目录上,说明那里配置了配额;若不存在,则该目录没有配额(但父目录上可能仍配了配额)。 #### 3.3.1 设置配额 ```bash setfattr -n ceph.quota.max_bytes -v 100000000 /some/dir # 100 MB setfattr -n ceph.quota.max_files -v 10000 /some/dir # 10,000 files ``` `ceph.quota.max_bytes` 也支持人类友好单位: ```bash setfattr -n ceph.quota.max_bytes -v 100K /some/dir # 100 KiB setfattr -n ceph.quota.max_bytes -v 5Gi /some/dir # 5 GiB ``` > 注意:即使输入 SI 单位,取值也会被**严格转换为 IEC 单位**,例如 `1K` → 1024 字节。 > 注意:要让客户端能通过修改相应扩展属性来设置配额,该客户端的认证能力中除 `rw` 外还需要 **`p` flag**(见 3.1.3)。 #### 3.3.2 查看配额 ```bash $ getfattr -n ceph.quota.max_bytes /some/dir # file: dir1/ ceph.quota.max_bytes="100000000" $ getfattr -n ceph.quota.max_files /some/dir # file: dir1/ ceph.quota.max_files="10000" ``` > 注意:对 CephFS 目录运行 `getfattr /some/dir -d -m -` **不会**打印任何 CephFS 扩展属性,因为 CephFS 的内核与 FUSE 客户端在 `listxattr(2)` 系统调用中隐藏了这些信息。要查看某个特定的 CephFS 扩展属性,请运行 `getfattr /some/dir -n ceph.`。 #### 3.3.3 移除或禁用配额 **方式一:删除属性** ```bash $ setfattr -x ceph.quota.max_bytes /some/dir $ getfattr /some/dir -n ceph.quota.max_bytes /some/dir/: ceph.quota.max_bytes: No such attribute $ setfattr -x ceph.quota.max_files /some/dir $ getfattr /some/dir/ -n ceph.quota.max_files /some/dir/: ceph.quota.max_files: No such attribute ``` **方式二:把值设为 0** ```bash $ setfattr -n ceph.quota.max_bytes -v 0 /some/dir $ getfattr /some/dir -n ceph.quota.max_bytes /some/dir/: ceph.quota.max_bytes: No such attribute $ setfattr -n ceph.quota.max_files -v 0 /some/dir $ getfattr /some/dir/ -n ceph.quota.max_files /some/dir/: ceph.quota.max_files: No such attribute ``` #### 3.3.4 空间用量报告与配额 当 CephFS 挂载的根目录上设置了配额时,`df` 等空间用量工具报告的可用空间基于配额上限:`available space = quota limit - used space`,而非 `available space = total space - used space`。 在 `ceph.conf` 的 client 段设置以下选项可禁用该行为: ```ini client quota df = false ``` #### 3.3.5 配额的 5 条限制 | # | 限制 | 说明 | |---|---|---| | 1 | **配额是协作式、非对抗式的** | CephFS 配额依赖挂载文件系统的**客户端主动配合**——达到上限时由客户端停止写入。**被改造过的或恶意的客户端无法被阻止**写入它想写的数据量。在客户端完全不可信的环境中,**不应依赖配额**来防止系统被填满 | | 2 | **配额不精确** | 正在写文件系统的进程会在达到配额上限**后的一小段时间**才被停止,必然会被允许写入超过配置上限的数据。超出多少主要取决于**时间**而非数据量。一般来说写入者会在越限后 **10 秒量级**内被停止 | | 3 | **配额在内核客户端 4.17 及以上实现** | 用户态客户端(libcephfs、ceph-fuse)支持配额。Linux 内核客户端 **>= 4.17** 支持 CephFS 配额,但**仅对 mimic+ 集群**生效。较新的内核客户端在较老的集群上**无法正确处理配额**,即使它们可能能设置配额扩展属性 | | 4 | **与基于路径的挂载限制配合使用时需谨慎配置** | 客户端必须能访问**配置了配额的那个目录 inode** 才能执行配额。若客户端基于 MDS capability 只能访问某个特定路径(如 `/home/user`),而配额配在其无权访问的祖先目录(如 `/home`)上,则客户端**不会执行**该配额。使用基于路径的访问限制时,务必把配额配在客户端被限制到的那个目录(如 `/home/user`)或其嵌套子目录上。对**内核客户端**而言,还需要能访问配额目录的**父目录**:若配额配在 `/home/volumes/group`,kclient 需要能访问其父目录 `/home/volumes`。创建这类用户的示例:`ceph auth get-or-create client.guest mds 'allow r path=/home/volumes, allow rw path=/home/volumes/group' mgr 'allow rw' osd 'allow rw tag cephfs metadata=*' mon 'allow r'` | | 5 | **已删除或已修改的快照文件数据不计入配额** | 见 http://tracker.ceph.com/issues/24284 | > 引自:[CephFS Quotas](https://docs.ceph.com/en/tentacle/cephfs/quota/) ### 3.4 目录项名规范化与大小写折叠(charmap) CephFS 允许把目录树配置为对目录项名做 **normalize(规范化)**以及可能的 **case fold(大小写折叠)**。这通常对由 Samba 这类网关导出的文件系统很有用——它们强制文件系统的大小写不敏感视图,而在原生不区分大小写的文件系统上实现这一点通常要付出性能代价。 #### 3.4.1 四个虚拟扩展属性 | 虚拟 xattr | 类型 | 含义 | |---|---|---| | `ceph.dir.casesensitive` | boolean | 目录的大小写敏感性。若为 true,则对目录项名做大小写折叠 | | `ceph.dir.normalization` | string | 对目录项名应用的 Unicode 规范化类型。客户端目前理解 D(`nfd`)、C(`nfc`)、KD(`nfkd`)、KC(`nfkc`)四种形式 | | `ceph.dir.encoding` | string | 目录项名使用并强制的编码。默认且目前**唯一**支持的编码是 UTF-8(`utf8`) | | `ceph.dir.charmap` | (JSON) | 便捷属性:一个目录完整的字符映射配置(JSON 编码),也可用于**移除全部设置**、恢复 CephFS 默认行为(即:不做解释的字节序列,不含 `/`,以 NUL 结尾) | #### 3.4.2 操作限制 修改这些扩展属性时的限制: - **目录必须为空。** - **目录不能是快照的一部分。** 在带有 `charmap` 配置的目录下新建的子目录会**继承(拷贝)**父目录的配置。 > 注意:`charmap` 配置**只作用于目录中的项**,不作用于目录自身的名字。 > 注意:可以对一个**继承了**配置的子目录移除 `charmap`,只要满足前提条件(为空且不属于任何已存在的快照)。 #### 3.4.3 Normalization 可选的规范化形式: | 取值 | Unicode 形式 | |---|---| | `nfd` | Form D(Canonical Decomposition)——**默认值** | | `nfc` | Form C(Canonical Decomposition,后接 Canonical Composition) | | `nfkd` | Form KD(Compatibility Decomposition) | | `nfkc` | Form KC(Compatibility Decomposition,后接 Canonical Composition) | 在路径遍历或查找过程中生成目录项名时,客户端会在向 MDS 提交任何操作**之前**对该名字应用规范化。**MDS 侧存储的目录项名只是这些规范化后的名字。** ```bash $ setfattr -n ceph.dir.normalization -v "" foo/ $ getfattr -n ceph.dir.charmap foo/ # file: foo/ ceph.dir.charmap="{\"casesensitive\":true,\"normalization\":\"nfd\",\"encoding\":\"utf8\"}" $ getfattr -n ceph.dir.normalization foo/ # file: foo/ ceph.dir.normalization="nfd" ``` > 注意:设置为**空字符串**会让 MDS 选择默认的规范化形式。 **所有字符映射配置都必须启用规范化。** 移除规范化会导致恢复默认值: ```bash $ setfattr -n ceph.dir.normalization -v nfc foo/ $ getfattr -n ceph.dir.normalization foo/ # file: foo/ ceph.dir.normalization="nfc" $ setfattr -x ceph.dir.normalization foo/ $ getfattr -n ceph.dir.normalization foo/ # file: foo/ ceph.dir.normalization="nfd" ``` 要**移除**一个目录上的规范化,必须移除 `ceph.dir.charmap` 配置。 > 注意:MDS 为目录项维护 `alternate_name` 元数据(也用于加密),使客户端能够持久保存应用使用的**原始未规范化名字**。MDS 不以任何方式解释该元数据,它只供客户端重建目录项的原始名字。 #### 3.4.4 Case Folding `ceph.dir.casesensitive` 接受布尔值。**默认情况下名字是大小写敏感的**(POSIX 文件系统的正常行为)。设为 false 会让该目录(及其后代目录)中的项变为**大小写不敏感**。 **大小写折叠要求名字同时被规范化。** 默认情况下,把目录设为大小写不敏感后,`charmap` 为: ```bash $ setfattr -n ceph.dir.casesensitive -v 0 foo/ $ getfattr -n ceph.dir.casesensitive foo/ # file: foo/ ceph.dir.casesensitive="0" $ getfattr -n ceph.dir.charmap foo/ # file: foo/ ceph.dir.charmap="{\"casesensitive\":false,\"normalization\":\"nfd\",\"encoding\":\"utf8\"}" ``` > 注意:设置目录的大小写敏感性会导致**默认规范化被选中**。 > 注意:**规范化先于大小写折叠应用**。MDS 使用的目录项名是「先规范化、再大小写折叠」的结果。 #### 3.4.5 移除字符映射 若目录为空且不属于任何快照,可以移除 `charmap`: ```bash $ setfattr -x ceph.dir.charmap foo/ $ getfattr -n ceph.dir.charmap foo/ foo/: ceph.dir.charmap: No such attribute ``` 若该属性不存在,则该目录没有字符映射。注意:(未来的)子目录或父目录**可能**有 charmap 配置,但对本目录没有影响——**charmap 配置只在目录创建时被继承**。 > 注意:**默认 charmap 包含的规范化无法被禁用。** 关闭该功能的唯一方式是移除 `ceph.dir.charmap` 这个虚拟扩展属性。 #### 3.4.6 限制不兼容的客户端访问 MDS 通过一个**新的客户端 feature bit** 保护带有 `charmap` 的目录树:MDS **不允许**不理解 `charmap` feature 的客户端修改带有 `charmap` 配置的目录,**唯一的例外是** unlink 文件或删除子目录。 也可以要求**所有**客户端都理解 `charmap` feature 才能使用该文件系统: ```bash ceph fs required_client_features add charmap ``` > 注意:**内核驱动不理解 `charmap` feature,而且很可能将来也不会**(因为现有内核库对大小写折叠与规范化形式有自己的观点)。因此**不推荐**把 `charmap` 加入 required client features。 #### 3.4.7 权限 与其他 CephFS 虚拟扩展属性一样,客户端**只有具备 `p` MDS auth cap** 才能在目录上设置 `charmap` 配置。**查看**该配置不需要此 cap。 > 引自:[CephFS Directory Entry Name Normalization and Case Folding](https://docs.ceph.com/en/tentacle/cephfs/charmap/) --- ## 第四部分 数据与元数据管理 ### 4.1 文件布局(file-layouts) 文件的 **layout** 控制其内容如何映射到 Ceph RADOS 对象。可以通过**虚拟扩展属性(virtual xattrs)**读写文件的 layout。 > 客户端在写文件 layout 时**必须**使用 `p` flag(见 3.1.3)。 layout xattr 的名字取决于文件是普通文件还是目录: | 对象 | xattr 名 | |---|---| | 普通文件 | `ceph.file.layout` | | 目录 | `ceph.dir.layout` | 下文示例中的 `ceph.file.layout`,在处理目录时请相应替换为 `dir`。 > 提示:某些 Linux 发行版默认不安装操作 xattr 的命令,所需软件包通常叫 `attr`。 #### 4.1.1 Layout 字段 | 字段 | 类型 | 含义 | |---|---|---| | `pool` | string | 可以是 ID 或 name,只允许 `[a-zA-Z0-9_-.]` 字符。决定存放文件数据对象的 RADOS 池 | | `pool_id` | string(数字) | Ceph 在创建 RADOS 池时分配的 pool ID | | `pool_name` | string | 用户创建 RADOS 池时定义的池名 | | `pool_namespace` | string | 只允许 `[a-zA-Z0-9_-.]` 字符。决定对象写入数据池中的哪个 RADOS namespace。**默认为空(即默认 namespace)** | | `stripe_unit` | integer | 分发文件时使用的数据块大小(字节)。一个文件的所有 stripe unit 大小相等。最后一个 stripe unit 通常只填了一部分数据:它持有直到 EOF 的文件数据,加上填充到固定 stripe unit 大小的 padding | | `stripe_count` | integer | 构成一个 RAID 0 "stripe" 的**连续 stripe unit** 数量 | | `object_size` | integer | 对象大小(字节)。文件数据被切分成该大小的 RADOS 对象 | > 提示:RADOS 对对象大小有可配置的限制——如果把 CephFS 对象大小调到超过该限制,写入可能失败。该 OSD 设置是 `osd_max_object_size`,默认 **128MB**。非常大的 RADOS 对象可能妨碍集群平稳运行,因此**不建议**把对象大小上限调过默认值。 #### 4.1.2 用 `getfattr` 读 layout 作为单个字符串读出: ```bash $ touch file $ getfattr -n ceph.file.layout file # file: file ceph.file.layout="stripe_unit=4194304 stripe_count=1 object_size=4194304 pool=cephfs_data" ``` 逐个字段读: ```bash $ getfattr -n ceph.file.layout.pool_name file # file: file ceph.file.layout.pool_name="cephfs_data" $ getfattr -n ceph.file.layout.pool_id file # file: file ceph.file.layout.pool_id="5" $ getfattr -n ceph.file.layout.pool file # file: file ceph.file.layout.pool="cephfs_data" $ getfattr -n ceph.file.layout.stripe_unit file # file: file ceph.file.layout.stripe_unit="4194304" $ getfattr -n ceph.file.layout.stripe_count file # file: file ceph.file.layout.stripe_count="1" $ getfattr -n ceph.file.layout.object_size file # file: file ceph.file.layout.object_size="4194304" ``` > 注意:读取 layout 时,pool 通常用**名字**表示。但在极少数情况下(池刚创建),可能输出 ID。 **目录在被自定义之前没有显式 layout**。若从未修改过,读取会失败——这表示将使用**最近的、带有显式 layout 的祖先目录**的 layout: ```bash $ mkdir dir $ getfattr -n ceph.dir.layout dir dir: ceph.dir.layout: No such attribute $ setfattr -n ceph.dir.layout.stripe_count -v 2 dir $ getfattr -n ceph.dir.layout dir # file: dir ceph.dir.layout="stripe_unit=4194304 stripe_count=2 object_size=4194304 pool=cephfs_data" ``` **JSON 格式**:若某个 inode 没有设置特定 layout,系统会沿目录路径**向上回溯**,找到最近的带 layout 的祖先目录,并以 JSON 格式返回。文件 layout 也可用 `ceph.file.layout.json` 这个 vxattr 以 JSON 获取。 JSON 输出中会额外加一个名为 **`inheritance`** 的虚拟字段表示 layout 状态: | `inheritance` 值 | 含义 | |---|---| | `@default` | 系统默认 layout | | `@set` | 该 inode 上设置了特定 layout | | `@inherited` | 返回的 layout 是从某个祖先继承来的 | ```bash $ getfattr -n ceph.dir.layout.json --only-values /mnt/mycephs/accounts {"stripe_unit": 4194304, "stripe_count": 1, "object_size": 4194304, "pool_name": "cephfs.a.data", "pool_id": 3, "pool_namespace": "", "inheritance": "@default"} ``` #### 4.1.3 用 `setfattr` 写 layout ```bash $ ceph osd lspools 0 rbd 1 cephfs_data 2 cephfs_metadata $ setfattr -n ceph.file.layout.stripe_unit -v 1048576 file2 $ setfattr -n ceph.file.layout.stripe_count -v 8 file2 $ setfattr -n ceph.file.layout.object_size -v 10485760 file2 $ setfattr -n ceph.file.layout.pool -v 1 file2 # 按 ID 设置 pool $ setfattr -n ceph.file.layout.pool -v cephfs_data file2 # 按 name 设置 pool $ setfattr -n ceph.file.layout.pool_id -v 1 file2 # 按 ID 设置 pool $ setfattr -n ceph.file.layout.pool_name -v cephfs_data file2 # 按 name 设置 pool ``` > 注意:**用 `setfattr` 修改文件的 layout 字段时,该文件必须为空**,否则会报错: ```bash # touch 一个空文件 $ touch file1 # 修改 layout 字段成功 $ setfattr -n ceph.file.layout.stripe_count -v 3 file1 # 往 file1 里写点东西 $ echo "hello world" > file1 $ setfattr -n ceph.file.layout.stripe_count -v 4 file1 setfattr: file1: Directory not empty ``` 文件和目录的 layout 也可以用 JSON 格式设置。**设置时 `inheritance` 字段被忽略。** 另外,若同时指定了 `pool_name` 和 `pool_id`,**优先采用 `pool_name`**(以便更好地消歧)。 ```bash $ setfattr -n ceph.file.layout.json -v '{"stripe_unit": 4194304, "stripe_count": 1, "object_size": 4194304, "pool_name": "cephfs.a.data", "pool_id": 3, "pool_namespace": "", "inheritance": "@default"}' file1 ``` #### 4.1.4 清除 layout 若想移除目录上显式的 layout、回退到继承祖先的 layout: ```bash setfattr -x ceph.dir.layout mydir ``` 类似地,若设置了 `pool_namespace` 属性、想改回默认 namespace: ```bash # 创建目录并在其上设置 namespace mkdir mydir setfattr -n ceph.dir.layout.pool_namespace -v foons mydir getfattr -n ceph.dir.layout mydir ceph.dir.layout="stripe_unit=4194304 stripe_count=1 object_size=4194304 pool=cephfs_data_a pool_namespace=foons" # 清除该目录 layout 中的 namespace setfattr -x ceph.dir.layout.pool_namespace mydir getfattr -n ceph.dir.layout mydir ceph.dir.layout="stripe_unit=4194304 stripe_count=1 object_size=4194304 pool=cephfs_data_a" ``` #### 4.1.5 Layout 的继承规则 **文件在创建时继承其父目录的 layout。但之后对父目录 layout 的修改不会影响已存在的子文件。** ```bash $ getfattr -n ceph.dir.layout dir # file: dir ceph.dir.layout="stripe_unit=4194304 stripe_count=2 object_size=4194304 pool=cephfs_data" # 演示 file1 继承父目录 layout $ touch dir/file1 $ getfattr -n ceph.file.layout dir/file1 # file: dir/file1 ceph.file.layout="stripe_unit=4194304 stripe_count=2 object_size=4194304 pool=cephfs_data" # 在创建第二个文件前先更新目录的 layout $ setfattr -n ceph.dir.layout.stripe_count -v 4 dir $ touch dir/file2 # file1 的 layout 未变 $ getfattr -n ceph.file.layout dir/file1 # file: dir/file1 ceph.file.layout="stripe_unit=4194304 stripe_count=2 object_size=4194304 pool=cephfs_data" # 而 file2 用了父目录的新 layout $ getfattr -n ceph.file.layout dir/file2 # file: dir/file2 ceph.file.layout="stripe_unit=4194304 stripe_count=4 object_size=4194304 pool=cephfs_data" ``` 如果中间目录**没有**设置 layout,那么目录的后代文件也会继承祖先的 layout: ```bash $ getfattr -n ceph.dir.layout dir # file: dir ceph.dir.layout="stripe_unit=4194304 stripe_count=4 object_size=4194304 pool=cephfs_data" $ mkdir dir/childdir $ getfattr -n ceph.dir.layout dir/childdir dir/childdir: ceph.dir.layout: No such attribute $ touch dir/childdir/grandchild $ getfattr -n ceph.file.layout dir/childdir/grandchild # file: dir/childdir/grandchild ceph.file.layout="stripe_unit=4194304 stripe_count=4 object_size=4194304 pool=cephfs_data" ``` #### 4.1.6 向文件系统添加数据池 在把某个池用于 CephFS 之前,必须先把它加到 Metadata Server 上: ```bash $ ceph fs add_data_pool cephfs cephfs_data_ssd $ ceph fs ls # 新池应已出现 .... data pools: [cephfs_data cephfs_data_ssd ] ``` 确认你的 CephX key 允许客户端访问这个新池。然后更新 CephFS 中某个目录的 layout 使用这个池: ```bash $ mkdir /mnt/cephfs/myssddir $ setfattr -n ceph.dir.layout.pool -v cephfs_data_ssd /mnt/cephfs/myssddir ``` 之后在该目录中新建的所有文件都会继承这个 layout,把数据放进新加的池。 **你会注意到:主数据池(传给 `fs new` 的那个)里的对象数仍在持续增长,即使文件都创建在你新加的池里。这是正常的**——文件数据存在 layout 指定的池里,但**所有文件都有一小份元数据保存在主数据池中**(即 backtrace,见 2.1.1)。 > 引自:[File layouts](https://docs.ceph.com/en/tentacle/cephfs/file-layouts/) ### 4.2 FS volumes 与 subvolumes(fs-volumes) #### 4.2.1 概念与用途 Ceph Manager 守护进程(ceph-mgr)的 **`volumes` 模块**为 CephFS 导出提供**单一事实来源(single source of truth)**。OpenStack 共享文件系统服务(**manila**)和 Ceph 容器存储接口(**CSI**)的存储管理员,都使用 ceph-mgr `volumes` 模块提供的这套公共 CLI 来管理 CephFS 导出。 `volumes` 模块实现了三层文件系统导出抽象: | 抽象 | 说明 | |---|---| | **FS volumes** | 对 CephFS 文件系统的抽象 | | **FS subvolume groups** | 比 FS subvolume 高一层的目录抽象,用于在一组 subvolume 上实施策略(例如 file-layouts) | | **FS subvolumes** | 对**独立的 CephFS 目录树**的抽象 | 典型用例: - FS subvolume 用作 **Manila shares** 或 **CSI volumes** - FS subvolume group 用作 **Manila share groups** **要求:** - Ceph **Nautilus (14.2.x)** 或更高版本 - 一个 Cephx 客户端用户,至少具备以下能力: ```text mon 'allow r' mgr 'allow rw' ``` #### 4.2.2 FS Volumes(卷) ```bash ceph fs volume create [placement] [--data-pool ] [--meta-pool ] ``` 该命令创建一个 CephFS 文件系统及其数据池与元数据池。 | 命令 | 用途 | |---|---| | `ceph fs volume create [placement] [--data-pool ] [--meta-pool ]` | 创建卷 | | `ceph fs volume rm [--yes-i-really-mean-it]` | 删除卷 | | `ceph fs volume ls` | 列出所有卷 | | `ceph fs volume rename [--yes-i-really-mean-it]` | 重命名卷 | | `ceph fs volume info vol_name [--human_readable]` | 查看卷信息 | #### 4.2.3 FS Subvolume Groups(子卷组) | 命令 | 用途 | |---|---| | `ceph fs subvolumegroup create [--size ] [--pool_layout ] [--uid ] [--gid ] [--mode ] [--normalization
] [--casesensitive ]` | 创建子卷组 | | `ceph fs subvolumegroup rm [--force]` | 删除子卷组 | | `ceph fs subvolumegroup getpath ` | 获取子卷组路径 | | `ceph fs subvolumegroup ls ` | 列出子卷组 | | `ceph fs subvolumegroup info ` | 查看子卷组信息 | | `ceph fs subvolumegroup exist ` | 判断子卷组是否存在 | | `ceph fs subvolumegroup resize [--no_shrink]` | 调整子卷组大小(配额) | | `ceph fs subvolumegroup snapshot rm [--force]` | 删除子卷组快照 | | `ceph fs subvolumegroup snapshot ls ` | 列出子卷组快照 | #### 4.2.4 FS Subvolumes(子卷) **创建:** ```bash ceph fs subvolume create [--size ] [--group_name ] [--pool_layout ] [--uid ] [--gid ] [--mode ] [--namespace-isolated] [--earmark ] [--normalization ] [--casesensitive ] ``` 该命令在子卷已存在时也会成功返回。 创建子卷时可以指定:所属子卷组、数据池 layout、uid、gid、八进制文件 mode、以及字节大小。**大小是通过在子卷上设置配额实现的**(见 3.3)。通过 `--namespace-isolated` 可以把子卷创建在**独立的 RADOS namespace** 中。 **默认值**:子卷默认创建在**默认子卷组**中,八进制文件 mode 为 `755`,uid 与 gid 继承自其子卷组,数据池 layout 继承自父目录,**没有大小限制**。 **Earmark(标记)**:可以用 `--earmark` 给子卷打标记。earmark 是唯一标识符,用于把子卷标记为特定用途(如 NFS 或 SMB 服务)。**默认不设置** earmark。空字符串(`""`)可用来移除已有的 earmark。 earmark 机制确保子卷被正确标记与管理,避免冲突,保证每个子卷关联到预期的服务或用例。 **合法 Earmark 取值:** | 服务 | 合法格式 | |---|---| | **NFS** | 顶层 scope:`'nfs'` | | **SMB** | 顶层 scope:`'smb'`;或 顶层 scope + 模块内 scope:`'smb.cluster.{cluster_id}'`(`cluster_id` 是唯一标识集群的短字符串)。示例:`smb`、`smb.cluster.cluster_1` | > 注意:如果把 earmark 从一个 scope 改到另一个(例如 nfs ↔ smb),要注意之前 scope 关联的用户权限与 ACL 可能仍然适用。请按需更新必要权限以维持正确的访问控制。 **Unicode 规范化与大小写敏感性**:创建子卷时还可以用 `--normalization` 指定 Unicode 规范化形式(会被内部用于"改写"文件名,使得可以用不同 Unicode 码点序列表示的字符都映射到同一表示,从而访问同一个文件;但用户看到的仍是创建时使用的名字)。 | 取值 | 含义 | |---|---| | `nfd` | canonical decomposition(**默认**) | | `nfc` | canonical decomposition,后接 canonical composition | | `nfkd` | compatibility decomposition | | `nfkc` | compatibility decomposition,后接 canonical composition | 也可以用 `--casesensitive=0` 把子卷配置为**大小写不敏感**访问:仅大小写不同的文件名会映射到同一个文件(创建时使用的大小写被保留)。 > 注意:设置 `--casesensitive=0` 会**隐式启用**该子卷上的 unicode 规范化。 **删除子卷:** ```bash ceph fs subvolume rm [--group_name ] [--force] [--retain-snapshots] ``` 该命令删除子卷及其内容,分两步:① 子卷被移到一个 trash 目录;② trash 目录的内容被**异步**清除。 若子卷有快照或不存在,删除会失败。`--force` 让"删除不存在的子卷"也成功。 **其余 subvolume 命令:** | 命令 | 用途 | |---|---| | `ceph fs subvolume resize [--group_name ] [--no_shrink]` | 调整子卷大小 | | `ceph fs subvolume authorize [--group_name=] [--access_level=]` | 授权某个 auth id 访问子卷 | | `ceph fs subvolume deauthorize [--group_name=]` | 取消授权 | | `ceph fs subvolume authorized_list [--group_name=]` | 列出已授权的 auth id | | `ceph fs subvolume evict [--group_name=]` | 驱逐子卷上的某个客户端 | | `ceph fs subvolume getpath [--group_name ]` | 获取子卷路径 | | `ceph fs subvolume info [--group_name ]` | 查看子卷信息 | | `ceph fs subvolume ls [--group_name ]` | 列出子卷 | | `ceph fs subvolume exist [--group_name ]` | 判断子卷是否存在 | **子卷元数据(metadata):** | 命令 | 用途 | |---|---| | `ceph fs subvolume metadata set [--group_name ]` | 设置元数据 | | `ceph fs subvolume metadata get [--group_name ]` | 读取元数据 | | `ceph fs subvolume metadata ls [--group_name ]` | 列出元数据 | | `ceph fs subvolume metadata rm [--group_name ] [--force]` | 删除元数据 | **Earmark 管理:** | 命令 | 用途 | |---|---| | `ceph fs subvolume earmark get [--group_name ]` | 查看 earmark | | `ceph fs subvolume earmark set [--group_name ] ` | 设置 earmark | | `ceph fs subvolume earmark rm [--group_name ]` | 移除 earmark | #### 4.2.5 子卷快照 | 命令 | 用途 | |---|---| | `ceph fs subvolume snapshot create [--group_name ]` | 创建快照 | | `ceph fs subvolume snapshot rm [--group_name ] [--force]` | 删除快照 | | `ceph fs subvolume snapshot getpath []` | 获取快照路径 | | `ceph fs subvolume snapshot ls [--group_name ]` | 列出快照 | | `ceph fs subvolume snapshot info [--group_name ]` | 查看快照信息 | | `ceph fs subvolume snapshot protect [--group_name ]` | 保护快照(防止被删,克隆需要) | | `ceph fs subvolume snapshot unprotect [--group_name ]` | 取消保护 | **快照元数据:** | 命令 | 用途 | |---|---| | `ceph fs subvolume snapshot metadata set [--group_name ]` | 设置快照元数据 | | `ceph fs subvolume snapshot metadata get [--group_name ]` | 读取快照元数据 | | `ceph fs subvolume snapshot metadata ls [--group_name ]` | 列出快照元数据 | | `ceph fs subvolume snapshot metadata rm [--group_name ] [--force]` | 删除快照元数据 | #### 4.2.6 快照克隆(clone) **发起克隆:** ```bash # 克隆到默认组 ceph fs subvolume snapshot clone # 指定源所在组 ceph fs subvolume snapshot clone --group_name # 指定目标组 ceph fs subvolume snapshot clone --target_group_name # 指定目标 pool layout ceph fs subvolume snapshot clone --pool_layout ``` > 注意:`subvolume snapshot clone` 命令依赖下面提到的 `snapshot_clone_no_wait` 配置选项。 **查看克隆状态:** ```bash ceph fs clone status [--group_name ] ``` **克隆状态取值:** | 状态 | 含义 | |---|---| | `pending` | 克隆操作尚未开始 | | `in-progress` | 克隆进行中 | | `complete` | 克隆成功完成 | | `failed` | 克隆失败 | | `canceled` | 克隆被用户取消 | 失败原因通过 `errno`(错误号)与 `error_msg`(错误字符串)展示。 `in-progress` 克隆的示例(含进度报告): ```bash ceph fs subvolume snapshot clone cephfs subvol1 snap1 clone1 ceph fs clone status cephfs clone1 ``` ```json { "status": { "state": "in-progress", "source": { "volume": "cephfs", "subvolume": "subvol1", "snapshot": "snap1" }, "progress_report": { "percentage cloned": "12.24%", "amount cloned": "376M/3.0G", "files cloned": "4/6" } } } ``` 进度也会打印在 `ceph status` 输出的底部(所有进行中克隆的集合进度): ```text progress: 3 ongoing clones - average progress is 47.569% (10s) [=============...............] (remaining: 11s) ``` 若克隆任务数超过 cloner 线程数,会打印**两条**进度条(进行中的 + 全部进行中+待处理的): ```text progress: 4 ongoing clones - average progress is 27.669% (15s) [=======.....................] (remaining: 41s) Total 5 clones - average progress is 41.667% (3s) [===========.................] (remaining: 4s) ``` > 注意:`failure` 段**只在**克隆状态为 `failed` 或 `cancelled` 时显示。 > 注意:因为 `subvol1` 在默认组中,`clone status` 的 `source` 对象里不含组名。 > 注意:**克隆出来的子卷只有在克隆操作成功完成后才可访问。** `failed` 克隆示例: ```json { "status": { "state": "failed", "source": { "volume": "cephfs", "subvolume": "subvol1", "snapshot": "snap1" "size": "104857600" }, "failure": { "errno": "122", "errstr": "Disk quota exceeded" } } } ``` **重试失败的克隆**:必须先删除未完成的克隆,再重新发起克隆: ```bash ceph fs subvolume rm [--group_name ] --force ``` > 注意:克隆只同步目录、普通文件和符号链接。inode 时间戳(访问时间与修改时间)同步到**秒级**粒度。 **取消克隆**:`in-progress` 或 `pending` 的克隆可以被取消: ```bash ceph fs clone cancel [--group_name ] ``` 取消成功后,克隆出的子卷进入 `canceled` 状态: ```json { "status": { "state": "canceled", "source": { "volume": "cephfs", "subvolume": "subvol1", "snapshot": "snap1" } } } ``` > 注意:删除被取消的克隆时,需要给 `fs subvolume rm` 加 `--force` 选项。 #### 4.2.7 Volumes 模块配置项 | 配置项 | 默认值 | 说明 | |---|---|---| | `mgr/volumes/max_concurrent_clones` | `4` | 并发克隆操作的最大数量 | | `mgr/volumes/pause_purging` | — | 暂停异步清除 trashed 子卷的线程。**在集群恢复场景下很有用** | | `mgr/volumes/pause_cloning` | — | 暂停异步克隆子卷快照的线程。**在集群恢复场景下很有用** | | `mgr/volumes/snapshot_clone_no_wait` | `True`(默认启用) | 当 cloner 线程(可用 `max_concurrent_clones` 等配置)不可用时,**拒绝**克隆创建请求 | ```bash ceph config set mgr mgr/volumes/max_concurrent_clones ceph config set mgr mgr/volumes/pause_purging true ceph config set mgr mgr/volumes/pause_purging false ceph config set mgr mgr/volumes/pause_cloning true ceph config set mgr mgr/volumes/pause_cloning false ceph config set mgr mgr/volumes/snapshot_clone_no_wait ceph config get mgr mgr/volumes/snapshot_clone_no_wait ``` > 💡 补充:`mgr/volumes/*` 是 mgr 模块级配置,不在 `src/common/options/*.yaml.in` 中定义,因此没有从该文件取到的「类型/默认值」;上表默认值以 `fs-volumes` 源文中明确写出的为准(仅 `max_concurrent_clones` 明确给出默认 4、`snapshot_clone_no_wait` 明确给出默认 True),其余两项官方只给了开关用法、未给默认值,故标注为「—」。 #### 4.2.8 Pinning 子卷与子卷组 子卷与子卷组可以根据策略自动 pin 到 rank 上,从而以可预测且稳定的方式跨 MDS rank 分布负载(机制见 2.4.5 与 2.4.6)。 ```bash # 子卷组 ceph fs subvolumegroup pin # 子卷 ceph fs subvolume pin ``` | 参数 | 取值 | |---|---| | `pin_type` | `export`、`distributed`、`random` | | `pin_setting` | 对应 pinning 文档中扩展属性的"值" | 大多数情况下应当设置**子卷组**的 pin。示例(在子卷组上设置 distributed pinning 策略): ```bash ceph fs subvolumegroup pin cephfilesystem-a csi distributed 1 ``` #### 4.2.9 子卷/子卷组的 charmap | 命令 | 用途 | |---|---| | `ceph fs subvolumegroup charmap set ` | 设置子卷组 charmap | | `ceph fs subvolume charmap set <--group_name=name> ` | 设置子卷 charmap | | `ceph fs subvolumegroup charmap get ` | 读取子卷组某项 charmap | | `ceph fs subvolume charmap get <--group_name=name> ` | 读取子卷某项 charmap | | `ceph fs subvolumegroup charmap get ` | 读取子卷组完整 charmap | | `ceph fs subvolume charmap get <--group_name=name>` | 读取子卷完整 charmap | | `ceph fs subvolumegroup charmap rm ` | 移除子卷组 charmap | | `ceph fs subvolume charmap rm <--group_name=name>` | 移除子卷 charmap | ```bash ceph fs subvolumegroup charmap set vol csi normalization nfd ceph fs subvolume charmap get vol subvol --group_name=csi casesensitive ceph fs subvolumegroup charmap get vol csi ceph fs subvolumegroup charmap rm vol csi ``` > 注意:**只有当 subvolumegroup 或 subvolume 为空时才能移除 charmap。** #### 4.2.10 Subvolume quiesce(静默) > 注意:本节内容**仅适用于 Squid 及以后**的 Ceph 版本。 **为什么需要它**:CephFS 快照在**多客户端写入**的场景下**不提供强一致性保证**——这使分布式应用的一致备份与灾难恢复成为严峻挑战。即使应用使用文件系统 flush 来跨其分布式组件同步检查点,也**无法保证**所有已确认的写都会包含在某个给定快照中。 subvolume quiesce 特性就是为使用一个或多个 subvolume 的多客户端应用提供**企业级一致性保证**:它可以**暂停**对某个 volume(文件系统)的一组 subvolume 的 IO。在所有客户端上强制这样一次暂停,就能保证:应用在暂停之前达到的任何持久化检查点,都能从暂停期间创建的快照中恢复。 `volumes` 插件提供了发起并等待这组 subvolume 暂停的 CLI,这个暂停叫 **`quiesce`**,也是命令名: ```bash $ ceph fs quiesce --set-id myset1 <[group_name/]sub_name...> --await # 在 IO 暂停生效期间执行操作,例如打快照 $ ceph fs quiesce --set-id myset1 --release --await # 若成功,该 set 的所有成员都被确认仍处于暂停状态并被释放 ``` `fs quiesce` 功能基于 MDS 守护进程提供的更底层的 **`quiesce db`** 服务,后者以**文件系统路径**为粒度操作。`volumes` 插件只是把 subvolume 名映射到给定文件系统上的对应路径,然后向 MDS 发出相应的 `quiesce db` 命令。 **操作(Operations)** quiesce 可以针对**一组**一个或多个 subvolume(即文件系统中的路径)发起,这组称为 **quiesce set**,每个 quiesce set 由一个唯一的 **set id** 标识。一个 set 可以做以下操作: | 操作 | 说明 | |---|---| | **include** | 纳入一个或多个 subvolume 作为 set 成员 | | **exclude** | 排除一个或多个成员 | | **cancel** | 取消该 set,异步中止其所有当前成员的暂停 | | **release** | 释放该 set,请求所有成员结束暂停,并期待所有客户端回复 ack | | **query** | 按 id 查询某个 set 的当前状态,或查询所有 active set / 所有已知 set | | **cancel all** | 在需要立即恢复 IO 时取消所有 active set | 上述操作**都是非阻塞的**:它们尝试执行预期的修改,然后返回目标 set 的最新版本(无论操作是否成功)。set 可能因为该修改而改变状态;响应中返回的版本**保证**处于与该操作(以及同一控制循环批次中其他成功操作)一致的状态。 有些 set 状态是 **`awaitable`(可等待)**的。上面任意命令都可以附加 **`--await`** 修饰符,使其在应用了预期修改后**阻塞**在该 set 上——只要结果状态是 awaitable。这样的命令会一直阻塞,直到 set 到达被等待的状态、被另一条命令修改、或迁移到另一个状态。返回码会**无歧义地**标识退出条件,响应内容总是携带最新的已知 set 状态。 > 状态图中,`Awaitable` 状态用 `(a)` 或 `(A)` 标记。阻塞版操作会在 set 处于 `(a)` 状态时挂起,若到达 `(A)` 状态则成功完成;若 set 已在 `(A)` 状态,操作立即成功返回。 **多数操作需要 set-id,例外是**:未指定 set id 时创建新 set、查询 active 或所有已知 set、以及 cancel all。 **创建新 set** 通过 `include`(或 `reset`)纳入成员实现。可以指定 set id——若是新 id,则创建出的 set 带着指定成员处于 `QUIESCING` 状态。若未指定 set id,则创建一个带唯一 set id 的新 set。 **Set 状态:** | 状态 | 含义 | |---|---| | `QUIESCING` | 正在建立暂停(成员陆续进入暂停) | | `QUIESCED` | 已全部暂停成功 | | `TIMEDOUT` | 有成员在 `timeout` 内未到达暂停状态,整个 set 被标记为 `TIMEDOUT` 且暂停被释放 | | `EXPIRED` | `QUIESCED` 之后 `expiration` 秒耗尽,set 迁移到 `EXPIRED`(除非被显式 release 或 cancel) | | `CANCELED` | 被 `cancel` 操作取消 | 关键规则: - `timeout` 是**按成员**计的:任何一个成员超过 `timeout` 未暂停,整个 set 被标记为 `TIMEDOUT` 并释放暂停。 - `expiration` 计时器**按整个 set 计**,不是按成员。 - `expiration timer` 在 set 处于 `QUIESCING` 时**不适用**;它在 **set 变为 `QUIESCED`** 时被重置为 `expiration` 属性的值。 - `timeout` **不适用于**已经 `QUIESCED` 的成员。 - 可以向 `QUIESCED` 的 set 添加新成员:此时它会回到 `QUIESCING`,再次变为 `QUIESCED` 后 expiration 计时器重启。 - 从 set 移除成员**不影响**其 `QUIESCED` 状态。 - 对非 `QUIESCING` 的 set 发起 `quiesce await` 是**不允许**的;对非 `QUIESCED` 的 set 做 `--release` 是 **`EPERM`** 错误(无论是否有 `--await`)。对已释放的 set 再 release、或对 `QUIESCED` 的 set 做 `quiesce await`,都是成功的 no-op。 **`--await` 与 `--await-for`:** ```bash $ time ceph fs quiesce fs1 sub1 --timeout=10 --await-for=2 ``` - 可以把 `--await` 理解为等价于一 `--await-for=Infinity`。 - 若 `--await` 与 `--await-for` 同时出现,**前者被忽略**,以 `--await-for` 的时间限制为准。 - **`quiesce await` 有一个副作用:它会重置内部的 expiration 计时器**。这为"长时间多步骤流程在 IO 暂停下进行"提供了 watchdog 模式:反复 `--await` 一个已经 `QUIESCED` 的 set 即可续期。 **典型用例:跨多个 subvolume 打一致性快照** ```bash $ ceph fs quiesce fs1 sub1 sub2 sub3 --timeout=30 --expiration=10 --set-id="snapshots" --await # (1) $ ceph fs subvolume snapshot create a sub1 snap1-sub1 # (2) $ ceph fs quiesce fs1 --set-id="snapshots" --await # (3) 续期 10 秒 $ ceph fs subvolume snapshot create a sub2 snap1-sub2 # (2) $ ceph fs quiesce fs1 --set-id="snapshots" --await # (3) $ ceph fs subvolume snapshot create a sub3 snap1-sub3 # (2) $ ceph fs quiesce fs1 --set-id="snapshots" --release --await # (4) ``` 排除成员: ```bash $ ceph fs quiesce fs1 --set-id="snapshots" --exclude sub3 ``` **`--if-version`(乐观并发控制):** `--if-version=` 会与 set 的当前版本比对;若不匹配,命令返回 **`ESTALE`**,且**所请求的动作不会被执行**。 ```bash $ ceph fs quiesce fs1 --set-id="snapshots" --release --await --if-version=13 ``` 另一个用途(对自动化软件很有用):传入 `--if-version=0` 可以确保 set id 确实是新的——只有不存在同名 set 时才会创建新 set: ```bash $ ceph fs quiesce fs1 sub1 sub2 sub3 --set-id="external-id" --if-version=0 ``` #### 4.2.11 禁用 Volumes 插件 默认情况下 volumes 插件是启用状态,且被设置为 `always on`。但在某些情况下禁用它是合适的:例如当 CephFS 处于 degraded 状态时,volumes 插件的命令可能在 MGR 中堆积而得不到服务,最终触发 policy throttle,导致 **MGR 无响应**。 此时即使它是 MGR 的 `always on` 模块,也可以禁用它: ```bash ceph mgr module disable volumes --yes-i-really-mean-it ``` > 注意:该命令会禁用操作并移除 volumes 插件的命令,因为**它会禁用通过该插件访问的 Ceph 集群上的所有 CephFS 服务**。 在采取如此激烈的措施之前,建议先试一些不那么激烈的手段,再评估文件系统体验是否因此改善。一个例子是:禁用 volumes 插件为克隆与清除 trash 而启动的异步线程(见「Pause Purge threads」与「Pause Clone Threads」,即 `mgr/volumes/pause_purging` 与 `mgr/volumes/pause_cloning`)。 #### 4.2.12 Pool namespace 命名格式变更 > 注意:直到 **Tentacle** 版本,CephFS volumes 的 pool namespace 命名格式是 `fsvolumens__`。但这可能在不同 subvolume group 中出现同名 subvolume 时导致 namespace 冲突。因此 **Tentacle 之后**,pool namespace 格式改为 `fsvolumens___`。 > 引自:[FS volume and subvolumes](https://docs.ceph.com/en/tentacle/cephfs/fs-volumes/) ### 4.3 快照(snapshots) CephFS 快照会在创建时刻生成一个**不可变(immutable)的文件系统视图**。快照在名为 `.snap` 的特殊隐藏子目录中管理,创建快照的动作就是在该目录下 `mkdir` 一个自定义名字的子目录。 快照目录名可以通过客户端配置改写: | 客户端类型 | 配置项/挂载选项 | 默认值 | 说明 | |---|---|---|---| | 内核客户端 | `snapdirname`(挂载选项) | 文档未明确(对应 `client_snapdir` 的 FUSE 侧配置) | 见下表 `client_snapdir` | | `ceph-fuse` | `client_snapdir` | `.snap` | 快照伪目录名 | | 配置项 | 类型 | 默认值 | 含义 | |---|---|---|---| | `client_snapdir` | `str` | `.snap` | pseudo directory for snapshot access to a directory | | `fuse_syncfs_on_mksnap` | `bool` | `true` | synchronize all local metadata/file changes after snapshot | | `mds_max_snaps_per_dir` | `uint` | `100` | max snapshots per directory | | `mds_snap_min_uid` | `uint` | `0` | minimum uid of client to perform snapshots | | `mds_snap_max_uid` | `uint` | `4294967294` | maximum uid of client to perform snapshots | | `mds_snap_rstat` | `bool` | `false` | enabled nested rstat for snapshots | > 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4 #### 4.3.1 启用快照功能 新创建的文件系统**默认开启**快照特性。对已有文件系统,需要显式打开: ```bash $ ceph fs set allow_new_snaps true ``` 开启后,CephFS 中所有目录都会出现一个 `.snap` 目录。 #### 4.3.2 创建快照 在 `.snap` 下创建子目录即为创建快照。例如对 `/file1/` 创建名为 `my_snapshot` 的快照: ```bash $ touch file1 $ cd .snap $ mkdir my_snapshot ``` #### 4.3.3 用快照恢复数据 一次典型的「误删恢复」流程: ```bash # 1) 创建 file1 并打快照 snap1 $ touch /mnt/cephfs/file1 $ cd .snap $ mkdir snap1 # 2) 创建 file2 并打快照 snap2 $ touch /mnt/cephfs/file2 $ cd .snap $ mkdir snap2 # 3) 删除 file1 后打快照 snap3 $ rm /mnt/cephfs/file1 $ cd .snap $ mkdir snap3 # 4) 从 snap2 中把 file1 拷回来 $ cd .snap $ cd snap2 $ cp file1 /mnt/cephfs/ ``` #### 4.3.4 删除快照 删除快照即对 `.snap` 下的目录执行 `rmdir`: ```bash $ cd .snap $ rmdir my_snapshot ``` 注意:**直接删除承载快照的目录会失败**,必须先删除其中的所有快照。 > 💡 补充:删除快照是元数据操作,但实际数据对象的回收走 MDS 的 purge queue(见 4.14),因此 `ceph df` / `du` 的容量下降会滞后于 `rmdir` 返回。 > 引自:[CephFS Snapshots](https://docs.ceph.com/en/tentacle/cephfs/snapshots/) --- ### 4.4 快照调度模块(snap-schedule) `snap_schedule` 是 Ceph Manager 的一个模块,提供定时快照的增删查改以及按保留策略(retention policy)自动清理旧快照的能力。 #### 4.4.1 启用 ```bash $ ceph mgr module enable snap_schedule ``` #### 4.4.2 命令空间与参数规则 - 所有子命令位于 `ceph fs snap-schedule` 命名空间下。 - 参数可以是**位置参数**或**关键字参数**;一旦出现关键字参数,其后所有参数都被当作关键字参数。 - 所有子命令都接受可选的 `fs` 参数,用于多 FS 场景;未指定时默认取 `fs_map` 中的第一个文件系统。使用 fs-volumes(volume)时,`fs` 等价于 `volume`。 - `subvolume` 参数**已不再被接受**。 | 子命令 | 说明 | |---|---| | `fs snap-schedule status [] [] []` | 打印某路径下所有 schedule 与 retention spec | | `fs snap-schedule list [--recursive] [] []` | 单行短格式列出 schedules,`--recursive` 递归列出整棵树 | | `fs snap-schedule add [] []` | 新增调度 | | `fs snap-schedule remove [] [] []` | 删除调度 | | `fs snap-schedule retention add [] []` | 新增保留策略 | | `fs snap-schedule retention remove [] []` | 删除保留策略 | | `fs snap-schedule activate [] [] []` | 激活调度 | | `fs snap-schedule deactivate [] [] []` | 停用调度 | #### 4.4.3 调度(repeat interval)与起始时间 - **repeat interval** 由「数字 + 周期后缀」组成,周期后缀为 `h`(our)、`d`(ay)、`w`(eek)、`M`(onth)、`y`(ear)。例如 `12h` 表示每 12 小时一次。 - **start time** 默认为「最近一次午夜」。例如在 13:50 添加一个 `1h` 调度,第一个快照会在 14:00 产生。 - 时间字符串必须是 ISO8601 格式;未显式带时区时按 UTC 处理,带时区则在执行时映射到 UTC。 ``` UTC: 2022-08-08T05:30:00 # 无时区偏移,按 UTC 处理 IDT: 2022-08-08T09:00:00+03:00 # 等价于 06:00 UTC EDT: 2022-08-08T05:30:00-04:00 # 等价于 09:30 UTC ``` #### 4.4.4 保留策略(retention spec) 保留策略由「路径 + retention spec」标识。一个路径**有且仅有一个**保留策略,但策略内可以包含多个「数量 + 周期」对。 - 语义:保证保留 `` 个彼此至少相隔 `