Files
wiki/存储/Ceph/知识库/08-CephFS-文件存储篇.md
2026-09-18 13:49:11 +08:00

5769 lines
300 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <fs_name> <metadata> <data> [--force] [--allow-dangerous-metadata-overlay] [<fscid:int>] [--recover] [--yes-i-really-really-mean-it] [<set>...]
```
| 参数/选项 | 含义 |
|---|---|
| `<fs_name>` | 文件系统名 |
| `<metadata>` | 元数据池名 |
| `<data>` | **默认**数据池名,一旦设定**不可更改** |
| `--force` | 用于三类操作之一:①把 EC 池设为默认数据池(不推荐);②元数据池非空(已有对象);③配合 `--fscid` 创建指定 fscid 的文件系统(`--fscid` 必须与 `--force` 同用) |
| `--allow-dangerous-metadata-overlay` | 允许复用已在使用的元数据/数据池。仅在紧急情况下、且仔细阅读文档后使用 |
| `--fscid <int>` | 用指定 fscid 创建文件系统。适用于希望文件系统 ID 在恢复后保持稳定的应用(例如 mon 数据库丢失重建后)。因此文件系统 ID 不一定随新文件系统单调递增 |
| `--recover` | 把文件系统 rank 0 的状态设为"存在但 failed"。这样当 MDS 认领 rank 0 时,会读取 RADOS 中已有的元数据而不是覆盖它。该 flag 同时阻止 standby MDS 加入文件系统 |
| `--yes-i-really-really-mean-it` | 某些 `fs set` 类操作也会用到此确认串 |
| `<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 <fs_name> 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 <fs_name> 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 <fs_name> 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 <fs_name> bal_rank_mask <hex>
```
| `<hex>` 取值 | 含义 |
|---|---|
| `0x3` | balancer 只在 rank 0 和 rank 1 上运行(每个 bit 代表一个 rank) |
| `-1` 或 `all` | 所有 active rank 都被 mask,即全部参与 balancer |
| `0x0` | 关闭 balancer |
```bash
ceph fs set <fs_name> bal_rank_mask 0x3
ceph fs set <fs_name> bal_rank_mask -1
ceph fs set <fs_name> 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 <fs name> standby_count_wanted <count>
```
这个计数**包含**处于 `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 <fs name> allow_standby_replay <bool>
```
设置后,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 <fs name> 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 <name>@<fsid>.<fs_name>=/ /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 <fs_name> client.<client_id> <path-in-cephfs> 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.<client_id> <mount-path> -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.<some-xattr>`。
#### 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 <fs_name> 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 <vol_name> [placement] [--data-pool <data-pool-name>] [--meta-pool <metadata-pool-name>]
```
该命令创建一个 CephFS 文件系统及其数据池与元数据池。
| 命令 | 用途 |
|---|---|
| `ceph fs volume create <vol_name> [placement] [--data-pool <data-pool-name>] [--meta-pool <metadata-pool-name>]` | 创建卷 |
| `ceph fs volume rm <vol_name> [--yes-i-really-mean-it]` | 删除卷 |
| `ceph fs volume ls` | 列出所有卷 |
| `ceph fs volume rename <vol_name> <new_vol_name> [--yes-i-really-mean-it]` | 重命名卷 |
| `ceph fs volume info vol_name [--human_readable]` | 查看卷信息 |
#### 4.2.3 FS Subvolume Groups(子卷组)
| 命令 | 用途 |
|---|---|
| `ceph fs subvolumegroup create <vol_name> <group_name> [--size <size_in_bytes>] [--pool_layout <data_pool_name>] [--uid <uid>] [--gid <gid>] [--mode <octal_mode>] [--normalization <form>] [--casesensitive <bool>]` | 创建子卷组 |
| `ceph fs subvolumegroup rm <vol_name> <group_name> [--force]` | 删除子卷组 |
| `ceph fs subvolumegroup getpath <vol_name> <group_name>` | 获取子卷组路径 |
| `ceph fs subvolumegroup ls <vol_name>` | 列出子卷组 |
| `ceph fs subvolumegroup info <vol_name> <group_name>` | 查看子卷组信息 |
| `ceph fs subvolumegroup exist <vol_name>` | 判断子卷组是否存在 |
| `ceph fs subvolumegroup resize <vol_name> <group_name> <new_size> [--no_shrink]` | 调整子卷组大小(配额) |
| `ceph fs subvolumegroup snapshot rm <vol_name> <group_name> <snap_name> [--force]` | 删除子卷组快照 |
| `ceph fs subvolumegroup snapshot ls <vol_name> <group_name>` | 列出子卷组快照 |
#### 4.2.4 FS Subvolumes(子卷)
**创建:**
```bash
ceph fs subvolume create <vol_name> <subvol_name> [--size <size_in_bytes>] [--group_name <subvol_group_name>] [--pool_layout <data_pool_name>] [--uid <uid>] [--gid <gid>] [--mode <octal_mode>] [--namespace-isolated] [--earmark <earmark>] [--normalization <form>] [--casesensitive <bool>]
```
该命令在子卷已存在时也会成功返回。
创建子卷时可以指定:所属子卷组、数据池 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 <vol_name> <subvol_name> [--group_name <subvol_group_name>] [--force] [--retain-snapshots]
```
该命令删除子卷及其内容,分两步:① 子卷被移到一个 trash 目录;② trash 目录的内容被**异步**清除。
若子卷有快照或不存在,删除会失败。`--force` 让"删除不存在的子卷"也成功。
**其余 subvolume 命令:**
| 命令 | 用途 |
|---|---|
| `ceph fs subvolume resize <vol_name> <subvol_name> <new_size> [--group_name <subvol_group_name>] [--no_shrink]` | 调整子卷大小 |
| `ceph fs subvolume authorize <vol_name> <sub_name> <auth_id> [--group_name=<group_name>] [--access_level=<access_level>]` | 授权某个 auth id 访问子卷 |
| `ceph fs subvolume deauthorize <vol_name> <sub_name> <auth_id> [--group_name=<group_name>]` | 取消授权 |
| `ceph fs subvolume authorized_list <vol_name> <sub_name> [--group_name=<group_name>]` | 列出已授权的 auth id |
| `ceph fs subvolume evict <vol_name> <sub_name> <auth_id> [--group_name=<group_name>]` | 驱逐子卷上的某个客户端 |
| `ceph fs subvolume getpath <vol_name> <subvol_name> [--group_name <subvol_group_name>]` | 获取子卷路径 |
| `ceph fs subvolume info <vol_name> <subvol_name> [--group_name <subvol_group_name>]` | 查看子卷信息 |
| `ceph fs subvolume ls <vol_name> [--group_name <subvol_group_name>]` | 列出子卷 |
| `ceph fs subvolume exist <vol_name> [--group_name <subvol_group_name>]` | 判断子卷是否存在 |
**子卷元数据(metadata):**
| 命令 | 用途 |
|---|---|
| `ceph fs subvolume metadata set <vol_name> <subvol_name> <key_name> <value> [--group_name <subvol_group_name>]` | 设置元数据 |
| `ceph fs subvolume metadata get <vol_name> <subvol_name> <key_name> [--group_name <subvol_group_name>]` | 读取元数据 |
| `ceph fs subvolume metadata ls <vol_name> <subvol_name> [--group_name <subvol_group_name>]` | 列出元数据 |
| `ceph fs subvolume metadata rm <vol_name> <subvol_name> <key_name> [--group_name <subvol_group_name>] [--force]` | 删除元数据 |
**Earmark 管理:**
| 命令 | 用途 |
|---|---|
| `ceph fs subvolume earmark get <vol_name> <subvol_name> [--group_name <subvol_group_name>]` | 查看 earmark |
| `ceph fs subvolume earmark set <vol_name> <subvol_name> [--group_name <subvol_group_name>] <earmark>` | 设置 earmark |
| `ceph fs subvolume earmark rm <vol_name> <subvol_name> [--group_name <subvol_group_name>]` | 移除 earmark |
#### 4.2.5 子卷快照
| 命令 | 用途 |
|---|---|
| `ceph fs subvolume snapshot create <vol_name> <subvol_name> <snap_name> [--group_name <subvol_group_name>]` | 创建快照 |
| `ceph fs subvolume snapshot rm <vol_name> <subvol_name> <snap_name> [--group_name <subvol_group_name>] [--force]` | 删除快照 |
| `ceph fs subvolume snapshot getpath <volname> <subvol_name> <snap_name> [<group_name>]` | 获取快照路径 |
| `ceph fs subvolume snapshot ls <vol_name> <subvol_name> [--group_name <subvol_group_name>]` | 列出快照 |
| `ceph fs subvolume snapshot info <vol_name> <subvol_name> <snap_name> [--group_name <subvol_group_name>]` | 查看快照信息 |
| `ceph fs subvolume snapshot protect <vol_name> <subvol_name> <snap_name> [--group_name <subvol_group_name>]` | 保护快照(防止被删,克隆需要) |
| `ceph fs subvolume snapshot unprotect <vol_name> <subvol_name> <snap_name> [--group_name <subvol_group_name>]` | 取消保护 |
**快照元数据:**
| 命令 | 用途 |
|---|---|
| `ceph fs subvolume snapshot metadata set <vol_name> <subvol_name> <snap_name> <key_name> <value> [--group_name <subvol_group_name>]` | 设置快照元数据 |
| `ceph fs subvolume snapshot metadata get <vol_name> <subvol_name> <snap_name> <key_name> [--group_name <subvol_group_name>]` | 读取快照元数据 |
| `ceph fs subvolume snapshot metadata ls <vol_name> <subvol_name> <snap_name> [--group_name <subvol_group_name>]` | 列出快照元数据 |
| `ceph fs subvolume snapshot metadata rm <vol_name> <subvol_name> <snap_name> <key_name> [--group_name <subvol_group_name>] [--force]` | 删除快照元数据 |
#### 4.2.6 快照克隆(clone)
**发起克隆:**
```bash
# 克隆到默认组
ceph fs subvolume snapshot clone <vol_name> <subvol_name> <snap_name> <target_subvol_name>
# 指定源所在组
ceph fs subvolume snapshot clone <vol_name> <subvol_name> <snap_name> <target_subvol_name> --group_name <subvol_group_name>
# 指定目标组
ceph fs subvolume snapshot clone <vol_name> <subvol_name> <snap_name> <target_subvol_name> --target_group_name <subvol_group_name>
# 指定目标 pool layout
ceph fs subvolume snapshot clone <vol_name> <subvol_name> <snap_name> <target_subvol_name> --pool_layout <pool_layout>
```
> 注意:`subvolume snapshot clone` 命令依赖下面提到的 `snapshot_clone_no_wait` 配置选项。
**查看克隆状态:**
```bash
ceph fs clone status <vol_name> <clone_name> [--group_name <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 <vol_name> <clone_name> [--group_name <group_name>] --force
```
> 注意:克隆只同步目录、普通文件和符号链接。inode 时间戳(访问时间与修改时间)同步到**秒级**粒度。
**取消克隆**:`in-progress` 或 `pending` 的克隆可以被取消:
```bash
ceph fs clone cancel <vol_name> <clone_name> [--group_name <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 <value>
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 <bool>
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 <vol_name> <group_name> <pin_type> <pin_setting>
# 子卷
ceph fs subvolume pin <vol_name> <group_name> <pin_type> <pin_setting>
```
| 参数 | 取值 |
|---|---|
| `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 <vol_name> <group_name> <setting> <value>` | 设置子卷组 charmap |
| `ceph fs subvolume charmap set <vol_name> <subvol> <--group_name=name> <setting> <value>` | 设置子卷 charmap |
| `ceph fs subvolumegroup charmap get <vol_name> <group_name> <setting>` | 读取子卷组某项 charmap |
| `ceph fs subvolume charmap get <vol_name> <subvol> <--group_name=name> <setting>` | 读取子卷某项 charmap |
| `ceph fs subvolumegroup charmap get <vol_name> <group_name>` | 读取子卷组完整 charmap |
| `ceph fs subvolume charmap get <vol_name> <subvol> <--group_name=name>` | 读取子卷完整 charmap |
| `ceph fs subvolumegroup charmap rm <vol_name> <group_name>` | 移除子卷组 charmap |
| `ceph fs subvolume charmap rm <vol_name> <subvol> <--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 <vol_name> --set-id myset1 <[group_name/]sub_name...> --await
# 在 IO 暂停生效期间执行操作,例如打快照
$ ceph fs quiesce <vol_name> --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=<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__<subvol-name>`。但这可能在不同 subvolume group 中出现同名 subvolume 时导致 namespace 冲突。因此 **Tentacle 之后**,pool namespace 格式改为 `fsvolumens__<subvol-grp-name>_<subvol-name>`。
> 引自:[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 <fs_name> 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 [<path>] [<fs>] [<format>]` | 打印某路径下所有 schedule 与 retention spec |
| `fs snap-schedule list <path> [--recursive] [<fs>] [<format>]` | 单行短格式列出 schedules,`--recursive` 递归列出整棵树 |
| `fs snap-schedule add <path> <snap_schedule> [<start>] [<fs>]` | 新增调度 |
| `fs snap-schedule remove <path> [<repeat>] [<start>] [<fs>]` | 删除调度 |
| `fs snap-schedule retention add <path> <retention_spec_or_period> [<retention_count>] [<fs>]` | 新增保留策略 |
| `fs snap-schedule retention remove <path> <retention_spec_or_period> [<retention_count>] [<fs>]` | 删除保留策略 |
| `fs snap-schedule activate <path> [<repeat>] [<start>] [<fs>]` | 激活调度 |
| `fs snap-schedule deactivate <path> [<repeat>] [<start>] [<fs>]` | 停用调度 |
#### 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」标识。一个路径**有且仅有一个**保留策略,但策略内可以包含多个「数量 + 周期」对。
- 语义:保证保留 `<number>` 个彼此至少相隔 `<time period>` 的快照。例如 `7d` 表示保留 7 个彼此至少相隔 1 天的快照。
- 支持的时间周期:`h`、`d`、`w`、`M`、`y` 以及特殊的 `n`。`10n` 表示「不管时间,保留最近 10 个快照」。
```bash
# 查看
ceph fs snap-schedule status /
ceph fs snap-schedule status /foo/bar --format=json
ceph fs snap-schedule list /
ceph fs snap-schedule list / --recursive=true
# 增/删调度
ceph fs snap-schedule add / 1h
ceph fs snap-schedule add / 1h 11:55
ceph fs snap-schedule add / 2h 11:55
ceph fs snap-schedule remove / 1h 11:55 # 删除单个精确匹配的 schedule
ceph fs snap-schedule remove / 1h # 删除所有 repeat=1h 的 schedule
ceph fs snap-schedule remove / # 删除该路径下所有 schedule
# 增/删保留策略
ceph fs snap-schedule retention add / h 24 # 保留 24 个间隔至少 1 小时的
ceph fs snap-schedule retention add / d 7 # 再保留 7 个间隔至少 1 天的
ceph fs snap-schedule retention remove / h 24
ceph fs snap-schedule retention add / 24h4w # 24 小时级 + 4 周级
ceph fs snap-schedule retention remove / 7d4w
```
#### 4.4.5 active / inactive 调度
- 可以为**尚不存在**的路径添加调度;路径被删除也不会影响已有调度。
- 当调度触发时目录不存在,该 schedule 会被置为 **inactive** 并排除在调度之外,直到重新 activate。
- 也可以手动 deactivate 来「暂停」某个调度。
```bash
ceph fs snap-schedule activate / # 激活根目录下所有 schedule
ceph fs snap-schedule deactivate / 1d # 停用根目录下的每日调度
```
#### 4.4.6 限制与注意事项
| 限制 | 说明 |
|---|---|
| 调度精度 | 用 Python Timer 实现;mgr 负载高时快照可能轻微延迟,但**不会累积漂移**(下一次仍按原时间表计算) |
| 卷删除 | 卷被删除而调度仍活跃时,可能看到 Python Traceback;此时只能 disable 再 enable `snap_schedule` 模块恢复 |
| 单目录快照数 | 模块自身限制**每个目录最多保留 50 个快照**,超出则截断为最新 50 个 |
| `mds_max_snaps_per_dir` | 该 MDS 参数默认 100;为保证能新建快照,实际最多保留 **99** 个 |
| 路径写法 | 传给 snap-schedule 的路径要**去掉挂载点前缀**。若 CephFS 挂载在 `/mnt`,要对 `/mnt/some/path` 打快照,应写 `/some/path` |
| 多 FS | 当存在多个文件系统时,`--fs` 参数是**必需的** |
| `created` 字段 | `status` 输出里的 `created` 是**调度被创建的时间**,与实际快照无关;实际快照累计数看 `created_count` |
#### 4.4.7 数据存储
调度数据存放在 CephFS **元数据池的一个 RADOS 对象**中;运行时全部数据位于一个 sqlite 数据库,该库序列化后作为 RADOS 对象保存。
> 引自:[Snapshot Scheduling Module](https://docs.ceph.com/en/tentacle/cephfs/snap-schedule/)
---
### 4.5 MDS 缓存配置(cache-configuration)
MDS 在所有 MDS 与 CephFS 客户端之间协调一个**分布式缓存**。缓存用于降低元数据访问延迟,并允许客户端安全地(一致地)修改元数据状态(例如 `chmod`)。MDS 通过发放 **capabilities**(caps)与 **directory entry leases** 来告知客户端可以缓存哪些状态、可以做哪些操作。
关键前提:**MDS 缓存大小不是硬限制**。MDS 永远允许客户端查找新元数据并载入缓存——这是避免客户端请求死锁的必要策略(某些请求可能依赖尚未释放的 capabilities)。
当缓存过大时,MDS 会 **recall**(召回)客户端状态,使缓存项解除 pin 从而可被丢弃。只有在没有客户端引用时 MDS 才能丢弃缓存状态。
#### 4.5.1 缓存大小
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_cache_memory_limit` | `size` | `4_G` | target maximum memory usage of MDS cache |
| `mds_cache_reservation` | `float` | `0.05` | amount of memory to reserve for future cached objects |
| `mds_health_cache_threshold` | `float` | `1.5` | threshold for cache size to generate health warning |
| `mds_cache_trim_interval` | `secs` | `1` | interval in seconds between cache trimming |
- `mds_cache_reservation` 是按内存百分比预留(默认 5%),用于给新的元数据操作保留余量,因此 MDS 一般会运行在内存限制**以下**。
- 当 MDS 无法把缓存控制在目标大小以内时,会向 Monitor 发送健康告警,阈值由 `mds_health_cache_threshold` 控制(默认 150%,即最大缓存的 1.5 倍)。
#### 4.5.2 缓存裁剪(trim)节流
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_cache_trim_threshold` | `size` | `256_K` | threshold for number of dentries that can be trimmed |
| `mds_cache_trim_decay_rate` | `float` | `1` | decay rate for trimming MDS cache throttle |
这两个参数控制一个内部**衰减计数器(decay counter)**:每裁剪一项元数据,计数器加一;`threshold` 设定计数器上限,`decay rate` 是指数半衰期。若 MDS 持续裁剪,稳态速率为:
```
-ln(0.5) / rate * threshold 项/秒
```
> ⚠️ 注意方向:`mds_cache_trim_decay_rate` **越大**,MDS 花在裁剪上的时间越少。想加快裁剪,应**调小**该值。
默认值偏保守,生产环境大缓存 MDS 通常需要调整。
#### 4.5.3 MDS Recall(召回客户端状态)
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_recall_max_caps` | `size` | `30000` | maximum number of caps to recall from client session in single recall |
| `mds_recall_max_decay_threshold` | `size` | `128_K` | decay threshold for throttle on recalled caps on a session |
| `mds_recall_max_decay_rate` | `float` | `1.5` | decay rate for throttle on recalled caps on a session |
| `mds_recall_global_max_decay_threshold` | `size` | `128_K` | decay threshold for throttle on recalled caps globally |
| `mds_recall_warning_threshold` | `size` | `256_K` | decay threshold for warning on slow session cap recall |
| `mds_recall_warning_decay_rate` | `float` | `60` | decay rate for warning on slow session cap recall |
- 单会话计数器:每召回一个 cap 加一,工作方式同 trim 计数器。
- 全局计数器:任意会话的任意召回也累加,decay rate 与 `mds_recall_max_decay_rate` 相同。
- 若客户端释放慢,会报 `MDS_HEALTH_CLIENT_RECALL`("failing to respond to cache pressure")。每次 cap 释放也累加到 warning 计数器。
#### 4.5.4 Cap 获取节流(Cap Acquisition Throttle)
一个在大目录树上执行的 `find`,会让客户端获取 cap 的速度远快于释放速度。MDS 会试图让客户端把 caps 降到 `mds_max_caps_per_client` 以下,但 recall 节流追不上获取速度,因此通过节流 **readdir** 来控制 cap 获取:
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_session_cap_acquisition_throttle` | `uint` | `100000` | threshold at which the cap acquisition decay counter throttles |
| `mds_session_cap_acquisition_decay_rate` | `float` | `30` | decay rate for session readdir caps leading to readdir throttle |
| `mds_session_max_caps_throttle_ratio` | `float` | `1.1` | ratio of mds_max_caps_per_client that client must exceed before readdir may be throttled by cap acquisition throttle |
| `mds_cap_acquisition_throttle_retry_request_timeout` | `float` | `0.5` | timeout in seconds after which a client request is retried due to cap acquisition throttling |
触发条件(两者同时满足):
1. 每会话 cap 获取数 > `mds_session_max_caps_throttle_ratio` × `mds_max_caps_per_client`
2. cap 获取衰减计数器 > `mds_session_cap_acquisition_throttle`
被节流的 readdir 请求会在 `mds_cap_acquisition_throttle_retry_request_timeout` 秒后重试。每次 readdir 调用按返回结果中的文件数累加计数器。
#### 4.5.5 会话活跃度(Session Liveness)
MDS 会跟踪会话是否「静默(quiescent)」。若客户端不使用其 caps 或很安静,即使**没有缓存压力**,MDS 也会开始召回状态,以此避免将来热点时才做这项工作。
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_session_cache_liveness_magnitude` | `size` | `10` | decay magnitude for preemptively recalling caps on quiet client |
| `mds_session_cache_liveness_decay_rate` | `float` | `5_min` | decay rate for session liveness leading to preemptive cap recall |
判定公式:客户端每操作或获取一个 cap,liveness 计数器加一(半衰期由 decay rate 给出)。`mds_session_cache_liveness_magnitude` 是 liveness 计数器与会话未释放 cap 数之间的 **base-2 数量级差**:
```
若客户端持有 1*2^20 (1M) 个 caps,而使用量 < 1*2^(20-10) (1K),
则 MDS 认为该客户端 quiescent,开始召回。
```
#### 4.5.6 单客户端 cap 上限
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_max_caps_per_client` | `uint` | `1_M` | maximum number of capabilities a client may hold |
| `mds_min_caps_per_client` | `uint` | `100` | minimum number of capabilities a client may hold |
不建议把 `mds_max_caps_per_client` 设到 5M 以上;限制单客户端 cap 数有助于缩短某些场景下的恢复时间。
#### 4.5.7 "clients failing to respond to cache pressure" 的处理
每隔 `mds_cache_trim_interval`(默认 1 秒),MDS 执行一次 cache trim,其中一个步骤是 "recall client state"。满足以下任一条件即需要召回 caps:
| 序号 | 条件 |
|---|---|
| 1 | 缓存已满(超过 `mds_cache_memory_limit`),需要释放一些 inode |
| 2 | 客户端超过 `mds_max_caps_per_client`(默认 1M) |
| 3 | 客户端处于非活跃状态 |
判定非活跃:比较会话的 `cache_liveness` 与 `(num_caps >> mds_session_cache_liveness_magnitude)`(`mds_session_cache_liveness_magnitude` 默认 10)。若 `cache_liveness` 小于该计算值,即视为非活跃,MDS 发送 "recall caps" 请求,召回数量为 `num_caps - mds_min_caps_per_client`(100)。
**示例场景**:客户端缓存 20k caps,被判定为非活跃,MDS 要求降到 100。每秒发送 `recall_caps = caps_num - 100`(不超过 `mds_recall_max_caps` = 30k)。若客户端每秒只释放 100 个:
- 第 1 秒:recall_caps = 20k - 100
- 第 2 秒:recall_caps = (20k-100) - 100
- 第 3 秒:recall_caps = (20k-200) - 100
会话的 `recall_caps` 计数器(统计最近一分钟发送的 recall 数)迅速增长,最终超过 `mds_recall_warning_threshold`(128K),集群开始报 "failing to respond to cache pressure"。
**调优路径**:
1. 先调 `mds_recall_max_decay_threshold`(默认 126K/128K)降低计数器增速;
2. 若仍不够,**递减** `mds_recall_max_caps`(例如 30k → 3K),直到告警消失。
> 💡 补充:把 `mds_recall_max_caps` 降到 3K 后,即便 MDS 每秒都发 3K,一分钟内计数器上限也只有 60 × 3K = 180K,需要客户端长时间不响应才可能再次触发告警。
> 引自:[MDS Cache Configuration](https://docs.ceph.com/en/tentacle/cephfs/cache-configuration/)
---
### 4.6 文件系统 Scrub
CephFS 提供一组 scrub 命令供管理员检查文件系统一致性。Scrub 分两类:
| 类型 | 说明 |
|---|---|
| **Forward Scrub** | 从文件系统根(或某个子目录)出发,遍历层次结构中所有可触及的对象,校验一致性 |
| **Backward Scrub** | 遍历文件系统池中的**每一个 RADOS 对象**,反查它在文件系统层次结构中的归属 |
> ⚠️ 警告:CephFS forward scrub 必须在 **rank 0** 上发起和控制,所有 scrub 命令都要发往 rank 0。
#### 4.6.1 发起 scrub
```bash
ceph tell mds.<fsname>:0 scrub start <path> [scrubopts] [tag]
```
- `scrubopts`:逗号分隔的列表,取值 `recursive`、`force`、`repair`
- `tag`:可选的自定义字符串标签(默认是生成的 UUID)
```bash
$ ceph tell mds.cephfs:0 scrub start / recursive
{
"return_code": 0,
"scrub_tag": "6f0d204c-6cfd-4300-9e02-73f382fd23c1",
"mode": "asynchronous"
}
```
- **recursive scrub 是异步的**(输出中的 `mode` 已提示),必须用 `scrub status` 轮询。
- `scrub_tag` 用于区分不同 scrub,同时会以 `scrub_tag` 扩展属性写到每个 inode 在**默认数据池**中的第一个数据对象上(backtrace 信息存放处)。可以用 RADOS 工具查看该 xattr 来验证某个 inode 是否被 scrub 过。
- Scrub 支持多 active MDS:由 rank 0 管理并分发到各 MDS。
#### 4.6.2 监控 scrub
```bash
$ ceph tell mds.cephfs:0 scrub status
{
"status": "scrub active (85 inodes in the stack)",
"scrubs": {
"6f0d204c-6cfd-4300-9e02-73f382fd23c1": {
"path": "/",
"options": "recursive"
}
}
}
```
- `status` 显示当前调度待 scrub 的 inode 数,每次调用都可能变化。
- `ceph status` 的 `task status` 段也会显示概要:
```text
task status:
scrub status:
mds.0: active [paths:/]
```
- scrub 完成的标准是它**不再出现在列表中**(未来版本可能变化)。
- 任何 damage 都会通过集群健康告警上报。
#### 4.6.3 控制 scrub
| 操作 | 命令 | 行为 |
|---|---|---|
| Pause | `ceph tell mds.cephfs:0 scrub pause` | 在飞行中的 RADOS op 完成后,不再 scrub 新的或待处理的 inode |
| Resume | `ceph tell mds.cephfs:0 scrub resume` | 重启被暂停的 scrub |
| Abort | `ceph tell mds.cephfs:0 scrub abort` | 在飞行 op 完成后,把待处理 inode 从 scrub 队列移除(中止 scrub) |
Pause 之后 `scrub status` 会显示 `PAUSED (66 inodes in the stack)`;此时再发起 `scrub start` 只是把 inode 排队。
#### 4.6.4 可上报与可修复的 damage 类型
| 类型 | 含义 |
|---|---|
| `DENTRY` | inode 的 dentry 缺失 |
| `DIR_FRAG` | inode 的目录 fragment(s) 缺失 |
| `BACKTRACE` | 数据池中该 inode 的 backtrace 损坏 |
修复命令:
```bash
ceph tell mds.<fsname>:0 scrub start /path recursive, repair, force
```
若修复成功,对应条目会自动从 damage table 中移除。
> 注意:带 `repair` 选项的 scrub **能识别**损坏的硬链接,但**不能修复**它。
#### 4.6.5 用 recursive scrub 评估 strays
```bash
# 评估/清理 ~mdsdir 下的 stray 目录
ceph tell mds.<fsname>:0 scrub start ~mdsdir recursive
# 在根目录做 stray 评估(~mdsdir 默认不入队),需同时给 scrub_mdsdir 与 recursive
ceph tell mds.<fsname>:0 scrub start / recursive,scrub_mdsdir
```
#### 4.6.6 导出 stray 目录内容
```bash
$ ceph tell mds.<fsname>:0 dump stray
{
"strays": [
{
"ino": "0x100000001f7",
"stray_prior_path": "/dir/dir1",
"client_caps": [
{
"client_id": 4156,
"pending": "pAsLsXsFscr",
"issued": "pAsLsXsFscr",
"wanted": "-",
"last_sent": 3
}
],
"loner": -1,
"want_loner": -1,
"mds_caps_wanted": [],
"is_subvolume": false
}
]}
```
#### 4.6.7 Scrub 相关配置项
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_max_scrub_ops_in_progress` | `int` | `5` | maximum number of scrub operations performed in parallel |
| `mds_scrub_stats_review_period` | `uint` | `1` | Period for which scrub stats will be available for review. |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
> 引自:[Ceph File System Scrub](https://docs.ceph.com/en/tentacle/cephfs/scrub/)
---
### 4.7 文件系统写满(full)
当 RADOS 集群达到 `mon_osd_full_ratio`(默认 95%)容量时,集群被标记 OSD full flag,该 flag 会让大多数普通 RADOS 客户端**暂停所有操作**直到问题解决(例如扩容)。CephFS 对 full flag 有特殊处理。
#### 4.7.1 Hammer 及以后版本的行为
文件系统写满后,以下操作会返回 **ENOSPC**:
| 操作 | 结果 |
|---|---|
| 客户端的**数据写入** | ENOSPC |
| 除 delete 和 truncate 之外的**元数据操作** | ENOSPC |
> 关键点:full 条件可能直到数据真正落盘(即 `write` 调用已返回 0 之后)才被发现,因此 ENOSPC 错误可能直到应用调用 `fsync` 或 `fclose`(或等价操作)时才出现。
| 调用 | 是否可靠反映落盘 |
|---|---|
| `fsync` | ✅ 保证可靠,数据未落盘会返回错误 |
| `fclose` | ❌ 仅当自上次 write 以来缓冲数据**恰好**被 flush 时才返回错误;成功的 `fclose` **不保证**数据落盘。在满空间场景下,缓冲数据可能在 `fclose` 后被丢弃 |
> ⚠️ 警告:若发现应用在写满的文件系统上行为异常,先检查它是否按要求调用了 `fsync()`。
**在途写入的取消**:当 OSD full flag 发出时仍在飞行中的数据写入可能被客户端取消。客户端在释放受影响文件的 capabilities 时会更新 `osd_epoch_barrier`,以确保这些被取消的操作不会干扰 MDS 或其他客户端后续对数据对象的访问。
#### 4.7.2 旧版(pre-hammer)行为
Hammer 之前,MDS 会忽略 RADOS 集群的 full 状态,客户端数据写入会一直 stall 直到集群不再满。该行为有两个危险场景:
1. 若客户端有待写入数据,它就无法把文件交还 MDS 删除 → 写满的文件系统难以清理空间;
2. 若客户端持续创建大量空文件,MDS 产生的元数据写入可能彻底耗尽 OSD 空间,导致连删除都无法执行。
> 引自:[Handling a full Ceph file system](https://docs.ceph.com/en/tentacle/cephfs/full/)
---
### 4.8 LazyIO
**LazyIO 放宽了 POSIX 语义**:即使一个文件被多个客户端上的多个应用同时打开,也允许缓冲读/写。**缓存一致性的责任交给应用自己**。
- libcephfs 从 Nautilus 版本开始支持 LazyIO。
#### 4.8.1 启用方式
| 方式 | 作用范围 |
|---|---|
| `client_force_lazyio` 选项 | 对 libcephfs 和 ceph-fuse 挂载**全局**启用 LAZY_IO |
| `ceph_lazyio(...)` / `ceph_ll_lazyio(...)` | 对 libcephfs 中的**单个文件句柄**启用 LAZY_IO |
#### 4.8.2 两个方法
| 方法 | 语义 |
|---|---|
| `lazyio_propagate(int fd, loff_t offset, size_t count)` | 确保客户端在 `[offset, offset+count)` 区间的缓冲写已传播到共享文件。`offset` 与 `count` 都为 0 时作用于整个文件(**目前仅支持这种用法**) |
| `lazyio_synchronize(int fd, loff_t offset, size_t count)` | 确保客户端在随后的 read 中能读到包含了其他客户端已传播写入的最新数据。实现方式是使该 inode 的文件缓存失效,强制客户端重新拉取。若调用方自身的写缓存是脏的(未传播),`lazyio_synchronize()` 也会先 flush |
LazyIO 启用后:
- 写:在调用 `lazyio_propagate()` 之前,**其他客户端可能看不到**;
- 读:在调用 `lazyio_synchronize()` 之前,可能来自本地缓存,**忽略其他客户端的改动**。
#### 4.8.3 libcephfs 示例 I/O 循环
```c
/* Client a (ca) opens the shared file file.txt */
int fda = ceph_open(ca, "shared_file.txt", O_CREAT|O_RDWR, 0644);
/* Enable LazyIO for fda */
ceph_lazyio(ca, fda, 1));
for(i = 0; i < num_iters; i++) {
char out_buf[] = "fooooooooo";
ceph_write(ca, fda, out_buf, sizeof(out_buf), i);
/* Propagate the writes associated with fda to the backing storage */
ceph_propagate(ca, fda, 0, 0);
/* The barrier makes sure changes associated with all file descriptors
are propagated so that there is certainty that the backing file
is up to date */
application_specific_barrier();
char in_buf[40];
/* Calling ceph_lazyio_synchronize here will ascertain that ca will
read the updated file with the propagated changes and not read
stale cached data */
ceph_lazyio_synchronize(ca, fda, 0, 0);
ceph_read(ca, fda, in_buf, sizeof(in_buf), 0);
/* A barrier is required here before returning to the next write
phase so as to avoid overwriting the portion of the shared file still
being read by another file descriptor */
application_specific_barrier();
}
```
> 引自:[LazyIO](https://docs.ceph.com/en/tentacle/cephfs/lazyio/)
---
### 4.9 目录分片(Directory Fragmentation, dirfrags)
CephFS 中,目录在变得**非常大**或**非常繁忙**时会被**分片(fragmented)**。分片把元数据拆开,从而可以在多个 MDS 守护进程之间、以及元数据池的多个对象之间共享。
- 正常运行中,目录分片对用户和管理员**不可见**,本节所有配置项都应保持默认值。
- 虽然分片让 CephFS 能处理单目录中极大量的条目,应用开发者仍应**保守地**创建超大目录:列举目录时所有 fragment 必须一次性加载,仍有资源代价。
> 💡 提示:**根目录不能被分片。**
所有目录初始都是单个 fragment,之后可以 **split**(分裂成更多 fragment)或 **merge**(合并以减少 fragment 数)。
#### 4.9.1 Split 与 Merge 的机制
- 当 MDS 识别出需要 split 的 fragment 时**不会立即执行**——因为 split 会中断元数据 IO,会先有一个短延迟让突发的客户端 IO 完成,延迟由 `mds_bal_fragment_interval`(默认 5 秒)配置。
- Split 时,fragment 被拆成 **2 的幂** 个新 fragment,数量由 `mds_bal_split_bits` 决定:若 `mds_bal_split_bits` = 2 则产生 4 个新 fragment,默认 3 即每次 split 产生 **8** 个新 fragment。
#### 4.9.2 分片相关配置项
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_bal_split_size` | `int` | `10000` | minimum size of directory fragment before splitting |
| `mds_bal_merge_size` | `int` | `50` | size of fragments where merging should occur |
| `mds_bal_fragment_size_max` | `int` | `100000` | maximum size of a directory fragment before new creat/links fail |
| `mds_bal_fragment_interval` | `int` | `5` | delay in seconds before interrupting client IO to perform splits |
| `mds_bal_fragment_fast_factor` | `float` | `1.5` | ratio of mds_bal_split_size at which fast fragment splitting occurs |
| `mds_bal_split_rd` | `float` | `25000` | hot read popularity threshold for splitting a directory fragment |
| `mds_bal_split_wr` | `float` | `10000` | hot write popularity threshold for splitting a directory fragment |
| `mds_bal_split_bits` | `int` | `3` | power of two child fragments for a fragment on split |
| `mds_decay_halflife` | `float` | `5` | rate of decay for temperature counters on each directory for balancing |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
#### 4.9.3 大小阈值
| 阈值 | 行为 |
|---|---|
| 超过 `mds_bal_split_size`(默认 10000 条目) | 具备 split 资格;通常延迟 `mds_bal_fragment_interval` 后执行 |
| 超过 `mds_bal_split_size` × `mds_bal_fragment_fast_factor`(1.5) | **立即 split**,会阻塞该目录上的客户端元数据 IO |
| 达到 `mds_bal_fragment_size_max`(默认 100000,即 split size 的 10 倍) | **硬限制**,客户端在该 fragment 内创建文件会收到 **ENOSPC**。配置合理的系统不应触及——普通目录早就 split 了。增大该值可能导致元数据池中出现过大的 dirfrag 对象,OSD 可能无法处理 |
| 低于 `mds_bal_merge_size`(默认 50 条目) | 具备 merge 资格 |
> 注意:merge **没有**对应的 "fast" 机制——fast split 是为了避免出现超大 dirfrag,而 merge 没有需要规避的对称问题。
#### 4.9.4 活跃度阈值
除了按大小 split,MDS 还会按**活跃度** split:
- MDS 为目录 fragment 的读、写操作分别维护**时间衰减负载计数器**,衰减基于 `mds_decay_halflife`(指数衰减)。
- 写操作(包括 rename、unlink、create 等元数据 IO)累加写计数器,超过 `mds_bal_split_wr`(默认 10000)触发 split。
- `mds_bal_split_rd`(默认 25000)作用于读计数器,主要跟踪 readdir 操作。
- 这两个配置表示「热度阈值」(read/write temperature)。默认读阈值是写的 2.5 倍,即需要 2.5 倍的读操作量才会触发 split。
- **因活跃度 split 出去的 fragment,只会依据大小阈值(`mds_bal_merge_size`)合并**,因此一次突发活跃可能导致目录**永久保持分片状态**,除非有条目被删除。
> 引自:[Configuring Directory fragmentation](https://docs.ceph.com/en/tentacle/cephfs/dirfrags/)
---
### 4.10 动态元数据管理(Dynamic Metadata Management)
元数据操作通常占文件系统全部操作的 **50% 以上**,而且元数据的扩展方式比存储扩展(线性提升 IO 吞吐)复杂得多,这是由文件系统元数据的层次化与相互依赖特性决定的。因此 CephFS 把元数据负载与数据负载**解耦**,由一组 MDS 承担元数据负载。CephFS 通过 **Dynamic Subtree Partitioning**(动态子树分区)在 MDS 之间分布元数据。
#### 4.10.1 从传统子树分区到动态子树分区
| 策略 | 优点 | 缺点 |
|---|---|---|
| 传统子树分区 | 良好的层次局部性、缓存线性增长、跨 MDS 水平扩展、元数据分布较均匀 | 负载沿**深度**方向增长会在单个 MDS 上形成热点 → 缺乏垂直扩展能力,非繁忙 MDS 资源被浪费 |
| 动态子树分区 | 在上述优点之外,把繁忙 MDS 上**负载密集的目录层次迁移**到非繁忙 MDS → 热点出现即被缓解,同时获得垂直扩展能力 | — |
#### 4.10.2 子树迁移(Export)流程中的消息
导出前置条件:exporter 校验子树允许导出(集群非 degraded、子树根未 frozen)。校验通过后,子树根目录被临时 **auth pin**,启动 **subtree freeze**,此后 exporter 承诺完成迁移(除非 exporter 或 importer 自身故障)。
| 步骤 | 消息/日志事件 | 说明 |
|---|---|---|
| 1 | `MExportDiscover` / `MDiscoverAck` | 确保待导出基目录的 inode 在目的节点上已打开;importer 对其 auth pin 以防被 trim。发生在 exporter 完成 freeze **之前**,以保证 importer 能复制必要元数据。收到 `MDiscoverAck` 后,exporter 移除临时 auth pin,允许 freeze 继续 |
| 2 | `MExportWarning` | **仅当**基目录还被 importer/exporter 之外的节点打开时出现。通知 bystander:该区域的 authority 暂时不确定,exporter 与 importer 都是权威 MDS。bystander 在 trim 缓存项时必须向**新旧两个权威**都发 `MCacheExpire`,以确保即使 importer 或 exporter 失败,存活的权威也能可靠收到所有过期通知。freeze 期间 bystander 暂不处理过期,先排队,直到 unfreeze 后确定权威归属 |
| 3 | `MExport` | exporter 打包子树的全部元数据并把对象标记为 non-authoritative,发送给 importer |
| 4 | `EImportStart`(journal) | importer 收到后插入缓存、把对象标记为 authoritative,并在一条 `EImportStart` journal 消息中记录全部元数据副本。**安全落盘后**才回复 |
| 5 | `MExportAck` | importer 回复;exporter 此时才写 `EExport` journal 条目 |
| 6 | `EExport`(journal) | 最终指明导出成功。出现故障时,**只有** `EExport` 条目的存在与否能在恢复期间消除 authority 的歧义 |
| 7 | `MExportNotify` | exporter 通知所有 bystander:authority 不再有歧义,缓存过期通知只发给新权威(importer) |
| 8 | `MExportFinish` | bystander 确认后(隐式 flush 了 stray expiration 消息流),exporter unfreeze 子树、清理迁移状态并发送最终的 `MExportFinish` |
| 9 | `EImportFinish(true)`(journal) | importer 收到后记录导出确实成功,unfreeze 自己的子树,处理排队的缓存过期,清理状态 |
> 相关配置项见表 4.10.3(与第 2.4 节 multimds 中的 `mds_export_ephemeral_*`、`mds_bal_*` 系列一致)。
#### 4.10.3 与子树迁移/均衡相关的配置项
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_export_ephemeral_random` | `bool` | `true` | 是否允许把目录随机 pin 到某个 rank(ephemeral random pinning) |
| `mds_export_ephemeral_random_max` | `float` | `0.01` | 随机 pin 触发的比例上限(源文给出的默认值为 .01) |
| `mds_export_ephemeral_distributed` | `bool` | `true` | 是否允许把目录分布式 pin(ephemeral distributed pinning) |
| `mds_thrash_exports` | `uint` | `0` | 测试用:随机导出子树(非 0 时启用) |
| `mds_thrash_fragments` | `uint` | `0` | 测试用:随机分裂/合并 fragment |
| `mds_bal_interval` | `int` | `10` | 负载均衡器执行频率(秒) |
| `mds_bal_sample_interval` | `float` | `3` | 采样间隔 |
| `mds_decay_halflife` | `float` | `5` | 目录温度计数器的衰减速率 |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
> 引自:[CephFS Dynamic Metadata Management](https://docs.ceph.com/en/tentacle/cephfs/dynamic-metadata-management/)
---
### 4.11 分布式元数据缓存(mdcache)
CephFS 中 inode 的**数据**存放在 RADOS、由客户端**直接访问**;而 inode 元数据与目录信息由 MDS 管理,并存放在**独立于文件数据的 RADOS 池**(元数据池)中。
#### 4.11.1 Capabilities 与缓存一致性
| 环节 | 说明 |
|---|---|
| 请求路径 | 客户端可请求 MDS 代其获取/修改 inode 元数据;MDS 也可以授予客户端每个 inode 的 **capabilities(caps)** |
| cap 的作用 | 授予客户端缓存、乃至修改该 inode 部分数据/元数据的能力 |
| 冲突处理 | 当另一客户端需要访问同一信息时,MDS **撤销(revoke)** 该 cap,客户端最终归还 cap,并带回更新后的 inode 元数据(若期间做过修改) |
| 撤销失败 | 客户端一般会拿到它请求的 caps,但在竞争访问或 MDS 内存压力时会被撤销。未及时归还的客户端可能被 **blocklisted**,从而无法与集群通信 |
| 一致性级别 | 由于缓存是分布式的,MDS 必须严格保证没有客户端持有相互冲突的 caps。这使得 CephFS 客户端能获得**远高于 NFS** 的缓存一致性(NFS 客户端可能在服务端数据已变更之后仍缓存着旧数据) |
#### 4.11.2 客户端元数据请求的两种选择
1. **向 MDS 发请求**:直接查询或请求修改元数据。请求的**应答**可能同时授予该 inode 的一组 caps,使后续请求无需再咨询 MDS。
2. **从自身缓存提供服务**:只有在持有必要 caps 时才可行。
客户端也可以**直接**向 MDS 请求 caps——这是读写文件数据的必要条件。
#### 4.11.3 MDS 集群中的分布式锁
| 规则 | 说明 |
|---|---|
| 加锁 | MDS 想要读取或修改某个 inode 的信息,必须先收集相应的锁 |
| 锁的多样性 | MDS 集群对同一 inode 可能有多种不同类型的锁,每个 MDS 可能持有互不相交的锁集合 |
| 撤销优先 | 若存在与所需锁冲突的未释放 caps,必须**先撤销**这些 caps,MDS 才能拿到锁并执行操作 |
| 权威 MDS | 任一时刻,对某个 inode **只有一个 MDS 是 authoritative**。所有修改该 inode 的请求必须由 authoritative MDS 执行;非权威 MDS 可以把请求**转发**给它 |
| 非权威 MDS 的读锁 | 非权威 MDS 也可以获取**读锁**,阻止 auth MDS 修改数据直到锁释放,从而能自行向客户端提供 inode 信息 |
| 权威转移 | inode 的 auth MDS 会随时间变化:MDS 之间会主动均衡 inode 缓存责任,但可以通过把某些子树 **pin** 到单个 MDS 来覆盖这一行为 |
> 引自:[CephFS Distributed Metadata Cache](https://docs.ceph.com/en/tentacle/cephfs/mdcache/)
---
### 4.12 MDS Journaling
#### 4.12.1 CephFS 元数据池
CephFS 使用**独立的元数据池**管理文件元数据(inode 与 dentry)。元数据池包含了 CephFS 中所有文件的信息,包括文件系统层次结构。此外,CephFS 还在其中维护其他实体的元信息,例如**文件系统日志(journal)**、**打开文件表(open file table)**、**会话表(session map)** 等。
#### 4.12.2 为什么需要 journaling
CephFS 元数据服务器在执行文件系统操作**之前**,先把元数据事件以 journal 的形式流式写入元数据池中的 RADOS。
| 目的 | 说明 |
|---|---|
| **一致性** | MDS 故障切换时,可重放 journal 事件以恢复到一致的文件系统状态。需要多次更新后端存储才能完成的元数据操作也必须 journal 以保证崩溃一致性(配合锁等其他一致性机制) |
| **性能** | Journal 更新(大部分)是顺序的,因此很快;多次更新可以**批量合并**为一次写,节省更新不同文件位置时的磁盘寻道时间。较大的 journal 还能帮助 standby MDS **预热缓存**,间接有利于 MDS failover |
- 每个 active MDS 在元数据池中维护**自己的** journal。
- Journal 条带化(striped)分布于多个对象之上。
- 不再需要的(被视为旧的)journal 条目会被元数据服务器 **trim**。
#### 4.12.3 Journal 事件类型
除了文件系统元数据更新,CephFS 还会 journal 各种其他事件,例如客户端会话信息、目录 import/export 状态等。这些事件用于让元数据服务器在需要时重建正确状态——例如 MDS 重启后重放 journal 时,某个事件类型指明某个客户端实体在重启前与该 MDS 存在会话,MDS 就会尝试重连该客户端。
查看 journal 中记录的事件列表:
```bash
cephfs-journal-tool --rank=<fs>:<rank> event get list
```
全部 16 种事件类型:
| 事件类型 | 说明 |
|---|---|
| `EVENT_COMMITTED` | 把一个请求(id)标记为已提交 |
| `EVENT_EXPORT` | 把目录映射到某个 MDS rank |
| `EVENT_FRAGMENT` | 跟踪目录分片(split/merge)的各个阶段 |
| `EVENT_IMPORTSTART` | 某 MDS rank 开始 import 目录 fragment 时记录 |
| `EVENT_IMPORTFINISH` | 某 MDS rank 完成 import 目录 fragment 时记录 |
| `EVENT_NOOP` | 空操作事件,用于跳过 journal 的某个区域 |
| `EVENT_OPEN` | 跟踪哪些 inode 有打开的文件句柄 |
| `EVENT_RESETJOURNAL` | 截断之后把 journal 标记为 `reset` |
| `EVENT_SESSION` | 跟踪打开的客户端会话 |
| `EVENT_SLAVEUPDATE` | 记录被转发给(slave)mds 的操作的各个阶段 |
| `EVENT_SUBTREEMAP` | 目录 inode 到目录内容(子树分区)的映射 |
| `EVENT_TABLECLIENT` | 记录 MDS 视角下客户端表(snap/anchor)的状态变迁 |
| `EVENT_TABLESERVER` | 记录 MDS 视角下服务端表(snap/anchor)的状态变迁 |
| `EVENT_UPDATE` | 记录对某 inode 的文件操作 |
| `EVENT_SEGMENT` | 记录一个新的 journal segment 边界 |
| `EVENT_LID` | 标记一个没有逻辑子树映射的 journal 的开始 |
#### 4.12.4 Journal Segment(LogSegment)
MDS journal 由逻辑段组成,代码中称为 **LogSegment**。Segment 的作用是把多个事件的元数据更新**收集成一个逻辑单元**,以便 trim。
- 每当 journal 尝试提交元数据操作(例如把一个 file create 作为 omap 更新 flush 到 dirfrag 对象)时,都以 LogSegment 中**可重放的一批更新**的形式进行。之所以可重放,是因为 MDS 可能在这一系列针对多个元数据对象的更新过程中失败。
- 批量提交的原因是把对同一元数据对象(一个 dirfrag)的更新归组——同一时间段内可能有多个 omap 条目被更新。
- Segment 被 trim 后即视为 **expired**;expired segment 的所有更新都已 flush 到后端 RADOS 对象,因此可被 journaler 删除,做法是把 journaler 的 "expire position" 推进到该 segment 末尾之后。部分 expired segment 会被**保留在 journal 中**,以改善 MDS 重启时的缓存局部性。
**Major / Minor segment 的演进**:
| 时期 | 行为 |
|---|---|
| 2023 年以前(CephFS 历史上绝大部分时间) | journal segment 由**子树映射**即 `ESubtreeMap` 事件界定。主要原因是 journal 恢复必须先拿到一份 subtree map 的副本,然后才能重放其他事件 |
| 2023 年起 | segment 可以由任何 `SegmentBoundary` 事件界定,包括 `ESubtreeMap`、`EResetJournal`、`ESegment`(2023)、`ELid`(2023) |
- `ESegment`:**轻量级** segment 边界,使 MDS 可以**降低** subtree map 的 journal 频率,同时保持 segment 较小以便 trim 事件短小。
- 为了维持「journal 重放看到的第一个事件必须是 `ESubtreeMap`」这一约束,以该事件开头的 segment 被称为 **major segment**,并新增了一条针对 expired segment 删除的约束:**journal 的第一个 segment 必须始终是 major segment**。
- `ELid`:用于把 MDS journal 标记为 "new"——此时需要一个逻辑上的 `LogSegment` 与日志序列号,以便其他操作(特别是 **MDSTable** 操作)能够进行。MDS 在**创建 rank** 或**关闭 rank** 时使用此事件。从这个初始状态重放 rank 时**不需要** subtree map。
#### 4.12.5 Journaling 相关配置项
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_log_events_per_segment` | `uint` | `1024` | maximum number of events in an MDS journal segment(以事件数衡量的 segment 目标大小) |
| `mds_log_minor_segments_per_major_segment` | `uint` | `16` | number of minor segments per major segment;控制 MDS trim 过期 segment 的频率(值越大,MDS 更新 journal expiry position 越不频繁) |
| `mds_log_max_segments` | `uint` | `128` | maximum number of segments which may be untrimmed(目标 segment 数上限) |
| `mds_log_warn_factor` | `float` | `2` | trigger MDS_HEALTH_TRIM warning when the mds log is longer than mds_log_max_segments * mds_log_warn_factor |
| `mds_log_skip_corrupt_events` | `bool` | `false` | 重放时是否跳过损坏的事件 |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
>
> 💡 补充:由于存在等待 trim 到下一个 major segment 的非 major segment,MDS 实际驻留的 segment 数通常会**略高于** `mds_log_max_segments`。
> 引自:[MDS Journaling](https://docs.ceph.com/en/tentacle/cephfs/mds-journaling/)
---
### 4.13 Journaler
Journaler 是 MDS(以及 cephfs-mirror 等组件)用于读写 RADOS 中 journal 对象的底层组件。该页面仅列出三个配置项:
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `journaler_write_head_interval` | `int` | `15` | Interval in seconds between journal header updates (to help bound replay time) |
| `journaler_prefetch_periods` | `uint` | `10` | Number of striping periods to prefetch while reading MDS journal |
| `journaler_prezero_periods` | `uint` | `5` | Number of striping periods to zero head of MDS journal write position |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
> 💡 补充:`journaler_write_head_interval` 的作用是周期性更新 journal header,从而把「重放时需要从头扫描」的范围限定住,直接**约束 replay 时间**;`journaler_prefetch_periods` 与 `journaler_prezero_periods` 分别影响读 journal 时的预取量和写 journal 时预置零(prezero)的量,两者都以 striping period 为单位。
> 引自:[Journaler](https://docs.ceph.com/en/tentacle/cephfs/journaler/)
---
### 4.14 Purge Queue(删除队列)
MDS 维护一个名为 **Purge Queue** 的数据结构,负责管理并执行文件的**并行删除**。每个 MDS rank 各有一个 purge queue。Purge queue 由 **purge items** 组成,其中只保留 inode 的名义信息,如大小和 layout(即丢弃所有其他不需要的元数据信息,使其独立于所有元数据结构)。
#### 4.14.1 删除流程
当客户端请求删除一个目录(例如 `rm -rf`)时:
| 步骤 | 说明 |
|---|---|
| 1 | MDS 把来自 pq(purge queue)journal 的文件与子目录(purge items)排入 purge queue |
| 2 | 在后台以**小而可控的分块**处理 inode 的删除 |
| 3 | MDS 指示底层 OSD 清理数据池中关联的对象 |
| 4 | 更新 journal |
> ⚠️ 注意:如果用户删除文件的速度**快于** purge queue 的处理速度,数据池使用量可能随时间**大幅增长**。极端场景下 purge queue 积压会大到拖慢容量回收,并且 Linux `du` 命令对 CephFS 的报告可能与 CephFS 数据池的实际情况**不一致**。
#### 4.14.2 可调参数
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `filer_max_purge_ops` | `uint` | `10` | Max in-flight operations for purging a striped range (e.g., MDS journal) |
| `mds_max_purge_files` | `uint` | `64` | maximum number of deleted files to purge in parallel |
| `mds_max_purge_ops` | `uint` | `8_K` | maximum number of purge operations performed in parallel |
| `mds_max_purge_ops_per_pg` | `float` | `0.5` | number of parallel purge operations performed per PG |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
一般默认值对大多数集群足够。但在**非常大的集群**中,如果出现 `pq_item_in_journal`(待删除项计数器)达到天文数字这类需求,可以先把这些配置调到默认值的 **4~5 倍**作为起点,再按需继续增加。
推荐调优顺序——先调最不起眼的 `filer_max_purge_ops`,它通常就足以加快空间回收:
```bash
$ ceph config set mds filer_max_purge_ops 40
```
若还不够,再继续调整其他几个:
```bash
$ ceph config set mds mds_max_purge_files 256
$ ceph config set mds mds_max_purge_ops 32768
$ ceph config set mds mds_max_purge_ops_per_pg 2
```
> ⚠️ 注意:调大这些值不会立刻破坏什么(它们只是控制向底层 RADOS 集群发出的 delete op 数量),但如果设得过高,会**吃掉一部分集群性能**。
>
> ⚠️ 注意:purge queue **不是**自动调优系统(不会依据当前状况自适应工作上限)。调整时请根据集群规模与负载**审慎决策**。
#### 4.14.3 Purge Queue perf counters
分析 MDS perf dump 时,purge queue 统计形如:
```json
"purge_queue": {
"pq_executing_ops": 56655,
"pq_executing_ops_high_water": 65350,
"pq_executing": 1,
"pq_executing_high_water": 3,
"pq_executed": 25,
"pq_item_in_journal": 6567004
}
```
| 名称 | 含义 |
|---|---|
| `pq_executing_ops` | 飞行中的 purge queue 操作数 |
| `pq_executing_ops_high_water` | 记录到的最大飞行 purge 操作数 |
| `pq_executing` | 正在被删除的 purge queue 文件数 |
| `pq_executing_high_water` | 记录到的最大并行文件删除数 |
| `pq_executed` | 已删除的 purge queue 文件数 |
| `pq_item_in_journal` | journal 中剩余的 purge item(文件)数 |
> 💡 补充:`pq_executing` 与 `pq_executing_ops` 看似相似但有细微差别:`pq_executing` 统计 purge queue 中的**文件数**,而 `pq_executing_ops` 是 purge queue 中所有文件对应的 **RADOS 对象数**。
> 引自:[Purge Queue](https://docs.ceph.com/en/tentacle/cephfs/purge-queue/)
---
### 4.15 Metrics(客户端指标)
CephFS 使用 **Perf Counters** 跟踪指标,且这些计数器是**带标签的(Labeled Perf Counters)**。
#### 4.15.1 客户端指标一览
| 名称 | 类型 | 说明 |
|---|---|---|
| `num_clients` | Gauge | 客户端会话数 |
| `cap_hits` | Gauge | 文件 capability 命中数占总 caps 数的百分比 |
| `cap_miss` | Gauge | 文件 capability 未命中数占总 caps 数的百分比 |
| `avg_read_latency` | Gauge | 读延迟的均值 |
| `avg_write_latency` | Gauge | 写延迟的均值 |
| `avg_metadata_latency` | Gauge | 元数据延迟的均值 |
| `dentry_lease_hits` | Gauge | 已发出的 dentry lease 命中数占总 dentry lease 请求数的百分比 |
| `dentry_lease_miss` | Gauge | dentry lease 未命中数占总 dentry lease 请求数的百分比 |
| `opened_files` | Gauge | 已打开文件数 |
| `opened_inodes` | Gauge | 已打开 inode 数 |
| `pinned_icaps` | Gauge | 被 pin 的 Inode Caps 数 |
| `total_inodes` | Gauge | Inode 总数 |
| `total_read_ops` | Gauge | 所有进程产生的读操作总数 |
| `total_read_size` | Gauge | 所有进程产生的 I/O 操作中读取的字节数 |
| `total_write_ops` | Gauge | 所有进程产生的写操作总数 |
| `total_write_size` | Gauge | 所有进程产生的 I/O 操作中写入的字节数 |
#### 4.15.2 获取指标
指标既可以从 MDS admin socket 抓取,也可以通过 `tell` 接口获取。`counter dump` 命令输出中的 `mds_client_metrics-<fsname>` 段按客户端展示指标:
```json
"mds_client_metrics": [
{
"labels": {
"fs_name": "<fsname>",
"id": "14213"
},
"counters": {
"num_clients": 2
}
}
],
"mds_client_metrics-<fsname>": [
{
"labels": {
"client": "client.0",
"rank": "0"
},
"counters": {
"cap_hits": 5149,
"cap_miss": 1,
"avg_read_latency": 0.000000000,
"avg_write_latency": 0.000000000,
"avg_metadata_latency": 0.000000000,
"dentry_lease_hits": 0,
"dentry_lease_miss": 0,
"opened_files": 1,
"opened_inodes": 2,
"pinned_icaps": 2,
"total_inodes": 2,
"total_read_ops": 0,
"total_read_size": 0,
"total_write_ops": 4836,
"total_write_size": 633864192
}
},
{
"labels": {
"client": "client.1",
"rank": "0"
},
"counters": {
"cap_hits": 3375,
"cap_miss": 8,
"avg_read_latency": 0.000000000,
"avg_write_latency": 0.000000000,
"avg_metadata_latency": 0.000000000,
"dentry_lease_hits": 0,
"dentry_lease_miss": 0,
"opened_files": 1,
"opened_inodes": 2,
"pinned_icaps": 2,
"total_inodes": 2,
"total_read_ops": 0,
"total_read_size": 0,
"total_write_ops": 3169,
"total_write_size": 415367168
}
}
]
```
> 引自:[Metrics](https://docs.ceph.com/en/tentacle/cephfs/metrics/)
---
### 4.16 `cephfs-top` 实用工具
CephFS 提供一个类似 `top(1)` 的工具,用于**实时**展示各种 Ceph 文件系统指标。`cephfs-top` 是一个基于 curses 的 Python 脚本,利用 Ceph Manager 的 `stats` 插件获取(并展示)指标。
#### 4.16.1 Manager 插件与数据流
| 环节 | 说明 |
|---|---|
| 采集 | CephFS 客户端周期性把各种指标转发给 MDS |
| 汇聚 | 每个 active MDS 把自己的指标集转发给 **MDS rank 0** |
| 上报 | rank 0 聚合并转发给 Ceph Manager |
指标分两类:
| 类别 | 说明 | 示例 |
|---|---|---|
| global | 文件系统整体的指标 | 客户端读延迟 |
| per-mds | 某个具体 MDS rank 的指标 | 某 MDS 处理的子树数 |
> 注意:**目前只跟踪 global 指标。**
`stats` 插件**默认禁用**,需显式启用:
```bash
$ ceph mgr module enable stats
```
启用后可通过以下命令获取 CephFS 指标:
```bash
$ ceph fs perf stats
```
输出为 JSON,字段如下:
| 字段 | 说明 |
|---|---|
| `version` | stats 输出的版本 |
| `global_counters` | global 性能指标列表 |
| `counters` | per-mds 性能指标列表 |
| `client_metadata` | CephFS 客户端元数据 |
| `global_metrics` | global 性能计数器 |
| `metrics` | per-MDS 性能计数器(当前为空)以及 delayed ranks |
> 💡 补充:`delayed_ranks` 是**正在上报过期指标**的 active MDS rank 集合。常见成因是 MDS rank 0 与其他 active MDS 之间出现(临时性)网络问题。
按客户端或按 MDS 子集过滤:
```bash
# 只取某个客户端(client-id: 1234)的指标
$ ceph fs perf stats --client_id=1234
# 只取部分 active MDS(rank 1 和 2)的指标
$ ceph fs perf stats --mds_rank=1,2
```
#### 4.16.2 运行 `cephfs-top`
`cephfs-top` 依赖 `stats` 插件获取性能指标并以 `top(1)` 风格展示,随 `cephfs-top` 软件包发布。
默认使用 `client.fstop` 用户连接集群:
```bash
$ ceph auth get-or-create client.fstop mon 'allow r' mds 'allow r' osd 'allow r' mgr 'allow r'
$ cephfs-top
```
#### 4.16.3 字段说明
| # | 字段 | 含义 |
|---|---|---|
| 1 | `chit` | Cap hit —— 文件 capability 命中数占总 caps 数的百分比 |
| 2 | `dlease` | Dentry lease —— 已发出的 dentry lease 占总 dentry lease 请求数的百分比 |
| 3 | `ofiles` | Opened files —— 已打开文件数 |
| 4 | `oicaps` | Pinned caps —— 被 pin 的 caps 数 |
| 5 | `oinodes` | Opened inodes —— 已打开 inode 数 |
| 6 | `rtio` | Total size of read IOs —— 所有进程产生的 I/O 操作中读取的字节数 |
| 7 | `wtio` | Total size of write IOs —— 所有进程产生的 I/O 操作中写入的字节数 |
| 8 | `raio` | Average size of read IOs —— 读取字节数的均值(相对总 IO 次数) |
| 9 | `waio` | Average size of write IOs —— 写入字节数的均值(相对总 IO 次数) |
| 10 | `rsp` | Read speed —— 相对于上次刷新间隔的读速度 |
| 11 | `wsp` | Write speed —— 相对于上次刷新间隔的写速度 |
| 12 | `rlatavg` | Average read latency —— 读延迟均值 |
| 13 | `rlatsd` | 读延迟的标准差(离散度,相对其均值) |
| 14 | `wlatavg` | Average write latency —— 写延迟均值 |
| 15 | `wlatsd` | 写延迟的标准差(离散度,相对其均值) |
| 16 | `mlatavg` | Average metadata latency —— 元数据延迟均值 |
| 17 | `mlatsd` | 元数据延迟的标准差(离散度,相对其均值) |
#### 4.16.4 命令行选项
| 选项 | 说明 |
|---|---|
| `--id <name>` | 使用非默认用户(默认 `client.fstop`) |
| `--cluster <cluster>` | 使用非默认集群名(默认 `ceph`) |
| `-d <seconds>` | 刷新间隔(默认 1 秒),必须为正整数 |
| `--dump` | 把指标输出到 stdout,不创建 curses 界面 |
| `--dumpfs <fs_name>` | 把指定文件系统的指标输出到 stdout,不创建 curses 界面 |
#### 4.16.5 交互式命令
| 按键 | 功能 |
|---|---|
| `m` | Filesystem selection —— 显示文件系统选择菜单 |
| `s` | Sort field selection —— 指定排序字段,默认 `cap_hit` |
| `l` | Client limit —— 设置显示的客户端数量上限 |
| `r` | Reset —— 把排序字段与 limit 重置为默认值 |
| `q` | Quit —— 在主屏(所有文件系统信息)时退出,否则返回主屏 |
指标显示可通过方向键、PgUp/PgDn、Home/End 以及鼠标滚动。
> 💡 补充:`cephfs-top` 最低兼容 Python 版本为 **3.6.0**,支持的发行版为 RHEL 8、Ubuntu 18.04、CentOS 8 及以上。
> 引自:[CephFS Top Utility](https://docs.ceph.com/en/tentacle/cephfs/cephfs-top/)
---
## 第五部分 运维与排障
### 5.1 CephFS 管理命令(administration)
> 注意:文件系统名、元数据池名、数据池名只能包含 `[a-zA-Z0-9_-.]` 集合中的字符。
默认情况下**只允许一个文件系统**;要允许创建多个文件系统,需先执行 `ceph fs flag set enable_multiple true`。
#### 5.1.1 文件系统级命令
| 命令 | 用途 | 说明 |
|---|---|---|
| `ceph fs new <file system name> <metadata pool name> <data pool name>` | 创建新文件系统 | 指定的 data pool 是**默认数据池,一旦设定不可更改**。每个文件系统有自己的一组 MDS 守护进程分配到各 rank,创建新 FS 前要确保有足够 standby |
| `ceph fs ls` | 按名字列出所有文件系统 | — |
| `ceph fs lsflags <file system name>` | 列出该文件系统上设置的所有 flag | — |
| `ceph fs dump [epoch]` | dump 指定 epoch(默认当前)的 FSMap | 包含所有文件系统设置、MDS 守护进程及其所持 rank、以及 standby MDS 列表 |
| `ceph fs rm <file system name> [--yes-i-really-mean-it]` | 销毁一个 CephFS 文件系统 | 只从 FSMap 中抹掉该 FS 的状态信息;**元数据池与数据池不受影响,需另行销毁** |
| `ceph fs get <file system name>` | 获取指定文件系统的信息(设置与 rank) | 是 `ceph fs dump` 信息的子集 |
| `ceph fs set <file system name> <var> <val> [--yes-i-really-mean-it]` | 修改某文件系统的设置 | 仅作用于该 FS,不影响其他 FS。确认 flag 只在**集群不健康时修改 `max_mds`** 才需要 |
| `ceph fs add_data_pool <file system name> <pool name/id>` | 为文件系统添加一个数据池 | 该池可作为文件 layout 的备选数据存放位置 |
| `ceph fs rm_data_pool <file system name> <pool name/id>` | 从文件系统的数据池列表中移除指定池 | 若仍有文件的 layout 指向被移除的池,**这些文件数据将不可访问**;创建 FS 时的**默认数据池不可移除** |
| `ceph fs rename <file system name> <new file system name> [--yes-i-really-mean-it]` | 重命名文件系统 | 同时把数据池与元数据池上的 application tag 改为新 FS 名;授权给旧 FS 名的 CephX ID 需重新授权,使用这些 ID 的客户端在途操作可能被打断;**要求该 FS 上已禁用 mirroring** |
| `fs swap <fs1-name> <fs1_id> <fs2-name> <fs2_id> [--swap-fscids=yes\|no] [--yes-i-really-mean-it]` | 交换两个文件系统的名字,并相应更新双方所有池的 application tag | 详见下文 |
| `ceph fs status` | 查看文件系统状态(含 MDS rank、池用量等) | **文档未明确**:`doc/cephfs/` 下未给出该命令输出的逐字段说明 |
> ⚠️ 注意:**当集群不健康时**修改 `max_mds` **必须**带确认 flag `--yes-i-really-mean-it`。这是刻意加上的预防措施:在排障或恢复期间改 `max_mds` 不但可能没帮助,反而可能**进一步 destabilize 集群**。
#### 5.1.2 `fs swap` 详解
- `FSCID` = **File System Cluster ID**。
- 交换前必须满足:
- 两个 CephFS 上都**已禁用 mirroring**(`cephfs-mirror` 守护进程内部使用 fscid,在其运行时改变 fscid 会导致未定义行为);
- 两个 CephFS 都**离线**;
- 两个 CephFS 都设置了文件系统 flag `refuse_client_sessions`。
- `--swap-fscids` 是**必填**选项,用于明确指示是否同时交换 FSCID。
- 设计目的:服务于**灾难恢复**——当由一个旧 FS 重建出的新 FS 准备好接管(可能已损坏的)原 FS 时,用一次 swap 代替两次 `fs rename`,从而**不存在任何一个 FSMap epoch 里主(生产)FS 名字缺失**。这一点对 Rook 这类持续 reconcile 存储系统的自动化 operator 很重要:一旦发现 FS 不存在,它可能立刻尝试重建。
- 交换后:若希望已有挂载「跟随」旧文件系统到新名字,需要重新授权 CephX 凭据。一般 DR 场景下,希望已有挂载**继续使用同一个 FS 名**。任一 CephFS 上的活跃挂载都必须**重新挂载**,未 flush 的在途操作会**丢失**。
- 判定某个被交换的 FS 已可服务客户端时,执行:
```bash
ceph fs set <fs> joinable true
ceph fs set <fs> refuse_client_sessions false
```
- 做 DR swap 时,可以有意**把其中一个 FS 留作离线**以便后续分析。
#### 5.1.3 文件系统设置(Settings)
```bash
ceph fs set <fs name> max_file_size <size in bytes>
```
| 项目 | 说明 |
|---|---|
| 默认最大文件大小 | **1TB** |
| 字段宽度 | 64 位 |
| 设为 0 的效果 | **并不能禁用该限制**,只会把客户端限制为**只能创建空文件** |
**最大文件大小与性能的关系**:
- CephFS 在**追加写文件或设置文件大小**的时刻强制该上限,它**不影响数据的实际存储方式**。
- 当用户创建一个超大尺寸的文件(未必真的写入数据)时,某些操作(如删除)会迫使 MDS 执行**大量操作**去检查「按文件大小推断可能存在」的范围内到底哪些 RADOS 对象真实存在。
- `max_file_size` 的作用正是阻止用户创建看似 EB 级大小的文件,从而避免 MDS 在 stat 或 delete 等操作中枚举对象时产生巨大负载。
#### 5.1.4 使集群下线(Taking the cluster down)
设置 down flag 即让 CephFS 集群下线:
```bash
ceph fs set <fs_name> down true
```
恢复上线:
```bash
ceph fs set <fs_name> down false
```
- 恢复上线时**会还原此前 `max_mds` 的值**。
- MDS 守护进程的下线方式是:journal 被 flush 到元数据池,且所有客户端 I/O 被停止。
#### 5.1.5 快速下线(用于删除或灾难恢复)
要快速删除一个文件系统(例如测试)或迅速把文件系统与 MDS 守护进程拉下线,用 `ceph fs fail`:
```bash
ceph fs fail <fs_name> {--yes-i-really-mean-it}
```
> 注意:确认 flag 是可选的,因为**只有**当 MDS 处于 active 且存在健康告警 `MDS_TRIM` 或 `MDS_CACHE_OVERSIZED` 时才需要它。
该命令设置一个文件系统 flag 以阻止 standby 在该 FS 上激活(即 `joinable` flag)。
等价的手动流程:
```bash
# 1) 禁止 standby 加入
ceph fs set <fs_name> joinable false
# 2) 对所有 rank 0-N 执行,使 MDS 守护进程以 standby 身份重生;FS 将处于 degraded 状态
ceph mds fail <fs_name>:<n> {--yes-i-really-mean-it}
# 3) 所有 rank 都 inactive 后,可删除 FS 或保持该状态(例如用于 DR)
# 4) 恢复
ceph fs set <fs_name> joinable true
```
#### 5.1.6 守护进程级命令
大多数操作 MDS 的命令接受 `<role>` 参数,它有三种形式:
```
<fs_name>:<rank>
<fs_id>:<rank>
<rank>
```
| 命令 | 用途 | 说明 |
|---|---|---|
| `ceph mds fail <gid/name/role>` | 把一个 MDS 守护进程标记为 failed | 等价于「该 MDS 守护进程已有 `mds_beacon_grace` 秒未向 mon 发消息」时集群会做的事。若该守护进程是 active 且有合适 standby,此命令会**强制 failover 到该 standby**。若守护进程其实仍在运行,`ceph mds fail` 会使其重启;若它此前是 active 且有 standby,则「failed」的这个守护进程会**以 standby 身份回归** |
| `ceph tell mds.<daemon name> command ...` | 向 MDS 守护进程发命令 | 用 `mds.*` 向所有守护进程发;用 `ceph tell mds.* help` 查看可用命令 |
| `ceph mds metadata <gid/name/role>` | 获取 Monitors 所知的该 MDS 的元数据 | — |
| `ceph mds repaired <role>` | 把文件系统 rank 标记为已修复 | 与名字给人的印象不同:它**不改动 MDS**,而是操作**已被标记 damaged 的文件系统 rank** |
| `ceph mds last-seen <name>` | 查询名为 `name` 的 MDS 最后一次出现在 FSMap 中的时间 | JSON 输出包含该 MDS 最后被看到的 epoch。历史信息受限于 mon 配置 `mon_fsmap_prune_threshold` |
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mon_fsmap_prune_threshold` | `secs` | `300` | prune fsmap older than this threshold in seconds(影响 `ceph mds last-seen` 可回溯的范围) |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
#### 5.1.7 Required Client Features
有时需要设定「客户端必须支持才能与 CephFS 通信」的特性集合:缺少这些特性的客户端可能干扰其他客户端或表现出意外行为;也可能想强制要求较新的特性,以阻止老旧(可能有 bug)的客户端连接。
| 命令 | 用途 |
|---|---|
| `ceph fs required_client_features <fs name> add reply_encoding` | 增加一个必需特性 |
| `ceph fs required_client_features <fs name> rm reply_encoding` | 移除一个必需特性 |
| `ceph fs feature ls` | 列出所有 CephFS 特性 |
**缺少新加入特性的客户端会被自动驱逐。**
CephFS 特性及其首次出现的版本:
| Feature | Ceph release | Upstream Kernel |
|---|---|---|
| `jewel` | jewel | 4.5 |
| `kraken` | kraken | 4.13 |
| `luminous` | luminous | 4.13 |
| `mimic` | mimic | 4.19 |
| `reply_encoding` | nautilus | 5.1 |
| `reclaim_client` | nautilus | N/A |
| `lazy_caps_wanted` | nautilus | 5.1 |
| `multi_reconnect` | nautilus | 5.1 |
| `deleg_ino` | octopus | 5.6 |
| `metric_collect` | pacific | N/A |
| `alternate_name` | pacific | 6.5 |
| `notify_session_state` | quincy | 5.19 |
| `op_getvxattr` | quincy | 6.0 |
| `32bits_retry_fwd` | reef | 6.6 |
| `new_snaprealm_info` | reef | UNKNOWN |
| `has_owner_uidgid` | reef | 6.6 |
| `client_mds_auth_caps` | squid+bp | PLANNED |
特性说明(文档给出描述的部分):
| Feature | 说明 |
|---|---|
| `reply_encoding` | 若客户端支持,MDS 以可扩展格式编码请求应答 |
| `reclaim_client` | MDS 允许新客户端回收另一个(已死的)客户端的状态。**NFS-Ganesha 使用该特性** |
| `lazy_caps_wanted` | 当陈旧客户端恢复时,若其支持该特性,MDS 只需重新发放**被明确 wanted** 的 caps |
| `multi_reconnect` | MDS failover 时客户端发送 reconnect 消息以重建缓存状态;若 MDS 支持该特性,客户端可把大的 reconnect 消息**拆成多条** |
| `deleg_ino` | 若客户端支持,MDS 把 inode 号**委派**给客户端。拥有被委派的 inode 号是客户端做**异步文件创建**的前提 |
| `metric_collect` | 若 MDS 支持,客户端可以向 MDS 发送性能指标 |
| `alternate_name` | 客户端可以为目录项设置并理解 "alternate names"。用于**加密文件名**支持 |
| `client_mds_auth_caps` | 要在客户端的 `mds` caps 中有效实现 `root_squash`,客户端必须理解自己正在强制 `root_squash` 及其他 cap 元数据。缺少此特性的客户端有**丢弃文件更新**的风险,**建议设置该 feature bit** |
> 💡 补充:源文档中未对 `jewel`/`kraken`/`luminous`/`mimic`/`notify_session_state`/`op_getvxattr`/`32bits_retry_fwd`/`new_snaprealm_info`/`has_owner_uidgid` 单独给出说明文字(文档未明确)。
#### 5.1.8 全局设置与高级命令
```bash
ceph fs flag set <flag name> <flag val> [<confirmation string>]
```
设置全局 CephFS flag(不针对某个具体文件系统)。**目前唯一的 flag 是 `enable_multiple`**,用于允许存在多个 CephFS 文件系统。部分 flag 需要用 `--yes-i-really-mean-it` 或类似字符串确认意图。
高级命令(正常运行不需要,仅用于异常情况;使用不当可能造成文件系统不可访问等严重问题):
| 命令 | 用途 |
|---|---|
| `ceph mds rmfailed` | 把一个 rank 从 failed set 中移除 |
| `ceph fs reset <file system name>` | 把文件系统状态重置为默认值(**名称和池除外**);非 0 rank 被保存到 stopped set |
| `ceph fs new <fs name> <metadata pool> <data pool> --fscid <fscid> --force` | 创建带**指定 fscid** 的文件系统 |
> 💡 补充:`--fscid` 的典型用途是——某应用期望文件系统的 ID 在恢复后保持稳定(例如 monitor 数据库丢失并重建之后)。因此文件系统 ID 并不总是随新建文件系统单调递增。
> 引自:[CephFS Administrative commands](https://docs.ceph.com/en/tentacle/cephfs/administration/)
---
### 5.2 MDS 状态(mds-states)
MDS 在正常运行期间会经历若干状态。例如某些状态表示 MDS 正从前一个 MDS 实例的 failover 中恢复。
#### 5.2.1 常见状态(Common states)
| 状态 | 说明 |
|---|---|
| `up:active` | MDS 的**正常运行状态**,表示该 MDS 及其在文件系统中的 rank 可用 |
| `up:standby` | 该 MDS 可用于接管一个 failed rank。一旦有 rank failed,Monitor 会**自动**把处于该状态的 MDS 分配过去 |
| `up:standby_replay` | 该 MDS 正在**跟随**另一个 `up:active` MDS 的 journal。若 active MDS 失败,拥有 standby replay MDS 是很理想的——因为它正在重放**实时 journal**,接管会更快。缺点是:standby replay MDS **不能**接管其他失败的 MDS,只能接管它所跟随的那个 |
#### 5.2.2 较少见或过渡状态(Less common or transitory states)
| 状态 | 说明 |
|---|---|
| `up:boot` | 启动期间向 Ceph monitors 广播的状态。**永远不可见**——Monitor 会立即把该 MDS 分配给一个可用 rank,或命令其作为 standby。此处列出仅为完整性 |
| `up:creating` | MDS 正在通过构造一些 per-rank 元数据(如 journal)并进入 MDS 集群来**创建一个新 rank**(可能是 rank 0) |
| `up:starting` | MDS 正在**重启一个已停止的 rank**。它打开相关的 per-rank 元数据并进入 MDS 集群 |
| `up:stopping` | 当 rank 被停止时,monitors 命令 active MDS 进入该状态。此状态下 MDS **不接受新的客户端连接**,把所有子树迁移到该文件系统中的其他 rank,flush 其元数据 journal;若是**最后一个 rank(0)**,则驱逐所有客户端并关闭 |
| `up:replay` | MDS 正在**接管一个 failed rank**,表示它正在恢复 journal 及其他元数据 |
| `up:resolve` | 若 Ceph 文件系统有**多个 rank**(含本 rank),MDS 从 `up:replay` 进入该状态,即不是单 active MDS 集群。此时 MDS 正在解决(resolve)任何**未提交的 inter-MDS 操作**。要取得进展,文件系统中**所有 rank** 都必须处于该状态或更晚的状态,即**不允许任何 rank 处于 failed/damaged 或 `up:replay`** |
| `up:reconnect` | MDS 从 `up:replay` 或 `up:resolve` 进入该状态,用于**征集客户端的重新连接**。任何此前与该 rank 有会话的客户端都必须在这段时间内重连,超时由 `mds_reconnect_timeout` 配置 |
| `up:rejoin` | MDS 从 `up:reconnect` 进入该状态,正在**重新加入 MDS 集群缓存**。特别是所有 inter-MDS 的元数据锁都会被重建。**若没有已知需要重放的客户端请求,MDS 会直接从该状态变为 `up:active`** |
| `up:clientreplay` | MDS **可能**从 `up:rejoin` 进入该状态,正在重放那些**已应答但尚未持久化(未 journaled)**的客户端请求。客户端在 `up:reconnect` 期间重发这些请求,请求被再次重放。完成重放后 MDS 进入 `up:active` |
#### 5.2.3 Failed 状态
以下三个状态**没有任何 MDS 实际持有**,而是**施加于文件系统中的 rank** 之上。
| 状态 | 说明 | `ceph fs dump` 示例特征 |
|---|---|---|
| `down:failed` | rank 位于 failed set 中,等待被一个 standby MDS 接管。若该状态**持续存在**,说明找不到合适的 MDS 守护进程分配给该 rank——可能是 standby 数量不足,或所有 standby 的 compat 不兼容 | `failed 0` |
| `down:damaged` | rank 已损坏并被放入 `damaged` set。曾作为该 rank 运行的 MDS 发现了**无法自动恢复**的元数据损坏,**需要管理员介入** | `damaged 0` |
| `down:stopped` | rank 因**降低 `max_mds`** 而被停止 | `stopped 1` |
`ceph fs dump` 输出片段示例:
```text
max_mds 1
in 0
up {}
failed 0
...
```
#### 5.2.4 状态图(State Diagram)
状态图展示 MDS/rank 的可能状态转移,图例如下:
**Color(颜色)**
| 颜色 | 含义 |
|---|---|
| Green | MDS is active |
| Orange | MDS 处于过渡状态,正试图变为 active |
| Red | MDS 处于会导致 rank 被标记 **failed** 的状态 |
| Purple | MDS 与 rank 正在 stopping |
| Black | MDS 处于会导致 rank 被标记 **damaged** 的状态 |
**Shape(形状)**
| 形状 | 含义 |
|---|---|
| Circle | 某个 MDS 持有该状态 |
| Hexagon | 没有 MDS 持有该状态(它是施加于 rank 之上的) |
**Lines(线条)**
| 线条 | 含义 |
|---|---|
| 双线(double-lined)形状 | 该 rank 是 "in" 的 |
> 引自:[MDS States](https://docs.ceph.com/en/tentacle/cephfs/mds-states/)
---
### 5.3 MDS 配置参考(mds-config-ref)
下面按 `doc/cephfs/mds-config-ref.rst` 中的**原始顺序列出全部 62 个配置项**。
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_cache_mid` | `float` | `0.7` | midpoint for MDS cache LRU |
| `mds_allow_batched_ops` | `bool` | `true` | allow MDS to batch lookup/getattr RPCs |
| `mds_dir_max_commit_size` | `int` | `10` | maximum size in megabytes for a RADOS write to a directory |
| `mds_dir_max_entries` | `uint` | `0` | maximum number of entries per directory before new creat/links fail |
| `mds_decay_halflife` | `float` | `5` | rate of decay for temperature counters on each directory for balancing |
| `mds_beacon_interval` | `float` | `4` | interval in seconds between MDS beacon messages sent to monitors |
| `mds_beacon_grace` | `float` | `15` | tolerance in seconds for missed MDS beacons to monitors |
| `mon_mds_blocklist_interval` | `float` | `1_day` | Duration in seconds that blocklist entries for MDS daemons remain in the OSD map |
| `mds_reconnect_timeout` | `float` | `45` | timeout in seconds to wait for clients to reconnect during MDS reconnect recovery state |
| `mds_tick_interval` | `float` | `5` | time in seconds between upkeep tasks |
| `mds_dirstat_min_interval` | `float` | `1` | The minimum interval (in seconds) to try to avoid propagating recursive stats up the tree. |
| `mds_scatter_nudge_interval` | `float` | `5` | minimum interval between scatter lock updates |
| `mds_client_prealloc_inos` | `int` | `1000` | number of unused inodes to pre-allocate to clients for file creation |
| `mds_early_reply` | `bool` | `true` | additional reply to clients that metadata requests are complete but not yet durable |
| `mds_default_dir_hash` | `int` | `2` | hash function to select directory fragment for dentry name |
| `mds_log_skip_corrupt_events` | `bool` | `false` | Determines whether the MDS should try to skip corrupt journal events during journal replay. |
| `mds_bal_sample_interval` | `float` | `3` | interval in seconds between balancer ticks |
| `mds_bal_replicate_threshold` | `float` | `8000` | hot popularity threshold to replicate a subtree |
| `mds_bal_unreplicate_threshold` | `float` | `0` | cold popularity threshold to merge subtrees |
| `mds_bal_split_size` | `int` | `10000` | minimum size of directory fragment before splitting |
| `mds_bal_split_rd` | `float` | `25000` | hot read popularity threshold for splitting a directory fragment |
| `mds_bal_split_wr` | `float` | `10000` | hot write popularity threshold for splitting a directory fragment |
| `mds_bal_split_bits` | `int` | `3` | power of two child fragments for a fragment on split |
| `mds_bal_merge_size` | `int` | `50` | size of fragments where merging should occur |
| `mds_bal_interval` | `int` | `10` | interval between MDS balancer cycles |
| `mds_bal_fragment_interval` | `int` | `5` | delay in seconds before interrupting client IO to perform splits |
| `mds_bal_fragment_fast_factor` | `float` | `1.5` | ratio of mds_bal_split_size at which fast fragment splitting occurs |
| `mds_bal_fragment_size_max` | `int` | `100000` | maximum size of a directory fragment before new creat/links fail |
| `mds_bal_idle_threshold` | `float` | `0` | idle metadata popularity threshold before rebalancing |
| `mds_bal_max` | `int` | `-1` | The number of iterations to run balancer before Ceph stops. (used for testing purposes only) |
| `mds_bal_max_until` | `int` | `-1` | The number of seconds to run balancer before Ceph stops. (used for testing purposes only) |
| `mds_bal_mode` | `int` | `0` | The method for calculating MDS load. - `0` = Hybrid. - `1` = Request rate and latency. - `2` = CPU load. |
| `mds_bal_min_rebalance` | `float` | `0.1` | amount overloaded over internal target before balancer begins offloading |
| `mds_bal_overload_epochs` | `int` | `2` | The number of epochs the overload lasts before Ceph migrates, setting it to a higher value can avoid frequent migrations caused by load fluctuations. |
| `mds_bal_min_start` | `float` | `0.2` | The minimum subtree temperature before Ceph searches a subtree. |
| `mds_bal_need_min` | `float` | `0.8` | The minimum fraction of target subtree size to accept. |
| `mds_bal_need_max` | `float` | `1.2` | The maximum fraction of target subtree size to accept. |
| `mds_bal_midchunk` | `float` | `0.3` | Ceph will migrate any subtree that is larger than this fraction of the target subtree size. |
| `mds_bal_minchunk` | `float` | `0.001` | Ceph will ignore any subtree that is smaller than this fraction of the target subtree size. |
| `mds_replay_interval` | `float` | `1` | time in seconds between replay of updates to journal by standby replay MDS |
| `mds_shutdown_check` | `int` | `0` | The interval for polling the cache during MDS shutdown. |
| `mds_thrash_exports` | `int` | `0` | Ceph will randomly export subtrees between nodes (testing only). |
| `mds_thrash_fragments` | `int` | `0` | Ceph will randomly fragment or merge directories. |
| `mds_dump_cache_on_map` | `bool` | `false` | Ceph will dump the MDS cache contents to a file on each MDSMap. |
| `mds_dump_cache_after_rejoin` | `bool` | `false` | Ceph will dump MDS cache contents to a file after rejoining the cache (during recovery). |
| `mds_verify_scatter` | `bool` | `false` | Ceph will assert that various scatter/gather invariants are `true` (developers only). |
| `mds_debug_scatterstat` | `bool` | `false` | Ceph will assert that various recursive stat invariants are `true` (for developers only). |
| `mds_debug_frag` | `bool` | `false` | Ceph will verify directory fragmentation invariants when convenient (developers only). |
| `mds_debug_auth_pins` | `bool` | `false` | The debug auth pin invariants (for developers only). |
| `mds_debug_subtrees` | `bool` | `false` | The debug subtree invariants (for developers only). |
| `mds_kill_mdstable_at` | `int` | `0` | Ceph will inject MDS failure in MDSTable code (for developers only). |
| `mds_kill_export_at` | `int` | `0` | Ceph will inject MDS failure in the subtree export code (for developers only). |
| `mds_kill_import_at` | `int` | `0` | Ceph will inject MDS failure in the subtree import code (for developers only). |
| `mds_kill_link_at` | `int` | `0` | Ceph will inject MDS failure in hard link code (for developers only). |
| `mds_kill_rename_at` | `int` | `0` | Ceph will inject MDS failure in the rename code (for developers only). |
| `mds_inject_skip_replaying_inotable` | `bool` | `false` | MDS will skip replaying the inotable when replaying the journal logs. (for testing only). |
| `mds_kill_after_journal_logs_flushed` | `bool` | `false` | The primary MDS will crash just after the mknod/openc journal logs are flushed to the pool. (for testing only). |
| `mds_wipe_sessions` | `bool` | `false` | Ceph will delete all client sessions on startup (for testing only). |
| `mds_wipe_ino_prealloc` | `bool` | `false` | Ceph will delete ino preallocation metadata on startup (for testing only). |
| `mds_skip_ino` | `int` | `0` | The number of inode numbers to skip on startup (for testing only). |
| `mds_min_caps_per_client` | `uint` | `100` | minimum number of capabilities a client may hold |
| `mds_symlink_recovery` | `bool` | `true` | Stores symlink target on the first data object of symlink file. Allows recover of symlink using recovery tools. |
| `mds_extraordinary_events_dump_interval` | `secs` | `0` | Interval in seconds for dumping the recent in-memory logs when there is an extra-ordinary event. |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
> 引自:[MDS Config Reference](https://docs.ceph.com/en/tentacle/cephfs/mds-config-ref/)
---
### 5.4 CephFS 健康告警(health-messages)
#### 5.4.1 集群级健康检查(Cluster health checks)
Ceph monitor 守护进程会根据文件系统 map 结构(及其内含的 MDS map)的某些状态生成健康消息。
| 消息 | 描述 |
|---|---|
| `mds rank(s) *ranks* have failed` | 一个或多个 MDS rank 当前**未分配给任何 MDS 守护进程**;在合适的替代守护进程启动之前集群不会恢复 |
| `mds rank(s) *ranks* are damaged` | 一个或多个 MDS rank 的存储元数据**遭受严重损坏**,修复前无法再次启动 |
| `mds cluster is degraded` | 一个或多个 MDS rank 当前**未启动运行**,客户端可能**暂停元数据 IO** 直到该情况解决。包括处于 failed 或 damaged 的 rank,**也包括**那些已在某个 MDS 上运行但尚未进入 `active` 状态的 rank(例如当前处于 `replay` 状态的 rank) |
| `mds *names* are laggy` | 指定的 MDS 守护进程已有至少 `mds_beacon_grace`(默认 15s)未向 monitor 发送 beacon 消息,而它们本应每 `mds_beacon_interval`(默认 4s)发一次。这些守护进程**可能已崩溃**。若有 standby,**Ceph monitor 会自动替换 laggy 守护进程** |
| `insufficient standby daemons available` | 一个或多个文件系统被配置为需要一定数量的 standby 守护进程(含 standby-replay 的守护进程),但集群的 standby 数量不足。**未处于 replay 的** standby 守护进程对**任意**文件系统都计数(即它们可以重叠)。该告警可通过 `ceph fs set <fs> standby_count_wanted <count>` 配置;把 `count` 设为 **0 可禁用**该告警 |
#### 5.4.2 守护进程上报的健康检查(Daemon-reported health checks)
MDS 守护进程能识别多种异常状况,并在 `ceph status` 输出中提示管理员。这些状况除了人类可读的消息外,还有一个以 `MDS_` 开头的**唯一代码**。
```text
$ ceph health detail
HEALTH_WARN 1 MDSs report slow metadata IOs; 1 MDSs report slow requests
MDS_SLOW_METADATA_IO 1 MDSs report slow metadata IOs
mds.fs-01(mds.0): 3 slow metadata IOs are blocked > 30 secs, oldest blocked for 51123 secs
MDS_SLOW_REQUEST 1 MDSs report slow requests
mds.fs-01(mds.0): 5 slow requests are blocked > 30 secs
```
全部守护进程级告警码:
| 代码 | 消息 | 描述 | 触发阈值/相关配置 |
|---|---|---|---|
| `MDS_TRIM` | "Behind on trimming..." | CephFS 维护的元数据 journal 被划分为 *log segments*。journal 长度(以 segment 数计)由 `mds_log_max_segments` 控制,超过后 MDS 开始回写元数据以便删除(trim)最老的 segment。若回写**太慢**,或**软件 bug** 阻止了 trim,就会出现该消息 | `mds_log_warn_factor`,默认 **2.0** |
| `MDS_HEALTH_CLIENT_LATE_RELEASE` / `MDS_HEALTH_CLIENT_LATE_RELEASE_MANY` | "Client *name* failing to respond to capability release" | MDS 向 CephFS 客户端发放 *capabilities*(类似锁)。有时(例如另一客户端需要访问)MDS 会要求客户端释放 caps。若客户端无响应或有 bug,可能不及时或干脆不释放 | 客户端耗时超过 `session_timeout`(默认 **60s**) |
| `MDS_CLIENT_RECALL` / `MDS_HEALTH_CLIENT_RECALL_MANY` | "Client *name* failing to respond to cache pressure" | 客户端维护元数据缓存;客户端缓存中的项(如 inode)在 MDS 缓存中也被 pin,所以当 MDS 需要收缩缓存(以维持在 `mds_cache_memory_limit` 之内)时会发消息让客户端也收缩。若客户端无响应,MDS 可能无法正常维持缓存上限,**最终可能耗尽内存并崩溃** | 客户端在最近 `mds_recall_warning_decay_rate` 秒内,未释放超过 `mds_recall_warning_threshold` 个 capabilities(以 `mds_recall_max_decay_rate` 为半衰期衰减) |
| `MDS_CLIENT_OLDEST_TID` / `MDS_CLIENT_OLDEST_TID_MANY` | "Client *name* failing to advance its oldest client/flush tid" | CephFS client-MDS 协议用 *oldest tid* 字段告知 MDS 哪些客户端请求已完全完成、可以被遗忘。若有 bug 的客户端不推进该字段,MDS 可能无法正常清理客户端请求占用的资源 | 客户端在 MDS 侧已完成、但尚未计入客户端 *oldest tid* 的请求数超过 `max_completed_requests`(默认 **100000**)。MDS 用于 trim 已完成客户端请求(或 flush)的最后一个 tid 会包含在 `session ls`(或 `client ls`)命令输出中,作为调试辅助 |
| `MDS_DAMAGE` | "Metadata damage detected" | 从元数据池读取时遇到**损坏或缺失**的元数据。该消息表示损坏**足够孤立**,MDS 仍可继续运行,但客户端访问受损子树会返回 **IO 错误**。用 admin socket 的 `damage ls` 命令获取更多细节 | 一旦遇到任何 damage 即出现 |
| `MDS_HEALTH_READ_ONLY` | "MDS in read-only mode" | MDS 进入 readonly 模式,对尝试修改任何元数据的客户端操作返回 **EROFS**。触发条件:向元数据池写入时遇到**写错误**,或管理员用 *force_readonly* admin socket 命令强制 | — |
| `MDS_SLOW_REQUEST` | "*N* slow requests are blocked" | 一个或多个客户端请求未及时完成,表明 MDS 运行非常慢、或 RADOS 集群未及时确认 journal 写入、或存在 bug。用 admin socket 的 `ops` 命令列出未完成的元数据操作 | 任何客户端请求耗时超过 `mds_op_complaint_time`(默认 **30s**) |
| `MDS_CACHE_OVERSIZED` | "Too many inodes in cache" | MDS 未能成功把缓存 trim 到管理员设定的限制之内。缓存过大时守护进程可能**耗尽可用内存并崩溃** | 默认当实际缓存(内存)≥ `mds_cache_memory_limit`(默认 **4GB**)的 **150%** 时出现。可通过修改 `mds_health_cache_threshold` 设置告警比例 |
| `FS_WITH_FAILED_MDS` | "Some MDS ranks do not have standby replacements" | 正常情况下 failed 的 MDS rank 会被 standby MDS 替换,这是**过渡性的、不算严重**。但若**没有可用的 standby MDS** 来替换某个 active MDS rank,则产生该健康告警 | — |
| `MDS_INSUFFICIENT_STANDBY` | "Insufficient number of available standby(-replay) MDS daemons than configured" | 所需的最少 standby(-replay) MDS 守护进程数由 `standby_count_wanted` 配置变量设定 | 配置值与实际可用的 standby(-replay) MDS 守护进程数不匹配时产生 |
| `FS_DEGRADED` | "Some MDS ranks have been marked failed or damaged" | 一个或多个 MDS rank 因不可恢复错误而进入 failed 或 damaged 状态 | 一个(或多个)rank 离线时,文件系统可能部分或完全不可用 |
| `MDS_UP_LESS_THAN_MAX` | "Number of active ranks are less than configured number of maximum MDSs" | 最大 MDS rank 数由 `max_mds` 配置变量设定 | 当 MDS rank 数**低于**该配置值时产生 |
| `MDS_ALL_DOWN` | "None of the MDS ranks are available (file system offline)" | 所有 MDS rank 都不可用,导致文件系统**完全离线** | — |
| `MDS_CLIENTS_LAGGY` | "Client *ID* is laggy; not evicted because some OSD(s) is/are laggy" | 若 OSD 处于 laggy(例如网络中断等原因),可能使客户端变 laggy(会话闲置或无法为 cap revoke 刷新脏数据)。若 `defer_client_eviction_on_laggy_osds` 设为 true(**默认 true**),则**不会**执行客户端驱逐,从而产生该告警 | 相关配置 `defer_client_eviction_on_laggy_osds` 默认 true |
| `MDS_CLIENTS_BROKEN_ROOTSQUASH` | "X client(s) with broken root_squash implementation (MDS_CLIENTS_BROKEN_ROOTSQUASH)" | `root_squash` 中发现一个 bug,可能**丢失**受 `root_squash` caps 限制的客户端所做的更改。修复需要改动协议,因此**需要升级客户端**。这是 **HEALTH_ERR** 级别告警,因为存在不一致与数据丢失的危险。建议:升级客户端、或临时停用 `root_squash`、或按需静默该告警 | 要驱逐并永久阻止有问题的客户端连接集群,可设置 `required_client_feature` 位 `client_mds_auth_caps` |
| `MDS_ESTIMATED_REPLAY_TIME` | "HEALTH_WARN Replay: x% complete. Estimated time remaining *x* seconds" | 当 MDS journal replay 耗时**超过 30 秒**时,该消息显示预计完成时间 | > 30 秒 |
| `MDS_SLOW_METADATA_IO` | "1 MDSs report slow metadata IOs" | 出现在 `ceph health detail` 的示例输出中;表示有慢的元数据 IO | 示例:`3 slow metadata IOs are blocked > 30 secs, oldest blocked for 51123 secs` |
> 💡 补充:`MDS_SLOW_METADATA_IO` 在本文档页面的「典型健康报告示例」中出现,但页面未为其单列小节给出描述文字(文档未明确)。
> 引自:[CephFS health messages](https://docs.ceph.com/en/tentacle/cephfs/health-messages/)
---
### 5.5 排障(troubleshooting)
#### 5.5.1 慢/卡住的操作:定位思路
CephFS 操作有时会挂起。排障第一步是**定位导致挂起的问题所在**,问题只可能出现在三处:
| # | 位置 |
|---|---|
| 1 | 客户端 |
| 2 | MDS |
| 3 | 连接客户端与 MDS 的网络 |
流程:
1. 用 5.5.2 的方法判断是**客户端**有卡住的操作,还是 **MDS** 有卡住的操作。
2. Dump MDS cache——缓存内容将用于诊断问题性质:
```bash
# ceph daemon mds.<name> dump cache /tmp/dump.txt
```
> 注意:**非 systemd 管理**的 MDS 服务会把 `dump.txt` 写到运行 MDS 的那台机器上;**由 systemd 管理**的 MDS 服务会把 `dump.txt` 写到 MDS 容器内的 tmpfs 中。可用 `nsenter(1)` 定位 `dump.txt`,或指定另一个系统级路径。
若 MDS 上设置了较高的日志级别,`dump.txt` 中应包含诊断并解决挂起问题所需的信息。
#### 5.5.2 慢请求(MDS)
在 MDS 主机上通过 admin socket 列出当前操作:
```bash
# ceph daemon mds.<name> dump_ops_in_flight
```
识别卡住的命令并分析原因。通常**最后一个 "event"** 是要么尝试收集锁,要么把操作发给 MDS log。
| 现象 | 原因 | 处理 |
|---|---|---|
| 操作在等待 OSD | RADOS 侧问题 | 修复 OSD |
| 操作卡在某个**特定 inode** 上 | 很可能是某个客户端**持有 capabilities**,阻止了其他客户端使用。可能因客户端正在 flush 脏数据,也可能是 CephFS 分布式文件锁("caps" 系统)代码中的 bug | 若确定是 caps 代码 bug,**重启 MDS**——通常能解决问题 |
| MDS 上没有慢请求,也没有客户端异常迹象 | 要么客户端自身有问题,要么客户端的请求根本没到 MDS | 转向客户端侧排查 |
#### 5.5.3 卡在 `up:replay`
若 MDS 卡在 `up:replay` 状态,说明 **journal 很可能非常长**。出现 `MDS_HEALTH_TRIM` 集群告警可提示 MDS 在 trim journal 时尚未追上。**非常大的 journal 可能要处理数小时**——没有绕过的办法,但可以加速:
```bash
# 1) 临时关闭 MDS debug 日志(把调试级别降到 0)
ceph config set mds debug_mds 0
ceph config set mds debug_ms 0
ceph config set mds debug_monc 0
```
> ⚠️ 注意:把它们设为 0 后,**若 MDS 失败将没有任何调试信息**可用来判断致命错误的原因。如果能够估算 `up:replay` 何时完成,请**在进入下一个状态之前**恢复这些配置:
```bash
ceph config rm mds debug_mds
ceph config rm mds debug_ms
ceph config rm mds debug_monc
```
```bash
# 2) 查看 journal replay 进度
$ ceph tell mds.<fs_name>:0 status | jq .replay_status
{
"journal_read_pos": 4195244,
"journal_write_pos": 4195244,
"journal_expire_pos": 4194304,
"num_events": 2,
"num_segments": 2
}
```
- **Replay 完成的标志**是 `journal_read_pos` 追上 `journal_write_pos`。
- 重放期间**写位置不变**;跟踪读位置的推进速度即可估算剩余时间。
- 当重放 journal 耗时超过 30 秒,MDS 会发出 `MDS_ESTIMATED_REPLAY_TIME` 警告,消息中包含预计剩余时间:
```text
mds.a(mds.0): replay: 50.0446% complete - elapsed time: 582s, estimated time remaining: 581s
```
#### 5.5.4 避免恢复路上的障碍(Avoiding recovery roadblocks)
| 措施 | 命令 | 说明 |
|---|---|---|
| 拒绝所有客户端重连 | `ceph config set mds mds_deny_all_reconnect true` | Blocklist 所有已有 CephFS 会话,使所有挂载挂起或不可用。**MDS 变 active 后记得撤销**。⚠️ 这**不能**阻止**新**会话连接——要用文件系统设置 `refuse_client_sessions` 来阻止新会话 |
| 延长 MDS heartbeat grace | `ceph config set mds mds_heartbeat_grace 3600` | 避免系统在某个操作「卡住」时替换 MDS。恢复中的操作有时会比预期耗时更久。作用是:即使内部 heartbeat 机制一小时未重置(未 beat),MDS 仍继续向 monitors 发 beacon。过去这是通过 monitor 设置 `mds_beacon_grace` 实现的 |
| 关闭 open-file-table prefetch | `ceph config set mds mds_oft_prefetch_dirfrags false` | 正常情况下 MDS 在恢复期间预取目录内容以加热缓存。但在**长时间恢复**中,缓存很可能**已经很热且很大**,此时预取既无必要也不可取 |
| 关掉客户端 | `ceph fs set <fs_name> refuse_client_session true` | 重连到刚 `up:active` 的 MDS 的客户端会在 FS 刚可用时制造新负载,通常不受欢迎。恢复后往往需要先做维护(例如加速 trim journal)再放客户端进来。该命令可阻止客户端与 MDS 建立**新会话** |
| **不要动 `max_mds`** | — | 排障/恢复期间改 `max_mds` 看似好主意,其实很可能不是——**可能进一步 destabilize 集群**。若必须改,请带确认 flag `--yes-i-really-mean-it` |
| 关闭异步 purge 线程 | `ceph config set mgr mgr/volumes/pause_purging true`(恢复:`false`) | volumes 插件会派生线程异步 purge 被丢弃/删除的 subvolume |
| 关闭异步 cloner 线程 | `ceph config set mgr mgr/volumes/pause_cloning true`(恢复:`false`) | volumes 插件会派生线程异步 clone subvolume snapshot |
#### 5.5.5 加速 MDS journal trim
`MDS_HEALTH_TRIM` 告警表示 MDS journal 增长过大。此时可用 `mds_tick_interval` 可调项修改 "MDS tick interval"——该 tick 间隔驱动 MDS 中的各种 upkeep 活动,缩短它可以**更频繁地 trim**,从而缩小 journal。
```bash
# ceph config set mds mds_tick_interval 2
```
> 注意:
> - 修改 `mds_tick_interval` 时请确保**没有显著的文件系统负载**(参见 5.5.4 降低负载的办法)。
> - 该设置**只影响处于 `up:active` 状态的 MDS**——MDS 在**恢复期间不 trim** journal。
#### 5.5.6 RADOS 健康
若 CephFS 元数据池或数据池的一部分不可用、且 CephFS 无响应,可能表明 **RADOS 本身不健康**。**先解决 RADOS 的问题**,再去找 CephFS 的问题。
#### 5.5.7 MDS 侧排查
运行 `ceph health`:
| 消息 | 含义 |
|---|---|
| `slow requests are blocked` | 有操作在 MDS 中挂起 |
| `failing to respond` | 某客户端未能响应 |
潜在原因:
| # | 原因 | 处理 |
|---|---|---|
| 1 | 系统过载。最可能的原因是**活动文件集大于 MDS 缓存** | 若有多余内存,调大 `mds_cache_memory_limit`(修改前请完整阅读 MDS Cache Configuration 一节) |
| 2 | 存在较老的(行为异常的)客户端 | 升级/处理该客户端 |
| 3 | 底层 RADOS 有问题 | 先修 RADOS |
| 4 | 以上都不是 | 很可能发现了新 bug,应向开发者报告 |
#### 5.5.8 ceph-fuse 调试
`ceph-fuse` 是 CephFS 内核驱动的替代方案,在用户空间挂载 CephFS。`ceph-fuse` 支持 `dump_ops_in_flight`。
> ⚠️ **文档未明确**:源文中该命令的具体形式被注释掉了(标注 "the command goes here - 10 Aug 2025"),因此本册无法给出确定命令。
获取更详细的调试信息:在前台列出当前操作,同时把日志输出到 console(`-d`)、启用 client debug(`--debug-client=20`)、并为每条发送的消息打印(`--debug-ms=1`):
```bash
# ceph daemon -d mds.<name> dump_ops_in_flight --debug-client=20 --debug-ms=1
```
若怀疑 monitor 有问题,再加上 monitor 调试(`--debug-monc=20`):
```bash
# ceph daemon -d mds.<name> dump_ops_in_flight --debug-client=20 --debug-ms=1 --debug-monc=20
```
#### 5.5.9 内核挂载调试
诊断与修复内核客户端问题的第一步,是判断问题在**内核客户端**还是 **MDS**。若内核客户端本身有问题,其证据会出现在**内核 ring buffer** 中:
```bash
# dmesg
```
**慢请求**:内核客户端**不提供** admin socket。但如果客户端内核启用了 `debugfs`,则有类似 admin socket 的接口可用。
在 `/sys/kernel/debug/ceph/` 下找一个类似 `28f7427e-5558-4ffd-ae1a-51ec3042759a.client25386880` 的文件夹,其中的文件可用于诊断慢请求原因,用 `cat` 查看内容:
| 文件 | 内容 |
|---|---|
| `bdi` | BDI info about the Ceph system(脏块、已写块等) |
| `caps` | 内存中文件 "caps" 结构的计数与用量 |
| `client_options` | dump 传给 CephFS 挂载的选项 |
| `dentry_lru` | dump 当前在内存中的 CephFS dentries |
| `mdsc` | **dump 当前发往 MDS 的请求**(最有用之一) |
| `mdsmap` | dump 当前 MDSMap epoch 与 MDSes |
| `mds_sessions` | dump 当前到 MDSes 的会话 |
| `monc` | dump 当前来自 monitor 的 map 及持有的 "subscriptions" |
| `monmap` | dump 当前 monitor map epoch 与 monitors |
| `osdc` | **dump 当前发往 OSD 的在途操作**(即文件数据 IO)(最有用之一) |
| `osdmap` | dump 当前 OSDMap epoch、池与 OSDs |
> 对慢请求诊断最有用的两个文件是 `mdsc`(发往 MDS 的当前请求)与 `osdc`(发往 OSD 的在途操作)。
>
> 💡 补充:若数据池处于 `NEARFULL` 状态,内核 CephFS 客户端会**切换为同步写**,而同步写**相当慢**。
#### 5.5.10 Disconnected + Remounted FS
由于 CephFS 具有 "consistent cache",当网络连接中断较长时间时,MDS 会**强制驱逐(并 blocklist)**客户端。此时内核客户端**无法安全地回写脏(缓冲)数据**,结果是**数据丢失**。
> 注意:该行为是**恰当的**,并且也符合 POSIX 语义。客户端必须**重新挂载**才能再次访问文件系统。这是默认行为,但可以通过挂载选项 `recover_session` 覆盖。
若 `dmesg` 输出中包含类似下面的内容,即处于该情形:
```text
[Fri Aug 15 02:38:10 2025] ceph: mds0 caps stale
[Fri Aug 15 02:38:28 2025] libceph: mds0 (2)XXX.XX.XX.XX :6800 socket closed (con state OPEN)
[Fri Aug 15 02:38:28 2025] libceph: mds0 (2)XXX.XX.XX.XX:6800 session reset
[Fri Aug 15 02:38:28 2025] ceph: mds0 closed our session
[Fri Aug 15 02:38:28 2025] ceph: mds0 reconnect start
[Fri Aug 15 02:38:28 2025] ceph: mds0 reconnect denied
```
#### 5.5.11 挂载错误
| 错误 | 现象 | 原因 | 处理 |
|---|---|---|---|
| **Mount 5** | mount 返回 5 | 表明 MDS 服务器**响应迟缓或已崩溃** | 确保至少一个 MDS 处于 up 且 running,并且集群处于 `active + healthy` |
| **Mount 12** | mount 返回 12,消息为 `cannot allocate memory` | :term:`Ceph Client` 版本与 :term:`Ceph Storage Cluster` 版本**不匹配** | 用 `ceph -v` 核对版本;若客户端版本更旧则升级:`sudo apt-get update && sudo apt-get install ceph-common`。若仍不行,卸载 + `autoclean` + `autoremove` 后重装 `ceph-common` |
#### 5.5.12 动态调试(Dynamic Debugging)
CephFS 内核驱动的动态调试允许**启用或禁用** debug 日志。内核驱动日志写入**内核 ring buffer**,可用 `dmesg(1)` 查看。**默认禁用**——因为启用 debug 日志会导致系统变慢、I/O 吞吐下降。
针对 CephFS 模块启用动态调试,参见:https://github.com/ceph/ceph/blob/master/src/script/kcon_all.sh
> 注意:运行上述脚本会同时为 **CephFS 内核驱动、libceph、以及内核 RBD 模块**启用 debug 日志。若只想为某个具体组件(例如 CephFS 内核驱动)启用:
```bash
# echo 'module ceph +p' > /sys/kernel/debug/dynamic_debug/control
```
禁用:
```bash
# echo 'module ceph -p' > /sys/kernel/debug/dynamic_debug/control
```
#### 5.5.13 内存日志 Dump(In-memory Log Dump)
当日志级别设置为**小于 10** 时,可通过设置 `mds_extraordinary_events_dump_interval` 来 dump 内存日志。该配置项是「发生 extraordinary event 时 dump 最近内存日志」的**间隔秒数**。
Extraordinary events 包括:
- Client Eviction
- Missed Beacon ACK from the monitors
- Missed Internal Heartbeats
**默认禁用**内存日志 dump,以避免生产环境默认出现日志文件膨胀。
启用步骤(按顺序执行两条命令):
```bash
# 1) log_level < 10,gather_level > 10 —— 此时内存日志 dump 被启用
$ ceph config set mds debug_mds <log_level>/<gather_level>
# 2) 设置检查间隔;此后 MDS 每 N 秒检查一次 extraordinary events,
# 一旦发生就把包含相关事件详情的内存日志 dump 到 Ceph MDS 日志
# ceph config set mds mds_extraordinary_events_dump_interval <seconds>
```
> 注意:当设置了**较高**日志级别(`log_level >= 10`)时,没有理由再 dump 内存日志;而**较低**的 gather 级别(`gather_level < 10`)**不足以**收集内存日志。也就是说,`debug_mds` 中 `log_level >= 10` 或 `gather_level < 10` 都会**阻止**启用内存日志 dump。这种情况下若发生故障,必须先把 `mds_extraordinary_events_dump_interval` 重置为 `0`,再执行上述命令。
禁用:
```bash
# ceph config set mds mds_extraordinary_events_dump_interval 0
```
#### 5.5.14 升级后文件系统不可访问
> 注意:**在升级前**执行下面的操作可以避免 `operation not permitted` 错误。截至 2023 年 5 月,此类错误似乎发生在 Nautilus(含)之后的升级中。
**IF**
- 你的 CephFS 文件系统的数据池和元数据池是由 `ceph fs new` 命令创建的(即**不是用默认值创建的**),
**OR**
- 你已有一个 CephFS 文件系统,并正在升级到一个 **post-Nautilus** 的新 Ceph 大版本,
**THEN**
为了让文档中的 `ceph fs authorize...` 命令按文档所述正常工作(并避免除 `client.admin` 之外所有用户做文件 I/O 时出现 'operation not permitted' 错误或类似安全问题),你必须先运行:
```bash
$ ceph osd pool application set <your metadata pool name> cephfs metadata <your ceph fs filesystem name>
$ ceph osd pool application set <your data pool name> cephfs data <your ceph fs filesystem name>
```
**原因**:否则当 OSD 收到读写数据的请求(不是目录信息,而是文件数据)时,它不知道该去查哪个 Ceph 文件系统名。池名也是如此——因为「默认值本身」在各个大版本中发生了变化:
```text
# 旧
data pool=fsname
metadata pool=fsname_metadata
# 新
data pool=fsname.data and
metadata pool=fsname.meta
```
> 💡 补充:任何所有挂载都用 `client.admin` 的环境都不会遇到此问题,因为 admin key 拥有 blanket permissions。
>
> 临时修复:把挂载请求改成使用 `client.admin` 用户及其 key。较轻但只治一半的修复:把该用户的 osd cap 改成 `caps osd = "allow rw"` 并删掉 `tag cephfs data=....`。
#### 5.5.15 报告问题
若已定位到具体问题,请尽可能详细地报告。特别重要的信息:
| # | 信息 |
|---|---|
| 1 | 客户端与服务端安装的 Ceph 版本 |
| 2 | 使用的是 kernel 还是 fuse 客户端 |
| 3 | 若使用 kernel 客户端,内核版本是多少 |
| 4 | 有多少客户端在跑,做什么类型的负载 |
| 5 | 若系统 "stuck",是影响**所有**客户端还是只有一个 |
| 6 | 任何 ceph health 消息 |
| 7 | ceph 日志中崩溃产生的任何 backtrace |
若确认发现了 bug,请提交到 bug tracker(http://tracker.ceph.com);一般性问题请发到 ceph-users 邮件列表(http://lists.ceph.com/listinfo.cgi/ceph-users-ceph.com/)。
> 引自:[Troubleshooting](https://docs.ceph.com/en/tentacle/cephfs/troubleshooting/)
---
### 5.6 客户端驱逐(eviction)
当文件系统客户端无响应或行为异常时,可能需要**强制终止**其对文件系统的访问,这一过程称为 **eviction(驱逐)**。
- 驱逐一个 CephFS 客户端会阻止它继续与 MDS 守护进程和 OSD 守护进程通信。
- 若该客户端正在做**缓冲 IO**,任何**未 flush 的数据都会丢失**。
- 驱逐适用于**所有类型**的客户端:FUSE 挂载、内核挂载、nfs-ganesha 网关、以及任何使用 libcephfs 的进程。
客户端可被**自动**驱逐(未能及时与 MDS 通信)或**手动**驱逐(由系统管理员执行)。
#### 5.6.1 自动驱逐的三种情形
| # | 情形 | 触发参数 | 默认值 |
|---|---|---|---|
| 1 | 在 active MDS 上,客户端超过 `session_autoclose`(一个**文件系统变量**)秒未与 MDS 通信 | `session_autoclose` | 300 秒(**文档未明确**:`src/common/options/*.yaml.in` 中未查到该项,源文给出的默认值为 300s) |
| 2 | 在 active MDS 上,客户端超过 `mds_cap_revoke_eviction_timeout`(配置选项)秒未响应 cap revoke 消息 | `mds_cap_revoke_eviction_timeout` | `0`(**默认禁用**) |
| 3 | MDS 启动期间(含 failover)会经过 `reconnect` 状态,等待所有客户端连接到新 MDS 守护进程;未能在 `mds_reconnect_timeout` 时间窗内连接的客户端被驱逐 | `mds_reconnect_timeout` | 45 秒 |
出现上述任一情形时会向集群日志发送一条警告消息。
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `mds_cap_revoke_eviction_timeout` | `float` | `0` | 客户端未响应 cap revoke 超过该秒数后驱逐;0 表示禁用 |
| `mds_reconnect_timeout` | `float` | `45` | timeout in seconds to wait for clients to reconnect during MDS reconnect recovery state |
| `mds_session_blocklist_on_timeout` | `bool` | `true` | 因超时驱逐时是否 blocklist 该会话 |
| `mds_session_blocklist_on_evict` | `bool` | `true` | 手动驱逐时是否 blocklist 该会话 |
| `defer_client_eviction_on_laggy_osds` | `bool` | `false` | Do not evict client if any osd is laggy |
| `client_reconnect_stale` | `bool` | `false` | 提示(FUSE)客户端尝试重连 |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
>
> ⚠️ **来源冲突提示**:`health-messages.rst` 中 `MDS_CLIENTS_LAGGY` 一节称 `defer_client_eviction_on_laggy_osds` "set to true (default true)",但 `src/common/options/*.yaml.in` @ v20.2.4 中该项默认值为 `false`。两者不一致,请以实际集群 `ceph config get mds defer_client_eviction_on_laggy_osds` 的输出为准。
#### 5.6.2 手动驱逐
典型场景:客户端已死、管理员不想等其会话超时;或客户端行为异常而管理员**无法访问该客户端节点**去卸载。
先查看客户端列表:
```bash
$ ceph tell mds.0 client ls
[
{
"id": 4305,
"num_leases": 0,
"num_caps": 3,
"state": "open",
"replay_requests": 0,
"completed_requests": 0,
"reconnecting": false,
"inst": "client.4305 172.21.9.34:0/422650892",
"client_metadata": {
"ceph_sha1": "ae81e49d369875ac8b569ff3e3c456a31b8f3af5",
"ceph_version": "ceph version 12.0.0-1934-gae81e49 (ae81e49d369875ac8b569ff3e3c456a31b8f3af5)",
"entity_id": "0",
"hostname": "senta04",
"mount_point": "/tmp/tmpcMpF1b/mnt.0",
"pid": "29377",
"root": "/"
}
}
]
```
确定要驱逐的客户端后,可用其**唯一 ID** 或其他属性来驱逐:
```bash
# These all work
ceph tell mds.0 client evict id=4305
ceph tell mds.0 client evict client_metadata.=4305
```
#### 5.6.3 高级:解除客户端 blocklist
通常被 blocklist 的客户端**不能**重连服务器——必须先卸载再重新挂载。
但在某些情况下,允许一个被驱逐的客户端尝试重连是有用的。由于 **CephFS 使用 RADOS OSD blocklist 来控制客户端驱逐**,因此可以通过把客户端从 blocklist 中移除来允许其重连:
```bash
$ ceph osd blocklist ls
listed 1 entries
127.0.0.1:0/3710147553 2018-03-19 11:32:24.716146
$ ceph osd blocklist rm 127.0.0.1:0/3710147553
un-blocklisting 127.0.0.1:0/3710147553
```
> ⚠️ 风险:如果其他客户端已经访问过被 blocklist 客户端正在做缓冲 IO 的文件,这样做可能**危及数据完整性**。而且它**不保证**能得到一个功能完好的客户端——驱逐后要恢复完全健康的客户端,最好的办法是**卸载后重新挂载**。
>
> 💡 若用这种方式重连客户端,在 FUSE 客户端上把 `client_reconnect_stale` 设为 true 以促使其尝试重连,可能也有帮助。
#### 5.6.4 高级:配置 blocklisting
如果由于客户端主机慢或网络不可靠而频繁发生驱逐,且**无法修复根因**,可以让 MDS 放宽要求:
| 目标 | 配置项 | 设为 |
|---|---|---|
| 超时类驱逐只丢弃 MDS 会话,允许其重开会话并继续与 OSD 通信 | `mds_session_blocklist_on_timeout` | `false` |
| 手动驱逐的等价行为 | `mds_session_blocklist_on_evict` | `false` |
> ⚠️ 注意:**禁用 blocklisting 后,驱逐客户端只对你发送命令的那个 MDS 生效**。在多 active MDS 的系统上,需要向**每个 active 守护进程**发送驱逐命令。而**启用 blocklisting(默认)时,只向单个 MDS 发送驱逐命令即可**,因为 blocklist 会把驱逐传播给其他 MDS。
#### 5.6.5 背景:Blocklisting 与 OSD epoch barrier
客户端被 blocklist 之后,必须确保**其他客户端和 MDS 守护进程**在尝试访问该被 blocklist 客户端可能正在访问的任何数据对象之前,已经拥有**最新的 OSDMap**(其中包含该 blocklist 条目)。
这通过一个内部的 **"osdmap epoch barrier"** 机制来保证。
**barrier 的目的**:当我们发放任何可能触及相同 RADOS 对象的 capabilities 时,被发放这些 caps 的客户端必须持有**足够新的 OSD map**,从而不会与(来自 ENOSPC 的)**被取消的操作**或(来自驱逐的)**被 blocklist 的客户端**发生竞争。
**会设置 epoch barrier 的场景**:
| 场景 | 原因 |
|---|---|
| 客户端驱逐 | 客户端被 blocklist,其他客户端必须等到 post-blocklist epoch 才能触及相同对象 |
| 客户端侧 OSD map full flag 处理 | 客户端可能取消了某些来自 pre-full epoch 的 OSD op,因此其他客户端必须等到 full epoch 或更晚才能触及相同对象 |
| MDS 启动 | 我们不持久化 barrier epoch,所以必须假设重启后总是需要最新的 OSD map |
> 说明:为简单起见,这是一个**全局值**。理论上可以按 inode 维护,但没这么做,原因是:
> 1. 会复杂得多;
> 2. 每个 inode 会多用 4 字节内存;
> 3. 效率提升也不明显——几乎所有人都有最新的 OSD map,绝大多数情况下大家会**轻松通过**这个 barrier 而不是等待;
> 4. 该 barrier 只在极少数情况下发生,所以按 inode 粒度带来的收益极少能被看到。
epoch barrier **随所有 capability 消息一起传输**,并指示消息接收方在看到该 OSD epoch 之前**不要再向 OSD 发送任何 RADOS 操作**。这主要适用于客户端(它们直接向文件写数据),也适用于 MDS——因为像**文件大小探测**和**文件删除**这类事情是 MDS 直接做的。
> 引自:[Ceph file system client eviction](https://docs.ceph.com/en/tentacle/cephfs/eviction/)
---
### 5.7 升级 MDS 集群(upgrading)
MDS 集群**目前没有内置的版本协商或文件系统 flag** 来支持 MDS 的无缝升级——若不加以控制,不同版本之间不兼容的消息或功能差异可能导致 assertion 或其他故障。因此在**任何集群升级中,必须先把某个文件系统的 active MDS 数量降到 1**,以避免两个不同版本的 active MDS 互相通信。
#### 5.7.1 正确的升级顺序
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1. 禁用并停止 standby-replay 守护进程(对每个文件系统) | `ceph fs set <fs_name> allow_standby_replay false` | 在 **Pacific** 中,执行该命令后 standby-replay 守护进程会**自动停止**;更老的版本需要手动停止它们(`ceph fs dump` 找出 standby-replay 守护进程,然后 `ceph mds fail mds.<X>`) |
| 2. 把 rank 数降到 1(对每个文件系统) | `ceph fs set <fs_name> max_mds 1` | — |
| 3. 等待集群停掉非 0 rank | `ceph status` | 直到只有 rank 0 是 active,其余都是 standby |
| 4. 逐个 MDS 升级软件包并重启 | `# use package manager to update cluster`<br>`systemctl restart ceph-mds.target` | 为减少 failover,**建议(但非必须)先升级 standby 守护进程** |
| 5. 恢复原先的 `max_mds` 与 `allow_standby_replay` | `ceph fs set <fs_name> max_mds <old_max_mds>`<br>`ceph fs set <fs_name> allow_standby_replay <old_allow_standby_replay>` | — |
#### 5.7.2 把 pre-Firefly 文件系统升级过 Jewel
> 💡 提示:该建议**只适用于**使用 **Firefly (0.80) 之前**的 Ceph 版本创建的文件系统的用户。新建文件系统的用户可忽略。
Firefly 之前的 Ceph 版本使用了一种**现已废弃**的 CephFS 目录对象存储格式,称为 **TMAPs**。RADOS 中对 TMAPs 的读取支持将在 **Jewel 之后被移除**,因此升级 CephFS 的用户必须确保任何旧的目录对象都已被转换。
在**所有** MDS 和 OSD 服务器上安装完 Jewel 并重启服务后,运行:
```bash
cephfs-data-scan tmap_upgrade <metadata pool name>
```
| 要点 | 说明 |
|---|---|
| 执行次数 | 只需运行一次 |
| 是否需要停服 | **不需要**停止任何其他服务 |
| 耗时 | 可能较长——它会迭代元数据池中的所有对象 |
| 是否可在线 | **可以**,执行期间可正常使用文件系统 |
| 中断处理 | 若因任何原因中止,**直接重跑即可** |
若要把 pre-Firefly 的 CephFS 升级到比 Jewel 更新的 Ceph 版本,必须**先升级到 Jewel 并运行 `tmap_upgrade` 命令**,然后才能继续升级到最新版本。
> 引自:[Upgrading the MDS Cluster](https://docs.ceph.com/en/tentacle/cephfs/upgrading/)
---
### 5.8 实验性特性(experimental-features)
CephFS 包含若干实验性特性,它们**尚未完全稳定或达到可让用户在实际部署中开启的合格程度**。Ceph 项目通常会尽力清晰地划界并把它们隔离(fence off),以防误用。
其中有些特性比另一些更接近完成。文档会对每个特性给出**风险程度的近似评估**,并简要说明启用所需的操作。注意:**启用这些特性会不可撤销地(irrevocably)在 monitor 的 map 上打标记**,表明曾经启用过该 flag,以便改进调试与支持流程。
#### 5.8.1 Inline data
| 项目 | 说明 |
|---|---|
| 默认行为 | CephFS 的**所有**文件数据都存放在 RADOS 对象中 |
| 特性效果 | inline data 特性允许把**小文件(一般 < 2KB)**存放在 **inode 中**,并由 MDS 直接提供 |
| 收益与代价 | 可能改善小文件性能,但**增加 MDS 负载** |
| 成熟度 | 测试不充分,**目前不足以支持使用**;不过其内部故障**不太可能**导致非 inline 数据不可访问 |
| 启用方式 | 该特性**一直默认关闭**,需设置 `inline_data` flag |
| 生命周期 | 已在 **Octopus** 版本被宣布**弃用(deprecated)**,并可能在未来版本中被**彻底移除** |
#### 5.8.2 Mantle: Programmable Metadata Load Balancer
Mantle 是内建于 MDS 的**可编程元数据均衡器**。其思路是:保留负载均衡的**机制**(migration、replication、fragmentation),但用 **Lua** 把均衡**策略**抽出来(stub out)。详见 `mantle` 文档页。
#### 5.8.3 LazyIO
LazyIO 放宽 POSIX 语义:即使一个文件被多个客户端上的多个应用打开,也允许缓冲读/写;**由应用自己负责缓存一致性**。(详见 4.8 节)
> 引自:[Experimental Features](https://docs.ceph.com/en/tentacle/cephfs/experimental-features/)
---
## 第六部分 灾备与镜像
### 6.1 CephFS 快照镜像(cephfs-mirroring)
CephFS 支持通过 `cephfs-mirror` 工具把快照**异步复制**到远端的 CephFS 文件系统。快照的同步方式是:先镜像快照数据,然后在远端文件系统上(针对给定目录)创建一个**与源快照同名**的远端快照。
#### 6.1.1 要求(Requirements)
| 项目 | 要求 |
|---|---|
| 版本 | 主(本地)与备(远端)Ceph 集群版本都应为 **Pacific 或更高** |
#### 6.1.2 创建用户
在**主(本地)集群**上为 `cephfs-mirror` 守护进程创建 Ceph 用户。该用户需要:
- 对**元数据池**有**写**能力(用于创建 watch/notify 操作所需的 RADOS 对象——index objects)
- 对**数据池**有**读**能力
```bash
$ ceph auth get-or-create client.mirror mon 'profile cephfs-mirror' mds 'allow r' osd 'allow rw tag cephfs metadata=*, allow r tag cephfs data=*' mgr 'allow r'
```
在**备(远端)集群**上为每个文件系统 peer 创建 Ceph 用户。该用户需要对 MDS(以便打快照)和 OSD 有**完整**能力:
```bash
$ ceph fs authorize <fs_name> client.mirror_remote / rwps
```
该用户将在添加 peer 时作为 peer 规格的一部分提供。
#### 6.1.3 启动 Mirror 守护进程
Mirror 守护进程由 `systemd` 管理,但多数情况下最好使用 `cephadm` 接口:
```bash
$ systemctl enable cephfs-mirror@mirror
$ systemctl start cephfs-mirror@mirror
```
也可以在前台运行:
```bash
$ cephfs-mirror --id mirror --cluster site-a -f
```
> 注意:这里指定的用户是 `mirror`,其创建方法见 6.1.2。
**多守护进程与负载分担**:可以部署多个 `cephfs-mirror` 守护进程以并发同步并实现高可用。Mirror 守护进程使用简单的 `M/N` 策略分担同步负载,其中 `M` 是目录数,`N` 是 `cephfs-mirror` 守护进程数。
当用 cephadm 管理集群时:
```bash
# 部署(默认数量)
$ ceph orch apply cephfs-mirror
# 部署多个
$ ceph orch apply cephfs-mirror --placement=<placement-spec>
# 例如在 3 台不同主机上部署 3 个
$ ceph orch apply cephfs-mirror --placement="3 host1,host2,host3"
```
#### 6.1.4 Mirroring 模块
Manager 的 `mirroring` 模块提供管理目录快照镜像的接口,这些接口(大部分)是 monitor 上管理文件系统镜像命令的**包装器**,也是**推荐的控制接口**。
| 职责/特性 | 说明 |
|---|---|
| 目录分配 | mirroring 模块负责把目录分配给 mirror 守护进程进行同步 |
| 并发与 HA | 可派生多个 mirror 守护进程实现并发;当守护进程被创建(或终止)时,mirroring 模块会发现 mirror 守护进程集合的变化,并在新集合上**重新平衡目录分配**,从而提供高可用 |
| 生产建议 | ⚠️ **建议只部署单个 mirror 守护进程。运行多个守护进程是未测试的** |
**支持被镜像的文件类型**:
| 类型 | 是否支持 |
|---|---|
| 普通文件(`-`) | ✅ |
| 目录文件(`d`) | ✅ |
| 符号链接文件(`l`) | ✅ |
| 其他文件类型 | ❌ 被镜像**忽略**——因此在成功同步的 peer 上不会存在 |
mirroring 模块**默认禁用**,启用:
```bash
$ ceph mgr module enable mirroring
```
#### 6.1.5 模块命令族(`fs snapshot mirror *`)
> ⚠️ **关键区分**:「Mirroring module」命令以 `fs snapshot mirror` 为前缀;「monitor commands」以 `fs mirror` 为前缀。**用 monitor 命令启用 mirroring 会导致 mirror 守护进程因缺少 `cephfs_mirror` index object 而进入 "failed" 状态**。因此在本上下文中务必使用 module 命令。
| 命令 | 用途 |
|---|---|
| `ceph fs snapshot mirror enable <fs_name>` | 为指定文件系统启用 mirroring。要增删目录,必须先对该 FS 启用 mirroring |
| `ceph fs snapshot mirror disable <fs_name>` | 为指定文件系统禁用 mirroring |
| `ceph fs snapshot mirror peer_add <fs_name> <remote_cluster_spec> [<remote_fs_name>] [<remote_mon_host>] [<cephx_key>]` | 添加 peer |
| `ceph fs snapshot mirror peer_remove <fs_name> <peer_uuid>` | 移除 peer |
| `ceph fs snapshot mirror peer_list <fs_name>` | 列出文件系统 mirror peers |
| `ceph fs snapshot mirror add <fs_name> <path>` | 配置一个目录进行镜像 |
| `ceph fs snapshot mirror ls <fs_name>` | 列出已配置的目录 |
| `ceph fs snapshot mirror remove <fs_name> <path>` | 停止镜像某目录的快照 |
| `ceph fs snapshot mirror daemon status` | 查看 mirror 守护进程状态 |
| `ceph fs snapshot mirror peer_bootstrap create <fs_name> <client_entity> <site-name>` | 在 peer 集群上创建 bootstrap token |
| `ceph fs snapshot mirror peer_bootstrap import <fs_name> <token>` | 在主集群导入 bootstrap token |
**peer 规格说明**:
| 参数 | 说明 |
|---|---|
| `<remote_cluster_spec>` | 格式为 `client.<id>@<cluster_name>`,本文档中称为 `remote_cluster_spec` |
| `<remote_fs_name>` | 可选,默认与(远端集群上的)`<fs_name>` 相同 |
| peer UUID | peer 被添加时会被分配一个唯一 ID(UUID) |
> 该命令要成功,**远端集群的 Ceph 配置与用户 keyring 必须在主集群上可用**。例如:若在远端集群创建了名为 `client_mirror`、对远端文件系统 `remote_fs` 有 `rwps` 权限的用户,且远端集群名为 `remote_ceph`(即主集群上远端集群配置文件名为 `remote_ceph.conf`),则:
```bash
$ ceph fs snapshot mirror peer_add primary_fs client.mirror_remote@remote_ceph remote_fs
```
> ⚠️ 注意:**当前只支持单个 peer。**
**路径规则**:
- **只允许绝对路径**。
- 路径会被 mirroring 模块**规范化**:`/a/b/../b` 等价于 `/a/b`。
- 路径总是**从 CephFS 文件系统根**开始,而不是从主机挂载点开始。
```bash
$ mkdir -p /d0/d1/d2
$ ceph fs snapshot mirror add cephfs /d0/d1/d2
{}
$ ceph fs snapshot mirror add cephfs /d0/d1/../d1/d2
Error EEXIST: directory /d0/d1/d2 is already tracked
```
**层级约束**:一个目录被加入镜像后,**不允许**再添加其子目录或祖先目录:
```bash
$ ceph fs snapshot mirror add cephfs /d0/d1
Error EINVAL: /d0/d1 is a ancestor of tracked path /d0/d1/d2
$ ceph fs snapshot mirror add cephfs /d0/d1/d2/d3
Error EINVAL: /d0/d1/d2/d3 is a subtree of tracked path /d0/d1/d2
```
#### 6.1.6 Bootstrap Peers
用 `peer_add` 子命令添加 peer 要求 peer 集群的配置与用户 keyring 在主集群(manager 主机和运行 mirror 守护进程的主机)上可用。可以通过 **bootstrap 并导入 peer token** 来避免这一点。
```bash
# 1) 在 peer 集群上创建 bootstrap token
$ ceph fs snapshot mirror peer_bootstrap create <fs_name> <client_entity> <site-name>
# 示例
$ ceph fs snapshot mirror peer_bootstrap create backup_fs client.mirror_remote site-remote
{"token": "eyJmc2lkIjogIjBkZjE3MjE3LWRmY2QtNDAzMC05MDc5LTM2Nzk4NTVkNDJlZiIsICJmaWxlc3lzdGVtIjogImJhY2t1cF9mcyIsICJ1c2VyIjogImNsaWVudC5taXJyb3JfcGVlcl9ib290c3RyYXAiLCAic2l0ZV9uYW1lIjogInNpdGUtcmVtb3RlIiwgImtleSI6ICJBUUFhcDBCZ0xtRmpOeEFBVnNyZXozai9YYUV0T2UrbUJEZlJDZz09IiwgIm1vbl9ob3N0IjogIlt2MjoxOTIuMTY4LjAuNTo0MDkxOCx2MToxOTIuMTY4LjAuNTo0MDkxOV0ifQ=="}
# 2) 在主集群导入
$ ceph fs snapshot mirror peer_bootstrap import <fs_name> <token>
```
- `site-name` 是一个**用户自定义字符串**,用于标识远端文件系统。在 `peer_add` 子命令的语境中,`site-name` 就是 `remote_cluster_spec` 中所含的那个。
- `peer_add` 命令也支持直接传入远端集群 monitor 地址与用户 key,但**bootstrap 一个 peer 是推荐的添加方式**。
- Bootstrap 会把远端集群的相关细节存放在**主集群的 monitor config store** 中。
#### 6.1.7 发起快照镜像
在主集群上为已配置的目录创建快照即可触发镜像:
```bash
$ mkdir -p /d0/d1/d2/.snap/snap1
```
#### 6.1.8 镜像状态(Mirroring Status)
**A. 守护进程状态(模块接口)**
```bash
$ ceph fs snapshot mirror daemon status
[
{
"daemon_id": 284167,
"filesystems": [
{
"filesystem_id": 1,
"name": "a",
"directory_count": 1,
"peers": [
{
"uuid": "02117353-8cd1-44db-976b-eb20609aa160",
"remote": {
"client_name": "client.mirror_remote",
"cluster_name": "ceph",
"fs_name": "backup_fs"
},
"stats": {
"failure_count": 1,
"recovery_count": 0
}
}
]
}
]
}
]
```
每个 mirror 守护进程实例显示一条记录,包含已配置 peers 与基本统计。更详细的统计需使用 admin socket 接口。
**B. admin socket 命令**
```bash
$ ceph --admin-daemon /path/to/mirror/daemon/admin/socket help
{
....
"fs mirror status cephfs@360": "get filesystem mirror status",
....
}
```
> 以 `fs mirror status` 开头的命令提供已启用 mirroring 的文件系统的镜像状态。注意 `cephfs@360` 的格式是 `filesystem-name@filesystem-id`。**必须**使用这种格式,因为 mirror 守护进程是**异步**收到文件系统镜像状态通知的(一个文件系统可能被删除后又以相同名字重建)。
```bash
$ ceph --admin-daemon /var/run/ceph/cephfs-mirror.asok fs mirror status cephfs@360
{
"rados_inst": "192.168.0.5:0/1476644347",
"peers": {
"a2dc7784-e7a1-4723-b103-03ee8d8768f8": {
"remote": {
"client_name": "client.mirror_remote",
"cluster_name": "site-a",
"fs_name": "backup_fs"
}
}
},
"snap_dirs": {
"dir_count": 1
}
}
```
输出中的 `peers` 段显示 peer 信息,包括唯一的 peer-id(UUID)与规格。**移除已有 peer 时需要该 peer-id**。
**C. peer 同步状态**
命令参数格式为 `filesystem-name@filesystem-id peer-uuid`:
```bash
$ ceph --admin-daemon /var/run/ceph/cephfs-mirror.asok fs mirror peer status cephfs@360 a2dc7784-e7a1-4723-b103-03ee8d8768f8
{
"/d0": {
"state": "idle",
"last_synced_snap": {
"id": 120,
"name": "snap1",
"sync_duration": 3,
"sync_time_stamp": "274900.558797s",
"sync_bytes": 52428800
},
"snaps_synced": 2,
"snaps_deleted": 0,
"snaps_renamed": 0
}
}
```
> 注意:`snaps_synced`、`snaps_deleted` 和 `snaps_renamed` 这些同步统计会在**守护进程重启**时、以及**目录被重新分配给另一个 mirror 守护进程**时(部署了多个 mirror 守护进程的情况下)**被重置**。
**D. 目录状态**
| 状态 | 含义 |
|---|---|
| `idle` | 该目录当前**未**在同步 |
| `syncing` | 该目录**正在**同步 |
| `failed` | 该目录已达到连续失败的**上限** |
同步中时,守护进程把它标为 `syncing`,且 `fs mirror peer status` 会在 `current_syncing_snap` 下显示正在同步的快照:
```bash
$ ceph --admin-daemon /var/run/ceph/cephfs-mirror.asok fs mirror peer status cephfs@360 a2dc7784-e7a1-4723-b103-03ee8d8768f8
{
"/d0": {
"state": "syncing",
"current_syncing_snap": {
"id": 121,
"name": "snap2"
},
"last_synced_snap": {
"id": 120, "name": "snap1", "sync_duration": 3,
"sync_time_stamp": "274900.558797s", "sync_bytes": 52428800
},
"snaps_synced": 2, "snaps_deleted": 0, "snaps_renamed": 0
}
}
```
同步完成后守护进程把它标回 `idle`。
**E. failed 状态的成因与恢复**
- 连续同步失败次数达到配置值 → 标记 `failed`,并**重试**同步。
- 失败次数上限由 `cephfs_mirror_max_consecutive_failures_per_directory` 控制(默认 **10**)
- 重试间隔由 `cephfs_mirror_retry_failed_directories_interval` 控制(默认 **60** 秒)
- 添加一个**普通文件**用于同步会导致 failed 状态:
```bash
$ ceph fs snapshot mirror add cephfs /f0
$ ceph --admin-daemon /var/run/ceph/cephfs-mirror.asok fs mirror peer status cephfs@360 a2dc7784-e7a1-4723-b103-03ee8d8768f8
{
"/d0": { "state": "idle", ... },
"/f0": {
"state": "failed",
"snaps_synced": 0,
"snaps_deleted": 0,
"snaps_renamed": 0
}
}
```
> 这实际上允许用户先添加一个**尚不存在**的目录用于同步:守护进程会把它标为 failed 并以较低频率重试;当该目录被创建后,守护进程会在成功同步后**清除 failed 状态**。
- 在**远端文件系统**的 `.snap` 目录中**手动**添加新快照或新目录,会导致对应已配置目录进入 failed 状态:
```bash
# 在远端文件系统上
$ ceph fs subvolume snapshot create cephfs subvol1 snap2 group1
# 或
$ mkdir /d0/.snap/snap2
# 主集群查状态
$ ceph --admin-daemon /var/run/ceph/cephfs-mirror.asok fs mirror peer status cephfs@360 a2dc7784-e7a1-4723-b103-03ee8d8768f8
{
"/d0": {
"state": "failed",
"failure_reason": "snapshot 'snap2' has invalid metadata",
"last_synced_snap": { "id": 120, "name": "snap1", "sync_duration": 3, "sync_time_stamp": "274900.558797s" },
"snaps_synced": 2, "snaps_deleted": 0, "snaps_renamed": 0
},
"/f0": { "state": "failed", "snaps_synced": 0, "snaps_deleted": 0, "snaps_renamed": 0 }
}
```
当该快照或目录从远端文件系统被移除后,守护进程会在成功同步待处理快照(若有)后**清除 failed 状态**。
> ⚠️ 注意:**把远端文件系统当作只读对待**。CephFS 本身并不强制这一点;但配合正确的 MDS caps,用户就无法在远端文件系统中对目录打快照。
- 当 mirroring 被禁用时,对应的 `fs mirror status` 命令将不会出现在命令 help 中。
#### 6.1.9 Metrics
CephFS 把镜像指标导出为 **Labeled Perf Counters**,供 OCP/ODF Dashboard 用于 Geo Replication 监控。这些指标可用 `counter dump` 命令查看。
**Mirror Status Metrics**
| 名称 | 类型 | 说明 |
|---|---|---|
| `mirroring_peers` | Gauge | 参与 mirroring 的 peer 数量 |
| `directory_count` | Gauge | 正在同步的目录总数 |
| `mirrored_filesystems` | Gauge | 被镜像的文件系统总数 |
| `mirror_enable_failures` | Counter | 启用 mirroring 的失败次数 |
**Replication Metrics**
| 名称 | 类型 | 说明 |
|---|---|---|
| `snaps_synced` | Counter | 成功同步的快照总数 |
| `sync_bytes` | Counter | 正在同步的总字节数 |
| `sync_failures` | Counter | 快照同步失败总数 |
| `snaps_deleted` | Counter | 被删除的快照总数 |
| `snaps_renamed` | Counter | 被重命名的快照总数 |
| `avg_sync_time` | Gauge | 所有快照同步所花费的平均时间 |
| `last_synced_start` | Gauge | 最后一个已同步快照的同步开始时间 |
| `last_synced_end` | Gauge | 最后一个已同步快照的同步结束时间 |
| `last_synced_duration` | Gauge | 最后一次同步的耗时 |
| `last_synced_bytes` | Counter | 最后一个已同步快照同步的总字节数 |
#### 6.1.10 配置选项
| 配置项 | 类型 | 默认值 | 含义 |
|---|---|---|---|
| `cephfs_mirror_max_concurrent_directory_syncs` | `uint` | `3` | maximum number of concurrent snapshot synchronization threads |
| `cephfs_mirror_action_update_interval` | `secs` | `2` | interval for driving asynchronous mirror actions |
| `cephfs_mirror_restart_mirror_on_blocklist_interval` | `secs` | `30` | interval to restart blocklisted instances |
| `cephfs_mirror_max_snapshot_sync_per_cycle` | `uint` | `3` | number of snapshots to mirror in one cycle |
| `cephfs_mirror_directory_scan_interval` | `uint` | `10` | interval to scan directories to mirror snapshots |
| `cephfs_mirror_max_consecutive_failures_per_directory` | `uint` | `10` | consecutive failed directory synchronization attempts before marking a directory as "failed" |
| `cephfs_mirror_retry_failed_directories_interval` | `uint` | `60` | failed directory retry interval for synchronization |
| `cephfs_mirror_restart_mirror_on_failure_interval` | `secs` | `20` | interval to restart failed mirror instances |
| `cephfs_mirror_mount_timeout` | `secs` | `10` | timeout for mounting primary/secondary ceph file system |
| `cephfs_mirror_perf_stats_prio` | `int` | `5` | Priority level for mirror daemon replication perf counters |
> 默认值取自 `src/common/options/*.yaml.in` @ v20.2.4
#### 6.1.11 重新添加 Peers(Re-adding Peers)
当要把一个 peer 重新添加(重新分配)到**另一个集群**中的某个文件系统时:
1. 确保**所有 mirror 守护进程都已停止**向该 peer 的同步。可通过 `fs mirror status` admin socket 命令检查——**Peer UUID 不应再出现在命令输出中**。
2. **建议**在把 peer 重新添加到另一个文件系统之前,先**清除**该 peer 上已同步的目录(尤其是那些可能在新主文件系统中也存在的目录)。
3. 若把 peer 重新添加回**它此前同步来源的同一个主文件系统**,则**不需要**执行上述清除。
> 引自:[CephFS Snapshot Mirroring](https://docs.ceph.com/en/tentacle/cephfs/cephfs-mirroring/)
---
### 6.2 灾难恢复(disaster-recovery)
#### 6.2.1 元数据损坏与修复
如果文件系统存在**不一致或缺失**的元数据,就认为它是 **damaged(损坏的)**。发现损坏的途径:
- 来自一条健康消息;
- 或在某些不幸的情况下,来自一个正在运行的 MDS 守护进程中的 **assertion**。
元数据损坏的两个来源:
| 来源 | 示例 |
|---|---|
| 底层 RADOS 层的**数据丢失** | 多块磁盘故障导致某个 PG 的所有副本都丢失 |
| **软件 bug** | — |
CephFS 包含一些**可能**能恢复已损坏文件系统的工具,但要安全地使用它们需要**扎实理解 CephFS 内部机制**。这些潜在危险操作的文档在单独一页:`disaster-recovery-experts`(见 6.4)。
#### 6.2.2 数据池损坏(受丢失数据 PG 影响的文件)
如果丢失的 PG 位于**数据池**,那么:
- 文件系统**继续正常运行**;
- 但**某些文件的某些部分会直接缺失**(读取返回 **zeros**)。
丢失一个数据池 PG 可能影响**很多文件**——文件被拆成很多 RADOS 对象,因此要识别哪些文件受到特定 PG 丢失的影响,需要**扫描所有存放文件数据的 RADOS 对象**。这类扫描对于「识别哪些文件必须从备份恢复」很有用。
> ⚠️ **危险**:该命令**不修复任何元数据**。因此在这种情况下恢复文件时,必须**删除**受损文件并**替换**它,以获得一个**全新的 inode**。**不要**就地覆盖受损文件。
若已知某些对象从 PG 中丢失,用 `pg_files` 子命令扫描可能因此受损的文件:
```bash
cephfs-data-scan pg_files <path> <pg id> [<pg id>...]
```
例如,若 PG `1.4` 和 `4.5` 的数据已丢失,想知道 `/home/bob` 下哪些文件受损:
```bash
cephfs-data-scan pg_files /home/bob 1.4 4.5
```
输出是**可能受损文件的路径列表**,每行一个文件。
> 注意:该命令**作为一个普通 CephFS 客户端**工作,去查找文件系统中的所有文件并读取它们的 layout。这意味着**MDS 必须处于 up 且 running,该命令才可用**。
#### 6.2.3 使用 `first-damage.py`
标准流程:
| 步骤 | 命令 |
|---|---|
| 1. 卸载所有客户端 | (umount) |
| 2. 尽可能 flush journal | `ceph tell mds.<fs_name>:0 flush journal` |
| 3. fail 该文件系统 | `ceph fs fail <fs_name>` |
| 4. 从 journal 恢复 dentries(若第 2 步 flush 成功,此步为 no-op) | `cephfs-journal-tool --rank=<fs_name>:0 event recover_dentries summary` |
| 5. reset journal | `cephfs-journal-tool --rank=<fs_name>:0 journal reset --yes-i-really-mean-it` |
| 6. 运行 `first-damage.py` 列出损坏的 dentries | `python3 first-damage.py --memo run.1 <pool>` |
| 7.(可选)移除损坏的 dentries | `python3 first-damage.py --memo run.2 --remove <pool>` |
> 注意:用 `--memo` 指定一个**不同的文件**来保存已遍历过的对象,这样可以把不同、独立运行期间产生的数据分开。
>
> 第 7 步的效果是:从快照或 head(当前层次结构中)**移除一个 dentry**。该 inode 的 linkage 会丢失。不过该 inode 在将来做 data-scan 恢复时**可能**能在 `lost+found` 中被找回。
> 引自:[Disaster recovery](https://docs.ceph.com/en/tentacle/cephfs/disaster-recovery/)
---
### 6.3 高级:元数据修复工具(disaster-recovery-experts)
> ⚠️ **警告**:如果你**不具备 CephFS 内部机制的专家知识**,在使用以下任何工具之前**需要寻求帮助**。这里提到的工具**既能修好问题,也同样容易造成破坏**。**在尝试修复之前,必须准确理解你的文件系统到底出了什么问题。** 如果无法获得集群的专业支持,请咨询 ceph-users 邮件列表或 #ceph IRC/Slack 频道。
> 注意:**CephFS 文件系统必须先离线**,元数据修复工具才能作用于它。若文件系统在线时调用,这些工具会报错。**如果任何恢复步骤未成功完成,切勿继续执行后续恢复步骤。** 若任何恢复步骤失败,请通过邮件列表、IRC 频道和 Slack 频道向专家求助。
#### 6.3.1 Journal 导出(备份)
在执行任何危险操作之前,先复制一份 journal:
```bash
# cephfs-journal-tool journal export backup.bin
```
若恢复过程不如预期,这份备份会派上用场。可通过导入把它还原到原始状态:
```bash
# cephfs-journal-tool journal import backup.bin
```
#### 6.3.2 从 journal 恢复 dentry
若 journal 损坏、或 MDS 因任何原因无法重放它,尝试运行下面的命令恢复文件元数据:
```bash
# cephfs-journal-tool event recover_dentries summary
```
| 项目 | 说明 |
|---|---|
| 作用范围 | **默认作用于 MDS rank 0**。用 `--rank=<n>` 操作其他 rank,或用 `--rank=all` 操作所有 MDS rank |
| 行为 | 把 journal 中可恢复的**所有 inode 与 dentry** 写入后端存储,但**仅当**它们的**版本号高于**后端存储中的现有内容 |
| 缺失/损坏区域 | 会被**跳过** |
| 附加动作 | 除写 dentry 与 inode 外,还会更新每个 `in` MDS rank 的 **InoTables**,以表明被写入的 inode 号现已被占用。在简单情况下,这将得到一个**完全有效的**后端存储状态 |
> ⚠️ 警告:由此得到的后端存储状态**不保证自洽(self-consistent)**,之后**必须**执行一次在线 MDS scrub。该命令**不会修改 journal 内容**——请在尽可能恢复之后**单独截断 journal**。
#### 6.3.3 Journal 截断
用如下形式的命令截断任何已损坏、或 MDS 无法重放的 journal:
```bash
# cephfs-journal-tool [--rank=<fs_name>:{mds-rank|all}] journal reset --yes-i-really-really-mean-it
```
当文件系统**当前有或曾经有多个 active MDS** 时,用 `--rank` 选项指定文件系统与 MDS rank。
> ⚠️ 警告:**重置 journal 一定会导致元数据丢失**,除非已通过 `recover_dentries` 等其他手段把 journal 数据抽取出来。重置 journal **很可能**在数据池中留下**孤儿对象**,并可能导致**已写入的 inode 被重新分配**,从而造成文件系统行为异常(bug 等)。
#### 6.3.4 MDS 表擦除(MDS table wipes)
**并非**每个 MDS 表都必须在恢复过程中重置。只有当对应的 RADOS 对象**缺失**(例如某些 PG 丢失)或这些表与 RADOS 后端存储的元数据**不一致**时,才需要重置某个 MDS 表。
**检查表对象是否缺失**:
| 表 | 命令 |
|---|---|
| Session table | `rados -p <metadata-pool> stat mds0_sessionmap` |
| Inode table | `rados -p <metadata-pool> stat mds0_inotable` |
| Snap table | `rados -p <metadata-pool> stat mds_snaptable` |
> 注意:`sessionmap` 与 `inotable` 对象是**按 MDS rank** 的(对象名带 rank 号:`mds0_inotable`、`mds1_inotable` 等)。
即使表对象存在,它也可能与 RADOS 后端存储的元数据不一致。不过,如果元数据确实不一致或已损坏,**不把文件系统拉上线就很难检测到**这种不一致。这种情况下 MDS 会把自己标记为 `down:damaged`。
**重置单个表**(作用于某个具体 MDS rank):
| 表 | 命令 |
|---|---|
| Session table | `cephfs-table-tool 0 reset session` |
| SnapServer | `cephfs-table-tool 0 reset snap` |
| InoTable | `cephfs-table-tool 0 reset inode` |
**对处于 `in` 状态的所有 MDS rank 操作**——把上面的 rank 号替换为 `all`:
```bash
# cephfs-table-tool all reset session
# cephfs-table-tool all reset snap
# cephfs-table-tool all reset inode
```
> 注意:**session 表被重置后,必须重新挂载或重启所有 CephFS 客户端。**
#### 6.3.5 MDS map 重置
当文件系统的 in-RADOS 状态(即元数据池的内容)已在一定程度上恢复后,可能需要更新 MDS map 以反映元数据池的新状态:
```bash
# ceph fs reset <fs name> --yes-i-really-mean-it
```
- 运行该命令后,**除 0 之外的任何 MDS rank 的 in-RADOS 状态都将被忽略**。这意味着运行该命令**可能导致数据丢失**。
**`fs reset` 与 `fs remove` 的效果差异**:
| 命令 | rank 0 的最终状态 | 后果 |
|---|---|---|
| `fs reset` | 把 rank 0 留在 **`active`** 状态 | 下一个认领该 rank 的 MDS 守护进程会**使用现有的 in-RADOS 元数据** |
| `fs remove` | 把 rank 0 留在 **`creating`** 状态 | 磁盘上已有的 root inode **会被覆盖**;运行该命令会**孤立(orphan)所有已有文件** |
#### 6.3.6 从缺失的元数据对象恢复
根据缺失或损坏的对象不同,可能需要运行额外命令来重新生成对象的默认版本。
**A. Root inode 或 MDS 目录(`~mdsdir`)缺失/损坏**:
```bash
# cephfs-data-scan init
```
- 这通常**是安全的**——因为若 root inode 与 mdsdir inode 已存在,该命令会**跳过生成**。
- 但如果这些 inode **已损坏**,则必须重新生成。
- 识别损坏的方法:
1. 尝试把文件系统拉回上线(此时 MDS 会转入 `down:damaged`,并在 **MDS 日志**中给出指出加载 root inode 或 mdsdir inode 时潜在问题的相应日志消息);
2. 或用 `ceph-dencoder` 工具解码这些 inode(这一步稍复杂)。
**B. 基于数据池内容重新生成缺失文件与目录的元数据对象**——三阶段流程:
| 阶段 | 命令 | 作用 |
|---|---|---|
| 1 | `cephfs-data-scan scan_extents [<data pool> [<extra data pool> ...]]` | 扫描**所有**对象,为 inode 计算 **size** 与 **mtime** 元数据 |
| 2 | `cephfs-data-scan scan_inodes [<data pool>]` | 扫描每个文件的**第一个对象**以收集这些元数据,并把它注入元数据池 |
| 3 | `cephfs-data-scan scan_links` | 检查 inode linkage 并**修复**发现的错误 |
> 注意:若数据池包含很多文件或非常大的文件,`scan_extents` 与 `scan_inodes` 命令可能耗时**非常久**。
**C. 并行加速**
决定 worker 数量,并给每个 worker 传入 `0-(worker_m - 1)` 范围内的一个编号。以下示例展示如何同时运行 4 个 worker:
```bash
# Worker 0
cephfs-data-scan scan_extents --worker_n 0 --worker_m 4
# Worker 1
cephfs-data-scan scan_extents --worker_n 1 --worker_m 4
# Worker 2
cephfs-data-scan scan_extents --worker_n 2 --worker_m 4
# Worker 3
cephfs-data-scan scan_extents --worker_n 3 --worker_m 4
# Worker 0
cephfs-data-scan scan_inodes --worker_n 0 --worker_m 4
# Worker 1
cephfs-data-scan scan_inodes --worker_n 1 --worker_m 4
# Worker 2
cephfs-data-scan scan_inodes --worker_n 2 --worker_m 4
# Worker 3
cephfs-data-scan scan_inodes --worker_n 3 --worker_m 4
```
> ⚠️ **重要**:必须确保**所有 worker 都已完成** `scan_extents` 阶段,才允许**任何一个** worker 进入 `scan_inodes` 阶段。
**D. 清理**
完成元数据恢复后,可能需要运行一次清理操作,删除恢复期间生成的辅助数据:
```bash
# cephfs-data-scan cleanup [<data pool>]
```
清理阶段也可并行运行以加速:
```bash
cephfs-data-scan cleanup --worker_n 0 --worker_m 4
cephfs-data-scan cleanup --worker_n 1 --worker_m 4
cephfs-data-scan cleanup --worker_n 2 --worker_m 4
cephfs-data-scan cleanup --worker_n 3 --worker_m 4
```
> 注意:`scan_extents`、`scan_inodes` 和 `cleanup` 命令的**数据池参数是可选的**,通常工具能自动探测这些池,不过你也可以覆盖它。`scan_extents` 要求**指定所有数据池**,而 `scan_inodes` 与 `cleanup` 只要求指定**主数据池**。
#### 6.3.7 已知限制与陷阱(Known Limitations And Pitfalls)
灾难恢复过程可能**耗时且令人生畏**。恢复步骤必须**严格按照上述顺序**并以极大的谨慎执行,以确保任何步骤的失败都能被清楚理解。你必须能够**绝对确定地判断**继续是否安全。如果你确信自己能应对这些挑战,在尝试灾难恢复前请研读下面这份限制与陷阱清单:
| # | 限制/陷阱 |
|---|---|
| 1 | data-scan 命令**无法估算**其操作完成所需的时间。提供该估算的功能正在开发中,详见 https://tracker.ceph.com/issues/63191 |
| 2 | 恢复后、CephFS 客户端开始使用文件系统之前,**必须**执行一次文件系统 **scrub** |
| 3 | 一般而言,**不建议**在处于故障状态时修改任何 MDS 相关设置(例如 `max_mds`) |
| 4 | 灾难恢复目前是**手工过程**。有计划通过 Disaster Recovery Super Tool 将其自动化,详见 https://tracker.ceph.com/issues/71804 |
| 5 | 一些社区用户使用的知名技巧是:当 MDS 因 journal 过长而**卡在 `up_replay` 状态**时,使用灾难恢复流程(尤其是 `recover_dentries` 步骤)。但在 MDS 重放 journal 只是稍慢一点时就急着调用 `recover_dentries` 之前,请考虑先按 `cephfs_dr_stuck_during_recovery` 所述的流程**加速 journal 重放** |
#### 6.3.8 使用备用元数据池进行恢复(Using an alternate metadata pool for recovery)
> ⚠️ 警告:该流程**未经过广泛测试**。除非有充分理由,否则建议按上文详述的恢复流程来恢复文件系统。若采用本流程,务须格外谨慎。
如果一个现有 CephFS 文件系统已损坏且无法工作,可以创建一个**全新的元数据池**,并尝试把损坏文件系统的元数据**重建**到这个新池中,同时**保留旧元数据不动**。这样做可以让恢复尝试更安全,因为现有元数据池不会被修改。
> ⚠️ **告诫**:在此过程中,**多个元数据池将包含指向同一个数据池的数据**。在此期间必须**极度谨慎**,避免改动数据池的内容。恢复完成后,请**归档或删除**那个已损坏的元数据池。
**流程(`<fs_name>` 指原始、已损坏的文件系统)**:
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1 | `ceph fs fail <fs_name>` | 先卸载所有客户端、取下现有文件系统,防止数据池被进一步修改 |
| 2 | `ceph osd pool create cephfs_recovery_meta`<br>`ceph fs new cephfs_recovery cephfs_recovery_meta <data_pool> --recover --allow-dangerous-metadata-overlay` | 创建 recovery 文件系统:先有其数据池,再挂一个新元数据池,并把新元数据池设为由旧数据池支撑。`--recover` flag 阻止任何 MDS 守护进程加入新 FS。恢复元数据池与文件系统名日后可改 |
| 3 | `cephfs-table-tool cephfs_recovery:0 reset session`<br>`cephfs-table-tool cephfs_recovery:0 reset snap`<br>`cephfs-table-tool cephfs_recovery:0 reset inode`<br>`cephfs-journal-tool --rank cephfs_recovery:0 journal reset --force --yes-i-really-really-mean-it` | 创建文件系统的初始元数据 |
| 4 | `cephfs-data-scan init --force-init --filesystem cephfs_recovery --alternate-pool cephfs_recovery_meta`<br>`cephfs-data-scan scan_extents --alternate-pool cephfs_recovery_meta --filesystem <fs_name>`<br>`cephfs-data-scan scan_inodes --alternate-pool cephfs_recovery_meta --filesystem <fs_name> --force-corrupt`<br>`cephfs-data-scan scan_links --filesystem cephfs_recovery` | 从数据池重建元数据池。每个 scan 过程都会遍历整个数据池,可能耗时很长(参见上节如何分发给多个 worker) |
| 5 | `cephfs-journal-tool --rank=<fs_name>:0 event recover_dentries list --alternate-pool cephfs_recovery_meta` | **若损坏文件系统含有脏 journal 数据**,可接着用该命令恢复 |
| 6 | `ceph config rm mds mds_verify_scatter`<br>`ceph config rm mds mds_debug_scatterstat` | 恢复后某些目录的统计会不正确。确保这两个参数**为 false(默认值)**,以防 MDS 检查统计。⚠️ 还要确认这些配置没有被**全局**设置或用本地 `ceph.conf` 文件设置 |
| 7 | `ceph fs set cephfs_recovery joinable true` | 允许一个 MDS 守护进程加入 recovery 文件系统 |
| 8 | `ceph tell mds.cephfs_recovery:0 scrub start / recursive,repair,force` | 运行一次 **forward scrub** 以修复 recursive statistics。执行前确保有 MDS 守护进程在运行 |
> 注意:**符号链接(Symbolic link)恢复**从 **Quincy** 版本开始支持;在此之前符号链接被恢复为**空的普通文件**。
>
> 建议**尽快**把数据从 recovery 文件系统迁移走。在 recovery 文件系统运行期间**不要**恢复旧文件系统。
>
> ⚠️ 注意:如果**数据池也已损坏**,某些文件可能无法恢复——因为与它们关联的 **backtrace 信息已丢失**。若有数据对象缺失(例如数据池上 PG 丢失),恢复出的文件在缺失数据处会**出现空洞**。
> 引自:[Advanced: Metadata repair tools](https://docs.ceph.com/en/tentacle/cephfs/disaster-recovery-experts/)
---
### 6.4 `cephfs-journal-tool`
#### 6.4.1 用途
如果一个 CephFS journal 已损坏,可能需要**专家介入**才能把文件系统恢复到可工作状态。`cephfs-journal-tool` 提供相应功能,帮助专家**检查、修改和抽取** journal 中的数据。
> ⚠️ **警告**:该工具是**危险的**,因为它直接修改文件系统的**内部数据结构**。请做好备份、谨慎操作,并**寻求专家建议**。如果不确定,**不要**运行该工具。作为预防措施,**`cephfs-journal-tool` 不能作用于一个 active 的文件系统**。
#### 6.4.2 语法
```
cephfs-journal-tool [options] journal <inspect|import|export|reset>
cephfs-journal-tool [options] header <get|set> <trimmed_pos|expire_pos|write_pos|pool_id> <value>
cephfs-journal-tool [options] event <get|splice|recover_dentries> [filter] <list|json|summary|binary>
```
该工具有三种模式:
| 模式 | 含义 |
|---|---|
| `journal` | 整个 journal |
| `header` | journal 的头部 |
| `event` | journal 内部的事件 |
#### 6.4.3 Journal mode
这是评估 journal 状态的**起点**。
| 子命令 | 说明 |
|---|---|
| `inspect` | 报告 journal 的**健康状况**。会识别任何**缺失的对象**或存储 journal 中的**损坏**。⚠️ 注意:它**不**识别事件本身的不一致,只检查事件是否存在且能被解码 |
| `import` / `export` | 以**稀疏文件格式**读写 journal 的二进制 dump。文件名作为最后一个参数传入。`import` 操作会检查被导入 journal 的 FSID 是否与在线集群 FSID 匹配,用 `--force` 可跳过 FSID 检查。`export` 操作对已损坏(缺失对象)的 journal 可能不可靠 |
| `reset` | 截断 journal,丢弃其中所有信息。用 `--force` 执行**硬重置**,不尝试从磁盘恢复 |
**示例:journal inspect**
```text
# cephfs-journal-tool journal inspect
Overall journal integrity: DAMAGED
Objects missing:
0x1
Corrupt regions:
0x400000-ffffffffffffffff
```
**示例:Journal import/export**
```text
# cephfs-journal-tool journal export myjournal.bin
journal is 4194304~80643
read 80643 bytes at offset 4194304
wrote 80643 bytes at offset 4194304 to myjournal.bin
NOTE: this is a _sparse_ file; you can
$ tar cSzf myjournal.bin.tgz myjournal.bin
to efficiently compress it while preserving sparseness.
# cephfs-journal-tool journal import myjournal.bin
undump myjournal.bin
start 4194304 len 80643
writing header 200.00000000
writing 4194304~80643
done.
```
> 注意:在进行任何进一步操作之前,明智的做法是先用 `journal export <backup file>` 命令做一份 journal 备份。
#### 6.4.4 Header mode
| 子命令 | 说明 |
|---|---|
| `get` | 输出 journal header 的当前内容 |
| `set` | 修改 header 的某个属性。允许的属性为 `trimmed_pos`、`expire_pos`、`write_pos` 和 `pool_id` |
**示例:header get/set**
```text
# cephfs-journal-tool header get
{ "magic": "ceph fs volume v011",
"write_pos": 4274947,
"expire_pos": 4194304,
"trimmed_pos": 4194303,
"stream_format": 1,
"layout": { "stripe_unit": 4194304,
"stripe_count": 1,
"object_size": 4194304,
"pool_id": 2,
"pool_ns": ""}}
# cephfs-journal-tool header set trimmed_pos 4194303
Updating trimmed_pos 0x400000 -> 0x3fffff
Successfully updated header.
```
#### 6.4.5 Event mode
Event mode 允许对 journal 内容做详细的检查与操作。它可以作用于 journal 中的**所有事件**,也可以应用**过滤器**。
```
cephfs-journal-tool event <action> [filter] <output>
```
**Actions(动作)**
| 动作 | 说明 |
|---|---|
| `get` | 从日志中读取事件 |
| `splice` | 擦除 journal 中的事件或区域 |
| `recover_dentries` | 从 journal 恢复 dentries。它执行一次**选择性的离线重放**:只把 dentry 读出来,并且**仅当**其版本**高于**后端存储中当前的版本时才写入后端存储 |
**Filtering(过滤器)**
| 过滤器 | 说明 |
|---|---|
| `--range <int begin>..[int end]` | 只包含 begin(含)到 end(不含)范围内的事件 |
| `--path <path substring>` | 只包含引用了含指定字符串元数据的事件 |
| `--inode <int>` | 只包含引用了含指定 inode 元数据的事件 |
| `--type <type string>` | 只包含该类型的事件 |
| `--frag <ino>[.frag id]` | 只包含引用该目录 fragment 的事件 |
| `--dname <string>` | 只包含引用该目录 fragment 中指定名字 dentry 的事件(**只能与 `--frag` 一起使用**) |
| `--client <int>` | 只包含来自该客户端会话 ID 的事件 |
> 多个过滤器按 **AND** 组合(即只取各过滤器结果的**交集**)。
**Output modes(输出模式)**
| 模式 | 说明 |
|---|---|
| `binary` | 把每个事件写成一个二进制文件,放在由 `--path` 控制名字的目录中 |
| `json` | 把所有事件写到由 `--path` 指定的单个文件中,作为一个 JSON 序列化的对象列表 |
| `summary` | 把所读事件的人类可读摘要写到标准输出 |
| `list` | 写一份人类可读的简略列表,列出每个事件的类型以及该事件影响的路径 |
**示例:event mode**
```text
# cephfs-journal-tool event get json --path output.json
Wrote output to JSON file 'output.json'
# cephfs-journal-tool event get summary
Events by type:
NOOP: 2
OPEN: 2
SESSION: 2
SUBTREEMAP: 1
UPDATE: 43
# cephfs-journal-tool event get list
0x400000 SUBTREEMAP: ()
0x400308 SESSION: ()
0x4003de UPDATE: (setattr)
/
0x40068b UPDATE: (mkdir)
diralpha
0x400d1b UPDATE: (mkdir)
diralpha/filealpha1
0x401666 UPDATE: (unlink_local)
stray0/10000000001
diralpha/filealpha1
0x40228d UPDATE: (unlink_local)
diralpha
stray0/10000000000
0x402bf9 UPDATE: (scatter_writebehind)
stray0
0x403150 UPDATE: (mkdir)
dirbravo
0x4037e0 UPDATE: (openc)
dirbravo/.filebravo1.swp
0x404032 UPDATE: (openc)
dirbravo/.filebravo1.swpx
# cephfs-journal-tool event get --path filebravo1 list
0x40785a UPDATE: (openc)
dirbravo/filebravo1
0x4103ee UPDATE: (cap update)
dirbravo/filebravo1
# cephfs-journal-tool event splice --range 0x40f754..0x410bf1 summary
Events by type:
OPEN: 1
UPDATE: 2
# cephfs-journal-tool event recover_dentries summary
Events by type:
LID: 1
SUBTREEMAP: 1
Errors: 0
# cephfs-journal-tool event get --inode=1099511627776 list
0x40068b UPDATE: (mkdir)
diralpha
...
# cephfs-journal-tool event get --frag=1099511627776 --dname=filealpha1 list
0x400d1b UPDATE: (mkdir)
diralpha/filealpha1
0x401666 UPDATE: (unlink_local)
stray0/10000000001
diralpha/filealpha1
# cephfs-journal-tool event get binary --path bin_events
Wrote output to binary files in directory 'bin_events'
```
#### 6.4.6 Options
| 选项 | 说明 |
|---|---|
| `--rank=<filesystem>:{mds-rank\|all}` | 指定文件系统、MDS rank 或所有 rank |
| `--journal=<mdlog\|purge_queue>` | journal 类型。**默认值为 `mdlog`**,也是**唯一**支持 event mode 的选项。使用 `purge_queue` 时,journal scanner 会创建一个 `PurgeItem` 对象并把它加入 EventMap 进行处理 |
> 引自:[cephfs-journal-tool](https://docs.ceph.com/en/tentacle/cephfs/cephfs-journal-tool/)
---
### 6.5 Monitor store 灾难性丢失后恢复文件系统
在极少数情况下,集群的**所有 monitor store** 可能损坏或丢失。此时要恢复集群,需要:
1. 利用 OSD 重建 monitor store(见 `mon-store-recovery-using-osds`);
2. 让池恢复正常(active+clean 状态)。
然而,**重建出来的 monitor store 不会恢复文件系统 map("FSMap")**,因此需要**额外的步骤**才能把文件系统找回来。
> ⚠️ **范围限制**:恢复**多 active MDS 文件系统**或**多文件系统**的步骤**尚未确定**。目前**只有**「集群中只有一个 single active MDS 文件系统、且没有其他文件系统」这一情形的步骤已被确定并测试过。
简要来说步骤是:用基本默认值重建 FSMap;然后让 MDS 从文件系统的池中所存储的 journal/元数据中恢复。
#### 6.5.1 详细步骤
| 步骤 | 命令 | 说明 |
|---|---|---|
| 1 | `ceph fs new <fs_name> <metadata_pool> <data_pool> --force --recover` | 用恢复出来的文件系统池重建文件系统。新的 FSMap 将具有该文件系统的**默认设置**;但用户自定义的文件系统设置(如 `standby_count_wanted`、`required_client_features`、额外数据池等)**已丢失,需要稍后重新应用** |
| 2 | `ceph fs set <fs_name> joinable true` | 允许 standby MDS 守护进程加入该文件系统 |
| 3 | `ceph fs status` | 检查文件系统不再处于 degraded 状态,且有一个 active MDS |
| 4 | (按需) | 重新应用任何其他自定义文件系统设置 |
**`--recover` flag 的作用**:
- 把文件系统 rank 0 的状态设为 **existing but failed**。因此当某个 MDS 守护进程最终接管 rank 0 时,它会**读取现有的 in-RADOS 元数据,而不会覆盖它**。
- 该 flag **还阻止** standby MDS 守护进程激活该文件系统。
**关于 fscid**:
- 文件系统的 **fscid 不会被保留**。
- 对某些期望文件系统在恢复前后**保持不变**的应用(例如 **Ceph CSI**)来说,这一行为可能不理想。
- 解决办法:可以在上述命令中**可选地**设置 `fscid` 选项(见「Advanced」一节中的 `ceph fs new ... --fscid <fscid> --force`)。
> 引自:[Recovering the file system after catastrophic Monitor store loss](https://docs.ceph.com/en/tentacle/cephfs/recover-fs-after-mon-store-loss/)
---
## 第七部分 生态与集成
### 7.1 NFS(通过 NFS-Ganesha 导出 CephFS)
CephFS 命名空间可以通过 **NFS-Ganesha NFS server** 以 NFS 协议导出。本节提供**手工配置** NFS-Ganesha 集群的信息。
> 💡 管理 NFS-Ganesha 集群与 CephFS 导出的**最简单且推荐**的方式是使用 `ceph nfs ...` 命令(详见 `mgr/nfs` 文档),部署由 **cephadm 或 rook** 完成。
#### 7.1.1 要求(Requirements)
| # | 要求 |
|---|---|
| 1 | Ceph 文件系统 |
| 2 | NFS server 主机上安装 `libcephfs2`、`nfs-ganesha`、`nfs-ganesha-ceph` 软件包 |
| 3 | NFS-Ganesha server 主机连接到 Ceph **public network** |
> 注意:**建议**使用 **3.5 或更高**的稳定版 NFS-Ganesha 软件包,配合 **pacific (16.2.x) 或更高**的稳定版 Ceph 软件包。
#### 7.1.2 配置 NFS-Ganesha 导出 CephFS
NFS-Ganesha 提供 **File System Abstraction Layer(FSAL)** 以插入不同的存储后端。**FSAL_CEPH** 是 CephFS 对应的插件 FSAL。对**每个** NFS-Ganesha export,FSAL_CEPH 使用一个 **libcephfs client** 去挂载它被导出所对应的 CephFS 路径。
搭建 NFS-Ganesha + CephFS 涉及三部分:
1. 配置 NFS-Ganesha 的配置文件;
2. 配置 Ceph 的配置文件;
3. 为 NFS-Ganesha 创建的 Ceph 客户端准备 **CephX 访问凭据**以访问 CephFS。
**NFS-Ganesha 配置**
官方提供了一个配置了 FSAL_CEPH 的 `ganesha.conf` 示例(见 `src/config_samples/ceph.conf`),适用于:
- 独立的 NFS-Ganesha server;
- 或 active/passive 配置的 NFS-Ganesha servers(由 Pacemaker 之类的集群软件管理)。
示例配置中包含的选项用于实现以下目标:
| 目标 | 说明 |
|---|---|
| 最小化 Ganesha 缓存 | 因为 FSAL_CEPH 的 libcephfs clients 本身也**激进地缓存** |
| 从 RADOS 对象读取 Ganesha 配置 | 配置文件存放在 RADOS 对象中 |
| 把 client recovery data 存入 RADOS | 使用 RADOS **OMAP key-value** 接口 |
| 强制 NFSv4.1+ 访问 | mandate NFSv4.1+ access |
| 启用 read delegations | 需要至少 v13.0.1 的 `libcephfs2` 包,以及 v2.6.0 stable 的 `nfs-ganesha` 与 `nfs-ganesha-ceph` 包 |
> ⚠️ **重要**:在某些条件下,使用 CephFS FSAL 的 NFS 访问会失败,抛出 **"Input/output error"** 错误。这种情况下,必须为 CephFS 的**元数据池和数据池**设置 application metadata:
```bash
$ ceph osd pool application set <cephfs_metadata_pool> cephfs <cephfs_data_pool> cephfs
```
**libcephfs 客户端的配置**
libcephfs 客户端的 `ceph.conf` 包含一个 `[client]` 段,其中 `mon_host` 选项用于让客户端连接到 Ceph 集群的 monitors,通常由 `ceph config generate-minimal-conf` 生成。例如:
```ini
[client]
mon host = [v2:192.168.1.7:3300,v1:192.168.1.7:6789], [v2:192.168.1.8:3300,v1:192.168.1.8:6789], [v2:192.168.1.9:3300,v1:192.168.1.9:6789]
```
#### 7.1.3 用 NFSv4 客户端挂载
**建议**使用 **NFSv4.1+** 协议挂载 NFS-Ganesha 导出,以获得 **sessions** 的好处。
挂载 NFS 资源的约定与平台相关;以下约定适用于 Linux 和某些 Unix 平台:
```bash
mount -t nfs -o nfsvers=4.1,proto=tcp <ganesha-host-name>:<ganesha-pseudo-path> <mount-point>
```
> 引自:[NFS](https://docs.ceph.com/en/tentacle/cephfs/nfs/)
---
### 7.2 在 Windows 上挂载 CephFS(ceph-dokan)
`ceph-dokan` 用于在 **Windows** 上挂载 CephFS 文件系统。它利用 **Dokany**——一个允许以类似 FUSE 的方式在**用户空间**实现文件系统的 Windows 驱动。
#### 7.2.1 挂载文件系统
```text
ceph-dokan.exe -c c:\ceph.conf -l x
```
| 参数 | 说明 |
|---|---|
| `-c c:\ceph.conf` | 指定配置文件。若 `ceph.conf` 位于默认位置 `%ProgramData%\ceph\ceph.conf`,则该参数**可选** |
| `-l x` | 使用驱动器号 `x` 挂载**默认**的 Ceph 文件系统。`-l` 参数也允许用**空文件夹**作为挂载点,而不使用驱动器号 |
**uid / gid**:挂载文件系统使用的 uid 与 gid **默认为 0**,可通过 `ceph.conf` 选项修改:
```ini
[client]
# client_permissions = true
client_mount_uid = 1000
client_mount_gid = 1000
```
**挂载非默认文件系统**:若集群上有多个文件系统,用 `--client_fs` 选项:
```powershell
mkdir -Force C:\mnt\mycephfs2
ceph-dokan.exe --mountpoint C:\mnt\mycephfs2 --client_fs mycephfs2
```
**挂载子目录**:用 `--root-path` 参数:
```text
ceph-dokan -l y --root-path /a
```
**可移动标志**:若设置了 `-o --removable` 标志,挂载会出现在 `Get-Volume` 结果中:
```powershell
PS C:\> Get-Volume -FriendlyName "Ceph*" | `
Select-Object -Property @("DriveLetter", "Filesystem", "FilesystemLabel")
DriveLetter Filesystem FilesystemLabel
----------- ---------- ---------------
Z Ceph Ceph
W Ceph Ceph - new_fs
```
运行 `ceph-dokan --help` 获取完整参数列表。
#### 7.2.2 凭据(Credentials)
`--id` 选项传入挂载 CephFS 文件系统时使用其 keyring 的 **CephX 用户名**。以下两条命令等价:
```text
ceph-dokan --id foo -l x
ceph-dokan --name client.foo -l x
```
#### 7.2.3 卸载文件系统
可以通过 **ctrl-c** 或使用 unmap 命令移除挂载:
```text
ceph-dokan.exe unmap -l x
```
> 注意:unmap CephFS 文件系统时,**必须指定创建该映射时所使用的挂载点参数**。
#### 7.2.4 限制(Limitations)
| 限制 | 说明 |
|---|---|
| Windows ACLs | 被**忽略**。支持 Posix ACLs,但**无法通过当前 CLI 修改**。未来可能会增加改变文件属主或权限的命令动作 |
| 强制文件锁 | CephFS **不支持** mandatory file locks,而 Windows heavily relies upon 它们。Ceph 依赖 **Dokan** 处理文件锁,这些锁**只在本地强制** |
| 服务化 | 与 `rbd-wnbd` 不同,`ceph-dokan` **不提供** `service` 命令。若要让 CephFS 挂载在主机重启后仍然存在,请使用 **NSSM** |
> 排障参见 Windows troubleshooting 页。
> 引自:[Mount CephFS on Windows](https://docs.ceph.com/en/tentacle/cephfs/ceph-dokan/)
---
### 7.3 分布式文件系统应用最佳实践(app-best-practices)
CephFS 是 **POSIX 兼容**的,因此应该能与任何期望 POSIX 文件系统的现有应用一起工作。然而,由于它是**网络文件系统**(不同于 XFS 等本地 FS)且是**高度一致的**(不同于 NFS),应用作者了解由此带来的一些后果会很有帮助。
以下各节描述分布式文件系统相对于本地文件系统**性能行为差异明显**的领域。
#### 7.3.1 `ls -l`
当你运行 `ls -l` 时,`ls` 程序会**先列出目录**,然后对目录中的**每一个文件**调用 `stat`。
- 这通常**远远超出**应用的真实需要;
- 对**大目录**来说可能很慢。
- **建议**:如果并不需要每个文件的全部元数据,就用普通的 `ls`。
#### 7.3.2 对正在被扩展的文件执行 `ls`/`stat`
如果**另一个客户端**正在被列出的目录中扩展(extending)文件,那么 `ls -l` 可能耗时**异常长**——因为列举者必须**等待写入者 flush 数据**,才能对**每个文件的大小**做一次有效的读取。
- **建议**:除非你**真的**需要知道目录中每个文件的精确大小,否则不要这么做。
- 这同样适用于任何**直接**对正被其他节点追加写的文件发出 `stat` 系统调用的应用代码。
#### 7.3.3 超大目录
你真的需要一个 10,000,000 文件的目录吗?
- 虽然**目录分片(directory fragmentation)**使 CephFS 能够处理它,但它**永远不如**把文件拆分到规模更适中的多个目录中高效。
- 即使是标准用户态工具,在超大目录上操作时也会变得相当慢。例如:`ls` 的**默认行为**是给出**按字母排序**的结果,但 `readdir` 系统调用**不**给出有序结果(这在一般情况下成立,不只 CephFS)。因此当你在百万文件目录上 `ls` 时,它会把一百万个名字加载进内存、排序、再输出到显示。
#### 7.3.4 硬链接(Hard links)
硬链接在文件系统为维护「同一份数据的两个引用」所必须做的内部簿记方面,存在**固有代价**。在 CephFS 中存在**特别的性能代价**——因为对于普通文件,inode 是**内嵌在目录中**的(即查找路径之后**不需要额外再取一次 inode**)。
#### 7.3.5 工作集大小(Working set size)
MDS 充当存放在 RADOS 中元数据的**缓存**。对于**元数据能装进该缓存**的负载,元数据性能会**非常不同**。
- 若你的负载的文件数**超过**缓存容量(用 `mds_cache_memory_limit` 设置配置),请务必**相应地做测试**;
- **不要**用少量文件测试系统、然后期望迁移到大量文件时获得同等性能。
#### 7.3.6 你真的需要文件系统吗?
请记住 Ceph 还包含一个**对象存储接口**。如果你的应用需要存储**巨大的扁平文件集合**,且只是**整体读写**整个文件,那么使用 **Object Gateway(RGW)** 可能更合适。
> 引自:[Application best practices for distributed file systems](https://docs.ceph.com/en/tentacle/cephfs/app-best-practices/)
---
### 7.4 Mantle:可编程元数据负载均衡器(研究/开发用)
> ⚠️ **警告**:Mantle 用于**元数据均衡算法的研发**,**不用于生产 CephFS 集群**。
Mantle 是内建于 MDS 的**可编程元数据均衡器**。
| 对比 | 行为 |
|---|---|
| **默认(无 Mantle)** | 多个 active MDS 可以迁移目录以均衡元数据负载;**何时、迁往何处、迁移多少**的策略被**硬编码**在元数据均衡模块中 |
| **使用 Mantle** | 保留负载均衡的**机制**(migration、replication、fragmentation),同时用 **Lua** 抑制(stub out)均衡**策略** |
Mantle 基于 Supercomputing '15 论文 [1],但**当前实现不具备**该论文中的以下特性:
| # | 论文中有而当前 Mantle 没有的特性 |
|---|---|
| 1 | **Balancing API**:论文中用户填写 when / where / how much / load calculation 四类策略;当前 Mantle **只要求** Lua 策略返回一张**目标负载表**(例如给每个 MDS 发送多少负载) |
| 2 | **"how much" hook**:论文中有一个允许用户控制 "fragment selector policy" 的钩子;当前 Mantle **没有**这个钩子 |
| 3 | 以 **"Instantaneous CPU utilization"** 作为指标 |
[1] http://sc15.supercomputing.org/schedule/event_detail-evid=pap168.html
#### 7.4.1 vstart 快速上手
> ⚠️ 用 vstart 开发均衡器比较困难——所有守护进程与客户端跑在一个节点上可能使系统过载。让系统跑一会儿,即使可能出现大量 lost heartbeat 告警与 laggy MDS 告警。多数情况下本指南可行,但有时用 vstart 开发时所有 MDS 会锁死、你无法真正看到 spill。**最好在多节点集群上运行**。
前置条件:已安装 `mdtest` 或拉取了相应 Docker 镜像。之所以用 mdtest,是因为需要生成**足够大的负载**才能越过均衡器中被随意设定的 **MIN_OFFLOAD 阈值**。例如下面这样**不足以**产生足够的元数据负载:
```bash
while true; do
touch "/cephfs/blah-`date`"
done
```
**步骤**:
```bash
# 1) 启动 Ceph 并调整日志,以便看到迁移发生
cd build
../src/vstart.sh -n -l
for i in a b c; do
bin/ceph --admin-daemon out/mds.$i.asok config set debug_ms 0
bin/ceph --admin-daemon out/mds.$i.asok config set debug_mds 2
bin/ceph --admin-daemon out/mds.$i.asok config set mds_beacon_grace 1500
done
# 2) 把均衡器放入 RADOS
bin/rados put --pool=cephfs_metadata_a greedyspill.lua ../src/mds/balancers/greedyspill.lua
# 3) 激活 Mantle
bin/ceph fs set cephfs max_mds 5
bin/ceph fs set cephfs_a balancer greedyspill.lua
# 4) 在另一个窗口挂载 CephFS
bin/ceph-fuse /cephfs -o allow_other &
tail -f out/mds.a.log
```
> 注意:如果观察**最后一个** MDS(可能是 a、b 或 c,随机的),会看到一次 "attempt to index a nil value"。这是因为最后一个 MDS 试图检查**它邻居**的负载,而那个邻居**不存在**。
```bash
# 5) 运行基准(此处用 Docker 版 mdtest 制造负载)
for i in 0 1 2; do
docker run -d \
--name=client$i \
-v /cephfs:/cephfs \
michaelsevilla/mdtest \
-F -C -n 100000 -d "/cephfs/client-test$i"
done
# 6) 结束后杀掉所有客户端
for i in 0 1 2 3; do docker rm -f client$i; done
```
**日志输出解读**:
初始时所有人都没有负载:
```text
2016-08-21 06:44:01.763930 ... lua.balancer MDS0: < auth.meta_load=0.0 all.meta_load=0.0 req_rate=1.0 queue_len=0.0 cpu_load_avg=1.35 > load=0.0
2016-08-21 06:44:01.764010 ... lua.balancer when: not migrating! my_load=0.0 hisload=0.0
2016-08-21 06:44:01.764033 ... mds.0.bal mantle decided that new targets={}
```
任务开始后 MDS0 获得约 1953 单位负载。greedy spill 均衡器规定**一半负载**给邻居 MDS,因此 Mantle 试图向 MDS1 发送 1953 单位负载:
```text
2016-08-21 06:45:21.870034 ... lua.balancer when: migrating! my_load=1953.3492228857 hisload=0.0
2016-08-21 06:45:21.870050 ... mds.0.bal mantle decided that new targets={0=0,1=976.675,2=0}
2016-08-21 06:45:21.870094 ... mds.0.bal - exporting [0,0.52287 1.04574] 1030.88 to mds.1 [dir 100000006ab /client-test2/ ...]
2016-08-21 06:45:21.870151 ... mds.0.migrator nicely exporting to mds.1 [dir 100000006ab /client-test2/ ...]
```
最终负载会重新分布:
```text
2016-08-21 06:47:10.210298 ... lua.balancer when: not migrating! my_load=415.79000078186 hisload=186.5606496623
2016-08-21 06:47:10.210311 ... mds.0.bal mantle decided that new targets={}
```
#### 7.4.2 实现细节
| 方面 | 说明 |
|---|---|
| 主体实现 | 大部分实现在 **MDBalancer** 中。指标通过 **Lua stack** 传给均衡策略,策略返回一个**负载列表**给 MDBalancer |
| 与现有均衡器关系 | 与当前均衡器实现**并存**;由 Ceph CLI 命令启用:`ceph fs set cephfs balancer mybalancer.lua`。**若 Lua 策略因任何原因失败,会回退到原始元数据负载均衡器** |
| 存储 | 均衡器存放在 **RADOS 元数据池**中;**MDSMap 中的一个字符串**告诉各 MDS 使用哪个均衡器 |
**把指标暴露给 Lua**
- 指标**直接作为全局变量**暴露给 Lua 代码,而不是用定义良好的函数签名。
- 有一个全局 `"mds"` 表,每个索引是一个 MDS 编号(如 `0`),每个值是一个「指标 → 值」的字典。Lua 代码可以这样取指标:
```lua
mds[0]["queue_len"]
```
- 这与 OSD 中的 cls-lua 不同——后者有明确定义的参数(如 input/output bufferlists)。直接暴露指标的好处是**更容易新增指标**而无需改动 Lua 侧 API(我们希望 API 随着探索哪些指标重要而自由增删)。缺点是编写 Lua 均衡策略的人必须**查看 Ceph 源码**才知道暴露了哪些指标——不过我们假定 Mantle 开发者本来就会接触 MDS 内部机制。
- 暴露给 Lua 策略的指标与已存放在 `mds_load_t` 中的一致:`auth.meta_load()`、`all.meta_load()`、`req_rate`、`queue_length`、`cpu_load_avg`。
**编译/执行均衡器**
- 使用 `lua_pcall` 而非 `lua_call`,因为我们希望在 MDBalancer 中**处理错误**,不希望错误沿调用链向上传播。(cls_lua 想自己处理错误,因为它必须优雅失败。对 Mantle 而言,我们不介意 Lua 错误把均衡器搞崩——那我们就回退到原始均衡器。)
- 使用 `lua_call` 相对 `lua_pcall` 的性能优势在这里用不上,因为**均衡器默认每 10 秒才被调用一次**。
**把策略决策返回给 C++**
- 强制 Lua 策略引擎**返回一个值表**,对应「要发送给每个 MDS 的负载量」。这些负载被**直接插入** MDBalancer 的 `my_targets` vector。
- **不允许** MDS 返回一张「MDS 列表 + 指标」的表,因为我们希望决策**完全在 Lua 侧**做出。
- 遍历 Lua 返回的表是通过**栈**完成的。用 Lua 的说法:先把一个 dummy 值压栈,然后 next 迭代器用 (k, v) 对替换栈顶。读取每个值后弹出该值,但**保留 key** 供下一次 `lua_next` 调用。
**从 RADOS 读取**
- 当 **MDS Map 中的均衡器版本变化**时,所有 MDS 都会从 RADOS 读取均衡代码。均衡器**同步**地从 RADOS 拉取 Lua 代码。
- 用**超时**实现:若异步读在**均衡 tick 间隔的一半**之内没有返回,操作被取消并返回 **Connection Timeout** 错误。默认均衡 tick 间隔是 10 秒,因此 **Mantle 使用 5 秒超时**。这种设计允许 Mantle 在任何 RADOS 相关出错时**立即返回错误**。
- 之所以这样实现,是因为**不希望在全局 MDS 锁内部做阻塞式 OSD 读**——那样一旦任何 OSD 无响应就会拖垮整个 MDS 集群(ceph-qa-suite 中通过把所有 OSD 设为 down/out 并确认 MDS 集群保持 active 来测试这一点)。
- 另一种方案是在处理 MDS Map 时异步发起读、并在后台填充 Lua 代码,但**不能这样做**——因为 MDS 不支持 daemon-local fallbacks,而且均衡器假设所有 MDS 在同一时刻做出相同决策(如 importers、exporters 等)。
**调试**
Lua 策略中的日志会出现在 MDS 日志中,语法与 cls 日志接口相同:
```lua
BAL_LOG(0, "this is a log message")
```
- 它通过把一个包装了 `dout` 日志框架的函数(`dout_wrapper`)用 `lua_register()` 原语注册给 Lua 来实现。Lua 代码实际上调用的是 C++ 中的 `dout` 函数。
- Warning 与 Info 消息通过 clog/Beacon **集中处理**。成功消息**只在版本变化时**由第一个 MDS 发送,以避免刷屏 `ceph -w`(这些消息用于集成测试)。
**测试**
- 测试通过 **ceph-qa-suite** 完成(`tasks.cephfs.test_mantle`)。
- **不测试**非法均衡器的日志,以及加载真实 Lua VM。
> 引自:[Mantle](https://docs.ceph.com/en/tentacle/cephfs/mantle/)
---
## 八、命令速查表
> 约定:`<fs_name>` 文件系统名;`<rank>` MDS rank 号;`<path>` CephFS 内绝对路径;`<id>` 客户端会话 ID。
### 8.1 文件系统生命周期
| 命令 | 用途 |
|---|---|
| `ceph fs flag set enable_multiple true` | 允许创建多个文件系统(全局 flag) |
| `ceph fs new <fs_name> <metadata_pool> <data_pool>` | 创建文件系统 |
| `ceph fs new <fs_name> <meta> <data> --fscid <fscid> --force` | 指定 fscid 创建文件系统 |
| `ceph fs new <fs_name> <meta> <data> --force --recover` | 重建 FSMap 并把 rank 0 置为 existing-but-failed(mon store 丢失恢复) |
| `ceph fs ls` | 列出所有文件系统 |
| `ceph fs lsflags <fs_name>` | 列出该 FS 上设置的所有 flag |
| `ceph fs get <fs_name>` | 查看单个文件系统(设置 + rank) |
| `ceph fs dump [epoch]` | dump FSMap |
| `ceph fs status` | 查看文件系统状态(输出字段官方文档未逐项说明) |
| `ceph fs rm <fs_name> [--yes-i-really-mean-it]` | 销毁文件系统(不动池) |
| `ceph fs rename <fs_name> <new_name> [--yes-i-really-mean-it]` | 重命名文件系统 |
| `fs swap <fs1> <fs1_id> <fs2> <fs2_id> --swap-fscids=yes\|no [--yes-i-really-mean-it]` | 交换两个文件系统名 |
| `ceph fs set <fs_name> max_mds <n>` | 设置最大 active rank 数 |
| `ceph fs set <fs_name> max_file_size <bytes>` | 设置最大文件大小(默认 1TB) |
| `ceph fs set <fs_name> allow_new_snaps true` | 启用快照特性 |
| `ceph fs set <fs_name> allow_standby_replay true\|false` | 启用/禁用 standby-replay |
| `ceph fs set <fs_name> standby_count_wanted <n>` | 设置期望的 standby 数量(0 禁用告警) |
| `ceph fs set <fs_name> down true\|false` | 集群下线 / 上线 |
| `ceph fs set <fs_name> joinable true\|false` | 允许 / 禁止 standby 在该 FS 激活 |
| `ceph fs set <fs_name> refuse_client_sessions true\|false` | 拒绝 / 允许新客户端会话 |
| `ceph fs set <fs_name> balancer <balancer.lua>` | 启用 Mantle 均衡器(研发用) |
| `ceph fs fail <fs_name>` | 快速把 FS 拉下线(设置 joinable=false 并 fail 所有 rank) |
| `ceph fs reset <fs_name> [--yes-i-really-mean-it]` | 重置 FS 状态为默认(保留名称与池) |
| `ceph fs add_data_pool <fs_name> <pool>` | 添加数据池 |
| `ceph fs rm_data_pool <fs_name> <pool>` | 移除数据池 |
| `ceph fs required_client_features <fs_name> add\|rm <feature>` | 增减必需客户端特性 |
| `ceph fs feature ls` | 列出所有 CephFS 特性 |
| `ceph fs authorize <fs_name> client.<id> <path> <caps>` | 创建/更新客户端 caps(只能加不能减) |
| `ceph fs subvolume ...` / `ceph fs subvolumegroup ...` | 子卷 / 子卷组管理(见 8.6) |
| `ceph fs clone status <fs> <vol> <clone> [<group>]` | 查看克隆进度 |
| `ceph fs clone cancel <fs> <vol> <clone> [<group>]` | 取消克隆 |
| `ceph fs quiesce ...` | 子卷静默/一致性快照(Squid+) |
| `ceph fs perf stats [--client_id=N] [--mds_rank=1,2]` | 获取性能统计 |
| `ceph fs snap-schedule ...` | 快照调度(见 8.5) |
| `ceph fs snapshot mirror ...` | 快照镜像(见 8.8) |
### 8.2 MDS 守护进程与 rank
| 命令 | 用途 |
|---|---|
| `ceph mds fail <gid/name/role>` | 标记 MDS 为 failed(触发 failover) |
| `ceph mds fail <fs_name>:<n>` | fail 指定文件系统的第 n 个 rank |
| `ceph mds metadata <gid/name/role>` | 查看 MDS 元数据 |
| `ceph mds repaired <role>` | 把 damaged 的 rank 标记为已修复 |
| `ceph mds last-seen <name>` | 查询该 MDS 最后出现在 FSMap 的时间/epoch |
| `ceph mds rmfailed` | 从 failed set 移除一个 rank |
| `ceph tell mds.<daemon name> <command>` | 向某个 MDS 发命令 |
| `ceph tell mds.* help` | 列出所有可用命令 |
| `ceph tell mds.<fs>:0 scrub start <path> [scrubopts] [tag]` | 发起 scrub |
| `ceph tell mds.<fs>:0 scrub status` | 查看 scrub 状态 |
| `ceph tell mds.<fs>:0 scrub pause` / `resume` / `abort` | 暂停/恢复/中止 scrub |
| `ceph tell mds.<fs>:0 dump stray` | 导出 stray 目录内容 |
| `ceph tell mds.<fs>:0 flush journal` | flush journal(DR 前置步骤) |
| `ceph tell mds.0 client ls` | 列出客户端会话 |
| `ceph tell mds.0 client evict id=<id>` | 驱逐客户端 |
| `ceph daemon mds.<name> dump cache /tmp/dump.txt` | dump MDS 缓存 |
| `ceph daemon mds.<name> dump_ops_in_flight` | 列出在途操作 |
| `ceph tell mds.<fs_name>:0 status \| jq .replay_status` | 查看 journal 重放进度 |
| `ceph osd blocklist ls` / `ceph osd blocklist rm <addr>` | 查看 / 解除客户端 blocklist |
| `ceph orch apply cephfs-mirror [--placement=...]` | 部署 cephfs-mirror 守护进程 |
### 8.3 挂载
| 命令 | 用途 |
|---|---|
| `mount -t ceph <name>@<fsid>.<fs_name>=/ <mnt> -o ...` | 内核驱动挂载(v2 语法) |
| `mount.ceph <mon_addrs>:/ <mnt> -o name=...,secretfile=...` | 通过 `mount.ceph` 挂载 |
| `umount <mnt>` / `umount.ceph <mnt>` | 卸载 |
| `ceph-fuse <mnt> --id <name> [--client_fs <fs>] [-r <path>]` | FUSE 挂载 |
| `fusermount -u <mnt>` | 卸载 FUSE |
| `systemctl start ceph-fuse@<mnt>.service` | systemd 挂载 FUSE(`ceph-fuse@.service`) |
| `ceph-dokan.exe -c c:\ceph.conf -l x` | Windows 挂载(drive letter) |
| `ceph-dokan.exe --mountpoint C:\mnt\fs2 --client_fs mycephfs2` | Windows 挂载非默认 FS |
| `ceph-dokan -l y --root-path /a` | Windows 挂载子目录 |
| `ceph-dokan.exe unmap -l x` | Windows 卸载 |
| `mount -t nfs -o nfsvers=4.1,proto=tcp <ganesha-host>:<pseudo-path> <mnt>` | 挂载 NFS-Ganesha 导出 |
### 8.4 权限、配额与 layout
| 命令 | 用途 |
|---|---|
| `ceph auth get-or-create client.<id> mon '...' mds '...' osd '...'` | 创建客户端 |
| `ceph fs authorize <fs_name> client.<id> <path> <caps>` | 授权 MDS caps(如 `/ rwps`) |
| `ceph auth caps client.<id> mds "allow rw fsname=<fs>" ...` | 直接改 caps(可减权限) |
| `setfattr -n ceph.quota.max_bytes -v <bytes> <dir>` | 设置字节配额 |
| `setfattr -n ceph.quota.max_files -v <n> <dir>` | 设置文件数配额 |
| `getfattr -n ceph.quota.max_bytes <dir>` | 查看配额 |
| `setfattr -n ceph.quota.max_bytes -v 0 <dir>` | 移除配额 |
| `getfattr -n ceph.file.layout <file>` / `-n ceph.dir.layout <dir>` | 读 layout |
| `setfattr -n ceph.file.layout.<field> -v <val> <file>` | 写 layout 字段 |
| `setfattr -x ceph.file.layout.<field> <file>` | 清除 layout 字段 |
| `setfattr -n ceph.dir.pin -v <rank> <dir>` | 静态 export pin |
| `setfattr -n ceph.dir.pin.distributed -v 1 <dir>` | ephemeral distributed pin |
| `setfattr -n ceph.dir.pin.random -v <ratio> <dir>` | ephemeral random pin |
| `setfattr -n ceph.dir.casesensitive -v 0\|1 <dir>` | 目录大小写敏感开关 |
| `setfattr -n ceph.dir.normalization -v <form> <dir>` | 目录名规范化形式 |
| `setfattr -n ceph.dir.encoding -v <enc> <dir>` | 目录名编码 |
| `setfattr -n ceph.dir.charmap -v <file> <dir>` | 目录字符映射文件 |
### 8.5 快照
| 命令 | 用途 |
|---|---|
| `ceph fs set <fs_name> allow_new_snaps true` | 启用快照 |
| `mkdir <dir>/.snap/<name>` | 创建快照 |
| `rmdir <dir>/.snap/<name>` | 删除快照 |
| `ceph mgr module enable snap_schedule` | 启用快照调度模块 |
| `ceph fs snap-schedule status <path> [--format=json]` | 查看调度与保留策略 |
| `ceph fs snap-schedule list <path> [--recursive=true]` | 列出调度 |
| `ceph fs snap-schedule add <path> <repeat> [<start>]` | 新增调度 |
| `ceph fs snap-schedule remove <path> [<repeat>] [<start>]` | 删除调度 |
| `ceph fs snap-schedule retention add <path> <spec_or_period> [<count>]` | 新增保留策略 |
| `ceph fs snap-schedule retention remove <path> <spec_or_period> [<count>]` | 删除保留策略 |
| `ceph fs snap-schedule activate <path> [<repeat>] [<start>]` | 激活调度 |
| `ceph fs snap-schedule deactivate <path> [<repeat>] [<start>]` | 停用调度 |
### 8.6 fs-volumes(volumes 插件)
| 命令 | 用途 |
|---|---|
| `ceph fs volume create <vol_name>` | 创建卷 |
| `ceph fs volume ls` | 列出卷 |
| `ceph fs volume info <vol_name>` | 查看卷信息 |
| `ceph fs volume rm <vol_name> [--yes-i-really-mean-it]` | 删除卷 |
| `ceph fs volume rename <vol> <new_vol>` | 重命名卷 |
| `ceph fs subvolumegroup create <vol> <group> [--pool_layout <pool>] [--mode <oct>]` | 创建子卷组 |
| `ceph fs subvolumegroup ls <vol>` | 列出子卷组 |
| `ceph fs subvolumegroup rm <vol> <group>` | 删除子卷组 |
| `ceph fs subvolumegroup snapshot create <vol> <group> <snap>` | 子卷组快照 |
| `ceph fs subvolume create <vol> <subvol> [--size <bytes>] [--group_name <g>] [--pool_layout <pool>] [--mode <oct>] [--earmark <e>] [--namespace-isolated]` | 创建子卷 |
| `ceph fs subvolume ls <vol> [<group>]` | 列出子卷 |
| `ceph fs subvolume info <vol> <subvol> [<group>]` | 查看子卷信息 |
| `ceph fs subvolume getpath <vol> <subvol> [<group>]` | 获取子卷挂载路径 |
| `ceph fs subvolume resize <vol> <subvol> <bytes> [--group_name <g>]` | 调整子卷大小 |
| `ceph fs subvolume rm <vol> <subvol> [<group>] [--retain-snapshots]` | 删除子卷 |
| `ceph fs subvolume snapshot create <vol> <subvol> <snap> [<group>]` | 创建子卷快照 |
| `ceph fs subvolume snapshot ls <vol> <subvol> [<group>]` | 列出子卷快照 |
| `ceph fs subvolume snapshot rm <vol> <subvol> <snap> [<group>]` | 删除子卷快照 |
| `ceph fs subvolume snapshot clone <vol> <subvol> <snap> <target> [<group>]` | 从快照克隆 |
| `ceph fs subvolume snapshot protect <vol> <subvol> <snap> [<group>]` | 保护快照(克隆前) |
| `ceph fs clone status <vol> <clone> [<group>]` | 查看克隆状态 |
| `ceph fs clone cancel <vol> <clone> [<group>]` | 取消克隆 |
| `ceph fs subvolume pin <vol> <subvol> <type> <setting> [<group>]` | 给子卷设置 pin |
| `ceph fs subvolumegroup pin <vol> <group> <type> <setting>` | 给子卷组设置 pin |
| `ceph fs subvolume charmap set|rm|ls <vol> <subvol>` | 子卷 charmap |
| `ceph fs quiesce <vol> <subvol1> [<subvol2> ...] [--await]` | 静默子卷以取一致性快照 |
| `ceph config set mgr mgr/volumes/pause_purging true\|false` | 暂停/恢复异步 purge 线程 |
| `ceph config set mgr mgr/volumes/pause_cloning true\|false` | 暂停/恢复异步克隆线程 |
### 8.7 灾备与元数据修复
| 命令 | 用途 |
|---|---|
| `cephfs-journal-tool journal inspect` | 检查 journal 完整性 |
| `cephfs-journal-tool journal export <file>` | 导出 journal 备份(稀疏文件) |
| `cephfs-journal-tool journal import <file>` | 导入 journal |
| `cephfs-journal-tool [--rank=<fs>:<rank>\|all] journal reset [--force] --yes-i-really-really-mean-it` | 截断 journal |
| `cephfs-journal-tool header get` | 读取 journal header |
| `cephfs-journal-tool header set <trimmed_pos\|expire_pos\|write_pos\|pool_id> <value>` | 修改 header 属性 |
| `cephfs-journal-tool event get [filter] <list\|json\|summary\|binary>` | 读取 journal 事件 |
| `cephfs-journal-tool event splice [filter] <output>` | 擦除 journal 事件/区域 |
| `cephfs-journal-tool event recover_dentries summary` | 从 journal 恢复 dentry |
| `cephfs-table-tool <rank>\|all reset session\|snap\|inode` | 重置 MDS 表 |
| `cephfs-data-scan init [--force-init] [--filesystem <fs>] [--alternate-pool <pool>]` | 生成 root/mdsdir inode |
| `cephfs-data-scan scan_extents [<pool> ...] [--worker_n N --worker_m M] [--alternate-pool <pool>] [--filesystem <fs>]` | 扫描对象计算 size/mtime |
| `cephfs-data-scan scan_inodes [<pool>] [--worker_n N --worker_m M] [--force-corrupt] [--alternate-pool <pool>] [--filesystem <fs>]` | 收集元数据并注入元数据池 |
| `cephfs-data-scan scan_links [--filesystem <fs>]` | 检查并修复 inode linkage |
| `cephfs-data-scan cleanup [<pool>] [--worker_n N --worker_m M]` | 清理恢复期间生成的辅助数据 |
| `cephfs-data-scan pg_files <path> <pg id> [<pg id>...]` | 查找受丢失数据 PG 影响的文件 |
| `cephfs-data-scan tmap_upgrade <metadata pool>` | 升级 pre-Firefly 的 TMAP 目录对象 |
| `python3 first-damage.py --memo run.1 <pool>` | 列出损坏的 dentries |
| `python3 first-damage.py --memo run.2 --remove <pool>` | 移除损坏的 dentries |
| `rados -p <metadata-pool> stat mds0_sessionmap` | 检查 session 表对象是否存在 |
| `rados -p <metadata-pool> stat mds0_inotable` | 检查 inode 表对象是否存在 |
| `rados -p <metadata-pool> stat mds_snaptable` | 检查 snap 表对象是否存在 |
### 8.8 镜像(cephfs-mirroring)
| 命令 | 用途 |
|---|---|
| `ceph mgr module enable mirroring` | 启用 mirroring 模块 |
| `ceph fs snapshot mirror enable <fs_name>` | 为 FS 启用 mirroring |
| `ceph fs snapshot mirror disable <fs_name>` | 为 FS 禁用 mirroring |
| `ceph fs snapshot mirror peer_add <fs> <client>@<cluster> [<remote_fs>] [<remote_mon_host>] [<key>]` | 添加 peer |
| `ceph fs snapshot mirror peer_remove <fs> <peer_uuid>` | 移除 peer |
| `ceph fs snapshot mirror peer_list <fs>` | 列出 peers |
| `ceph fs snapshot mirror peer_bootstrap create <fs> <client_entity> <site-name>` | 生成 bootstrap token |
| `ceph fs snapshot mirror peer_bootstrap import <fs> <token>` | 导入 bootstrap token |
| `ceph fs snapshot mirror add <fs> <path>` | 添加待镜像目录 |
| `ceph fs snapshot mirror ls <fs>` | 列出待镜像目录 |
| `ceph fs snapshot mirror remove <fs> <path>` | 移除待镜像目录 |
| `ceph fs snapshot mirror daemon status` | 查看 mirror 守护进程状态 |
| `ceph --admin-daemon <asok> fs mirror status <fs>@<fsid>` | 查看 FS 镜像状态 |
| `ceph --admin-daemon <asok> fs mirror peer status <fs>@<fsid> <peer-uuid>` | 查看 peer 同步状态 |
| `systemctl start cephfs-mirror@mirror` | 启动 mirror 守护进程 |
| `cephfs-mirror --id mirror --cluster site-a -f` | 前台运行 mirror 守护进程 |
### 8.9 日常运维与排障
| 命令 | 用途 |
|---|---|
| `ceph status` / `ceph -s` | 集群状态(含 scrub task status) |
| `ceph health detail` | 详细健康信息(含 MDS_* 代码) |
| `ceph mgr module enable stats` | 启用 stats 插件(`ceph fs perf stats` 前置) |
| `cephfs-top [--id <name>] [--cluster <c>] [-d <sec>] [--dump] [--dumpfs <fs>]` | 实时指标 top 工具 |
| `ceph config set mds mds_tick_interval 2` | 加速 journal trim |
| `ceph config set mds mds_heartbeat_grace 3600` | 延长内部心跳宽限(避免恢复中被替换) |
| `ceph config set mds mds_deny_all_reconnect true` | 拒绝所有客户端重连 |
| `ceph config set mds mds_oft_prefetch_dirfrags false` | 关闭 open-file-table prefetch |
| `ceph config set mds debug_mds 0` / `debug_ms 0` / `debug_monc 0` | 临时关闭调试日志以加速 replay |
| `ceph config rm mds debug_mds` / `debug_ms` / `debug_monc` | 恢复默认调试级别 |
| `ceph config set mds mds_extraordinary_events_dump_interval <sec>` | 启用内存日志 dump |
| `echo 'module ceph +p' > /sys/kernel/debug/dynamic_debug/control` | 启用内核 CephFS 动态调试 |
| `dmesg` | 查看内核 ring buffer |
| `cat /sys/kernel/debug/ceph/<uuid>.client<id>/mdsc` | 内核客户端:发往 MDS 的请求 |
| `cat /sys/kernel/debug/ceph/<uuid>.client<id>/osdc` | 内核客户端:发往 OSD 的在途 op |
| `ceph osd pool application set <meta_pool> cephfs metadata <fs>` | 修补池的 application tag(升级后必做) |
| `ceph osd pool application set <data_pool> cephfs data <fs>` | 同上 |
| `ceph -v` | 检查客户端/集群版本(排查 mount 12) |
---
## 文档未明确项
以下内容在 Ceph Tentacle v20.2.4 的 `doc/cephfs/` 源文中**没有给出确定答案**,本册不做编造,列出以待查证:
| # | 项目 | 说明 |
|---|---|---|
| 1 | `session_autoclose` 的真实默认值 | `eviction.rst` 称默认 300 秒,但 `src/common/options/*.yaml.in` @ v20.2.4 中**查不到**该配置项(可能是文件系统变量而非 config option) |
| 2 | `session_timeout` 的真实默认值 | `health-messages.rst` 称 `MDS_HEALTH_CLIENT_LATE_RELEASE` 的阈值为 60 秒,但 yaml 中查不到该项 |
| 3 | `max_completed_requests` 的真实默认值 | `health-messages.rst` 称默认 100000,但 yaml 中查不到该项 |
| 4 | `ceph fs status` 的输出字段说明 | `doc/cephfs/` 下没有任何页面逐字段解释该命令的输出 |
| 5 | `mds_standby_for_fscid` / `mds_standby_for_name` / `mds_standby_for_rank` / `mds_standby_replay` | 源文(`standby.rst`)提到相关概念,但这些名字在 yaml 中查不到对应条目 |
| 6 | `mds_scrub_min_interval` / `mds_scrub_max_interval` | 源文中未出现,yaml 中也未查到 |
| 7 | `mds_client_max_purge_files` | 源文中未出现,yaml 中也未查到 |
| 8 | `mds_root_ino_client_skip` | 源文中未出现,yaml 中也未查到 |
| 9 | `defer_client_eviction_on_laggy_osds` 默认值冲突 | `health-messages.rst` 称 "default true",yaml 中默认值为 `false` |
| 10 | 部分 CephFS feature 的说明文字 | `jewel`、`kraken`、`luminous`、`mimic`、`notify_session_state`、`op_getvxattr`、`32bits_retry_fwd`、`new_snaprealm_info`、`has_owner_uidgid` 在 `administration.rst` 中只有版本表,**没有**单独的说明段落 |
| 11 | `MDS_SLOW_METADATA_IO` 的正式描述 | 该代码只出现在 `health-messages.rst` 的示例输出中,页面未为其单列小节 |
| 12 | `snapdirname`(内核挂载选项)的默认值 | `snapshots.rst` 只说明它可改写快照目录名,未给默认值;FUSE 侧的 `client_snapdir` 默认值为 `.snap` |
| 13 | `ceph-fuse` 的 `dump_ops_in_flight` 命令形式 | `troubleshooting.rst` 中该命令被注释掉(标注 "the command goes here - 10 Aug 2025") |
| 14 | 多个 active MDS 文件系统 / 多文件系统在 mon store 丢失后的恢复步骤 | `recover-fs-after-mon-store-loss.rst` 明确说明这些步骤**尚未确定** |
| 15 | data-scan 系列命令的时间估算 | 文档明确说明**无法估算**完成时间(功能开发中,见 tracker #63191) |
| 16 | `client_mds_auth_caps` 的内核版本 | 版本表中标注为 `PLANNED`,即上游内核支持尚未落地 |
| 17 | `new_snaprealm_info` 的内核版本 | 版本表中标注为 `UNKNOWN` |
| 18 | `first-damage.py` 脚本的获取方式 | 文档只给出调用方式,未说明它随哪个软件包发布或位于何处 |