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

300 KiB
Raw Permalink Blame History

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 的统一存储体系。

最小上手路径(大多数部署只需要一条命令):

# 创建一个名为 cephfs 的 CephFS volume
ceph fs volume create cephfs

Orchestrator 会自动创建并配置 MDS(前提是后端部署技术支持,见 Orchestrator 支持矩阵);否则需要手工部署 MDS(见 2.3 节)。挂载方式见 2.6~2.8 节。另外还提供了一个命令行交互工具 cephfs-shell 用于交互式访问或脚本化操作。

引自:Ceph File System

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

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

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

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

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


第二部分 部署与挂载

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
# 用默认设置创建两个池(示例)
ceph osd pool create cephfs_data
ceph osd pool create cephfs_metadata

注意:文件系统名、元数据池名、数据池名只能包含 [a-zA-Z0-9_-.] 字符集中的字符。

2.1.2 ceph fs new 完整语法

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):

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 状态:

ceph mds stat
# cephfs-1/1/1 up {0=a=up:active}

如果创建了多个文件系统,客户端挂载时若不指定,默认选哪个文件系统可用 ceph fs set-default 控制。

2.1.3 在 CephFS 中使用纠删码(EC)池

  • EC 池可以作为 CephFS 数据池,前提是开启 overwrites:
ceph osd pool set my_ec_pool allow_ec_overwrites true

注意:EC overwrites 仅在使用 BlueStore 后端的 OSD 上支持。

  • 若存储大量小文件或频繁修改文件,可开启 EC 优化提升性能:
ceph osd pool set my_ec_pool allow_ec_optimizations true
  • EC 池不能用作 CephFS 元数据池:CephFS 元数据使用 RADOS OMAP 数据结构存储,而 EC 池无法存 OMAP。

引自:Create a Ceph file system

2.2 多个文件系统(multifs)

默认情况下一个 Ceph 集群只允许一个 CephFS。启用多文件系统:

ceph fs flag set enable_multiple true

然后可以用 ceph fs volume create 创建额外的文件系统。

关键约束:

  • 多个文件系统之间不共享池(各有自己的元数据池与数据池)。
  • 每个文件系统有各自独立的 MDS rank 集合。
  • 每个文件系统需要独立的 standby 守护进程来保障高可用。

引自:Creating Multiple File Systems

2.3 部署、增加、移除 MDS(add-remove-mds)

2.3.1 硬件建议

项目 建议
CPU MDS 是单线程的,2~3 核足够
内存 缓存按 4GB 配置时,建议 8GB RAM;大型集群可能需要 64GB
磁盘 MDS 本地不持久化元数据,本地盘需求小,但 journal 与 cache 相关内存开销大

2.3.2 手工部署一个 MDS

# 创建 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):

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)

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,集群会通过健康告警提示。)

# 扩容前: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

# 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:

setfattr -n ceph.dir.pin -v 2 path/to/dir
规则 说明
取值 要把该目录子树分配到的 rank。默认值 -1 表示该目录未被 pin
继承 一个目录的 export pin 继承自最近的、设置了 export pin 的父目录。因此在目录上设置 pin 会影响其所有子目录
覆盖 子目录可以显式设置 pin 来覆盖父目录的 pin
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
# 分布式:让 /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,反之亦然。

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。

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:

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。

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
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

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,则以下命令等价:

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 守护进程数,用于健康判定:

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 守护进程跟随它。

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"——该行为可以禁用:
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):

$ 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

2.6 挂载前置条件(mount-prerequisites)

挂载 CephFS(无论内核还是 FUSE)之前,客户端需要:

  1. 一份最小化的 ceph 配置文件与 keyring。生成最小配置:
ceph config generate-minimal-conf > /etc/ceph/ceph.conf
chmod 644 /etc/ceph/ceph.conf
  1. 一个有 CephFS 访问权限的 cephx 用户:
ceph fs authorize cephfs client.foo / rw
  1. keyring 权限收紧:
chmod 600 /etc/ceph/ceph.client.foo.keyring

引自:Mount CephFS: Prerequisites

2.7 使用内核驱动挂载

CephFS 内核驱动是 Linux 内核的一部分,可以以原生内核性能把 CephFS 挂成常规文件系统。它是大多数用例的首选客户端。

CephFS 挂载设备串现在使用新语法("v2")。mount helper 向后兼容旧语法,内核也向后兼容旧语法——也就是说用更新的 mount helper 和内核时,旧语法依然可用。

2.7.1 前置检查

# 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 通用语法

mount -t ceph {device-string}={path-to-mounted} {mount-point} -o {key-value-args} {other-args}

2.7.3 挂载示例(v2 语法)

mkdir /mnt/mycephfs
mount -t ceph <name>@<fsid>.<fs_name>=/ /mnt/mycephfs
  • name:用于挂载的 CephX 用户名。
  • fsid:集群 FSID,可用 ceph fsid 查;fs_name:要挂载的文件系统。
  • 内核驱动需要 monitor 地址与 CephX 用户的 secret key:
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 来发现这些信息:

mount -t ceph cephuser@.cephfs=/ -o secret=AQATSKdNGBnwLhAAnNDKnH65FmVKpXZJVasUeQ==

注意:cephuser@.cephfs 中的点(.)必须是设备串的一部分。

上面这种写法会把 secret 留在 shell 命令历史里。改用文件传密钥:

mount -t ceph cephuser@.cephfs=/ /mnt/mycephfs -o secretfile=/etc/ceph/cephuser.secret

密钥文件权限建议设为 600。

多个 monitor 地址用 / 分隔:

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 关闭时省略所有凭据相关选项:

mount -t ceph cephuser@.cephfs=/ /mnt/mycephfs

注意:Ceph 用户名必须作为设备串的一部分传入。

挂载子树:把路径追加到设备串末尾:

mount -t ceph cephuser@.cephfs=/subvolume/dir1/dir2 /mnt/mycephfs -o secretfile=/etc/ceph/cephuser.secret

2.7.4 旧语法(向后兼容)

mkdir /mnt/mycephfs
mount -t ceph :/ /mnt/mycephfs -o name=admin

挂载非默认文件系统(例如 cephfs2):

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 卸载

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

2.8 使用 FUSE 挂载

ceph-fuse 是内核驱动的替代方案,挂载发生在用户态。因此 ceph-fuse 挂载性能低于内核驱动,但更容易管理、更容易升级。

注意:用 FUSE 挂载 CephFS 需要超级用户权限(sudo/root)。libfuse 接口没有提供裁剪内核中缓存项的机制,因此需要一个 remount(mount(2))系统调用来强制内核丢弃缓存的元数据;ceph-fuse 会周期性发出这些 remount 调用,以响应 MDS 的缓存压力或元数据缓存回收。

2.8.1 语法与示例

ceph-fuse {mount point} {options}
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 时用)
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 卸载

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 条目后执行:

systemctl start ceph-fuse@/mnt/mycephfs.service
systemctl enable ceph-fuse.target
systemctl enable ceph-fuse@-mnt-mycephfs.service

引自:Mount CephFS 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

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


第三部分 权限与配额

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。

授权语法:

ceph fs authorize <fs_name> client.<client_id> <path-in-cephfs> rw

示例:限制客户端 foo 只能在文件系统 cephfs_a 的 bar 目录中写:

ceph fs authorize cephfs_a client.foo / r /bar rw

结果:

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 目录,省略根目录:

ceph fs authorize cephfs_a client.foo /bar rw

若客户端的读权限被限制在某个路径,客户端只能在挂载命令中指定一个可读路径来挂载文件系统。

用 all 或 * 作为文件系统名表示授权访问每一个文件系统。通常需要对 * 加引号以避免被 shell 展开。

挂载侧限制子目录:

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 文件系统的用量。

要让客户端报告文件系统的整体用量(而非挂载子目录上的配额用量),在客户端设置:

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 则不行:

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 必须按字母顺序指定)。

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)

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 个文件系统:

$ 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"

该客户端只能看到它被授权的文件系统:

$ 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:

$ 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)成功:

$ 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 失败:

$ 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 目录树内除外:

ceph fs authorize a client.test_a / rw root_squash /volumes rw
ceph auth get client.test_a
[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)。

重复执行同一条命令的行为:

$ 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:

$ 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:

$ 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 权限示例:

$ 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)任何能力:

$ 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:

$ 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

3.2 CephFS 中的 capabilities(capabilities)

当客户端想操作一个 inode 时,会以多种方式查询 MDS,MDS 随后授予客户端一组 capabilities(能力),赋予客户端以各种方式操作该 inode 的权限。与 NFS、SMB 等其他网络文件系统的一个主要差别是:CephFS 授予的能力相当细粒度,多个客户端可以对同一个 inode 持有不同的能力。

3.2.1 Generic cap bits(通用能力位)

/* 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 的哪一部分数据或元数据":

/* 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 位移会用到前两位以外的位。

由每个位值左移到正确的位,就得到一批常量:

#define CEPH_CAP_AUTH_SHARED  (CEPH_CAP_GSHARED  << CEPH_CAP_SAUTH)

这些位可以 or 在一起,构成表示一组能力的位掩码。

唯一的例外——"pin":

#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

3.3 CephFS 配额(quota)

CephFS 允许在文件系统的任何目录上设置配额。配额可以限制该目录层级之下存储的字节数或文件数。

与 CephFS 中大多数其他东西一样,配额通过虚拟扩展属性配置:

虚拟 xattr 含义
ceph.quota.max_files 文件数限制
ceph.quota.max_bytes 字节数限制

如果这些扩展属性出现在某个目录上,说明那里配置了配额;若不存在,则该目录没有配额(但父目录上可能仍配了配额)。

3.3.1 设置配额

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 也支持人类友好单位:

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 查看配额

$ 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 移除或禁用配额

方式一:删除属性

$ 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

$ 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 段设置以下选项可禁用该行为:

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

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 侧存储的目录项名只是这些规范化后的名字。

$ 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 选择默认的规范化形式。

所有字符映射配置都必须启用规范化。 移除规范化会导致恢复默认值:

$ 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 为:

$ 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:

$ 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 才能使用该文件系统:

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


第四部分 数据与元数据管理

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

作为单个字符串读出:

$ 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"

逐个字段读:

$ 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:

$ 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 是从某个祖先继承来的
$ 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

$ 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 字段时,该文件必须为空,否则会报错:

# 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(以便更好地消歧)。

$ 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:

setfattr -x ceph.dir.layout mydir

类似地,若设置了 pool_namespace 属性、想改回默认 namespace:

# 创建目录并在其上设置 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 的修改不会影响已存在的子文件。

$ 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:

$ 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 上:

$ ceph fs add_data_pool cephfs cephfs_data_ssd
$ ceph fs ls  # 新池应已出现
.... data pools: [cephfs_data cephfs_data_ssd ]

确认你的 CephX key 允许客户端访问这个新池。然后更新 CephFS 中某个目录的 layout 使用这个池:

$ 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

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 客户端用户,至少具备以下能力:
mon 'allow r'
mgr 'allow rw'

4.2.2 FS Volumes(卷)

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(子卷)

创建:

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 规范化。

删除子卷:

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)

发起克隆:

# 克隆到默认组
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 配置选项。

查看克隆状态:

ceph fs clone status <vol_name> <clone_name> [--group_name <group_name>]

克隆状态取值:

状态 含义
pending 克隆操作尚未开始
in-progress 克隆进行中
complete 克隆成功完成
failed 克隆失败
canceled 克隆被用户取消

失败原因通过 errno(错误号)与 error_msg(错误字符串)展示。

in-progress 克隆的示例(含进度报告):

ceph fs subvolume snapshot clone cephfs subvol1 snap1 clone1
ceph fs clone status cephfs clone1
{
  "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 输出的底部(所有进行中克隆的集合进度):

progress:
  3 ongoing clones - average progress is 47.569% (10s)
    [=============...............] (remaining: 11s)

若克隆任务数超过 cloner 线程数,会打印两条进度条(进行中的 + 全部进行中+待处理的):

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 克隆示例:

{
    "status": {
        "state": "failed",
        "source": {
            "volume": "cephfs",
            "subvolume": "subvol1",
            "snapshot": "snap1"
            "size": "104857600"
        },
        "failure": {
            "errno": "122",
            "errstr": "Disk quota exceeded"
        }
    }
}

重试失败的克隆:必须先删除未完成的克隆,再重新发起克隆:

ceph fs subvolume rm <vol_name> <clone_name> [--group_name <group_name>] --force

注意:克隆只同步目录、普通文件和符号链接。inode 时间戳(访问时间与修改时间)同步到秒级粒度。

取消克隆:in-progress 或 pending 的克隆可以被取消:

ceph fs clone cancel <vol_name> <clone_name> [--group_name <group_name>]

取消成功后,克隆出的子卷进入 canceled 状态:

{
    "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 等配置)不可用时,拒绝克隆创建请求
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)。

# 子卷组
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 策略):

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
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,也是命令名:

$ 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:

$ 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 打一致性快照

$ 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)

排除成员:

$ ceph fs quiesce fs1 --set-id="snapshots" --exclude sub3

--if-version(乐观并发控制):

--if-version=<version> 会与 set 的当前版本比对;若不匹配,命令返回 ESTALE,且所请求的动作不会被执行。

$ ceph fs quiesce fs1 --set-id="snapshots" --release --await --if-version=13

另一个用途(对自动化软件很有用):传入 --if-version=0 可以确保 set id 确实是新的——只有不存在同名 set 时才会创建新 set:

$ 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 模块,也可以禁用它:

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

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 启用快照功能

新创建的文件系统默认开启快照特性。对已有文件系统,需要显式打开:

$ ceph fs set <fs_name> allow_new_snaps true

开启后,CephFS 中所有目录都会出现一个 .snap 目录。

4.3.2 创建快照

在 .snap 下创建子目录即为创建快照。例如对 /file1/ 创建名为 my_snapshot 的快照:

$ touch file1
$ cd .snap
$ mkdir my_snapshot

4.3.3 用快照恢复数据

一次典型的「误删恢复」流程:

# 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:

$ cd .snap
$ rmdir my_snapshot

注意:直接删除承载快照的目录会失败,必须先删除其中的所有快照。

💡 补充:删除快照是元数据操作,但实际数据对象的回收走 MDS 的 purge queue(见 4.14),因此 ceph df / du 的容量下降会滞后于 rmdir 返回。

引自:CephFS Snapshots


4.4 快照调度模块(snap-schedule)

snap_schedule 是 Ceph Manager 的一个模块,提供定时快照的增删查改以及按保留策略(retention policy)自动清理旧快照的能力。

4.4.1 启用

$ 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 个快照」。
# 查看
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 来「暂停」某个调度。
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


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


4.6 文件系统 Scrub

CephFS 提供一组 scrub 命令供管理员检查文件系统一致性。Scrub 分两类:

类型 说明
Forward Scrub 从文件系统根(或某个子目录)出发,遍历层次结构中所有可触及的对象,校验一致性
Backward Scrub 遍历文件系统池中的每一个 RADOS 对象,反查它在文件系统层次结构中的归属

⚠️ 警告:CephFS forward scrub 必须在 rank 0 上发起和控制,所有 scrub 命令都要发往 rank 0。

4.6.1 发起 scrub

ceph tell mds.<fsname>:0 scrub start <path> [scrubopts] [tag]
  • scrubopts:逗号分隔的列表,取值 recursive、force、repair
  • tag:可选的自定义字符串标签(默认是生成的 UUID)
$ 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

$ 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 段也会显示概要:
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 损坏

修复命令:

ceph tell mds.<fsname>:0 scrub start /path recursive, repair, force

若修复成功,对应条目会自动从 damage table 中移除。

注意:带 repair 选项的 scrub 能识别损坏的硬链接,但不能修复它。

4.6.5 用 recursive scrub 评估 strays

# 评估/清理 ~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 目录内容

$ 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


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


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 循环

/* 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


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


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


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


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 中记录的事件列表:

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


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


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,它通常就足以加快空间回收:

$ ceph config set mds filer_max_purge_ops 40

若还不够,再继续调整其他几个:

$ 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 统计形如:

"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


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> 段按客户端展示指标:

"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


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 插件默认禁用,需显式启用:

$ ceph mgr module enable stats

启用后可通过以下命令获取 CephFS 指标:

$ 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 子集过滤:

# 只取某个客户端(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 用户连接集群:

$ 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


第五部分 运维与排障

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 已可服务客户端时,执行:
ceph fs set <fs> joinable true
ceph fs set <fs> refuse_client_sessions false
  • 做 DR swap 时,可以有意把其中一个 FS 留作离线以便后续分析。

5.1.3 文件系统设置(Settings)

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 集群下线:

ceph fs set <fs_name> down true

恢复上线:

ceph fs set <fs_name> down false
  • 恢复上线时会还原此前 max_mds 的值。
  • MDS 守护进程的下线方式是:journal 被 flush 到元数据池,且所有客户端 I/O 被停止。

5.1.5 快速下线(用于删除或灾难恢复)

要快速删除一个文件系统(例如测试)或迅速把文件系统与 MDS 守护进程拉下线,用 ceph fs fail:

ceph fs fail <fs_name> {--yes-i-really-mean-it}

注意:确认 flag 是可选的,因为只有当 MDS 处于 active 且存在健康告警 MDS_TRIM 或 MDS_CACHE_OVERSIZED 时才需要它。

该命令设置一个文件系统 flag 以阻止 standby 在该 FS 上激活(即 joinable flag)。

等价的手动流程:

# 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 全局设置与高级命令

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


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 输出片段示例:

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


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


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_ 开头的唯一代码。

$ 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


5.5 排障(troubleshooting)

5.5.1 慢/卡住的操作:定位思路

CephFS 操作有时会挂起。排障第一步是定位导致挂起的问题所在,问题只可能出现在三处:

# 位置
1 客户端
2 MDS
3 连接客户端与 MDS 的网络

流程:

  1. 用 5.5.2 的方法判断是客户端有卡住的操作,还是 MDS 有卡住的操作。
  2. Dump MDS cache——缓存内容将用于诊断问题性质:
# 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 列出当前操作:

# 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 可能要处理数小时——没有绕过的办法,但可以加速:

# 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 何时完成,请在进入下一个状态之前恢复这些配置:

ceph config rm mds debug_mds
ceph config rm mds debug_ms
ceph config rm mds debug_monc
# 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 警告,消息中包含预计剩余时间:
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。

# 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):

# ceph daemon -d mds.<name> dump_ops_in_flight --debug-client=20 --debug-ms=1

若怀疑 monitor 有问题,再加上 monitor 调试(--debug-monc=20):

# ceph daemon -d mds.<name> dump_ops_in_flight --debug-client=20 --debug-ms=1 --debug-monc=20

5.5.9 内核挂载调试

诊断与修复内核客户端问题的第一步,是判断问题在内核客户端还是 MDS。若内核客户端本身有问题,其证据会出现在内核 ring buffer 中:

# 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 输出中包含类似下面的内容,即处于该情形:

[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 内核驱动)启用:

# echo 'module ceph +p' > /sys/kernel/debug/dynamic_debug/control

禁用:

# 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,以避免生产环境默认出现日志文件膨胀。

启用步骤(按顺序执行两条命令):

# 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,再执行上述命令。

禁用:

# 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' 错误或类似安全问题),你必须先运行:

$ 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 文件系统名。池名也是如此——因为「默认值本身」在各个大版本中发生了变化:

# 旧
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


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 手动驱逐

典型场景:客户端已死、管理员不想等其会话超时;或客户端行为异常而管理员无法访问该客户端节点去卸载。

先查看客户端列表:

$ 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 或其他属性来驱逐:

# 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 中移除来允许其重连:

$ 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


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
systemctl restart ceph-mds.target
为减少 failover,建议(但非必须)先升级 standby 守护进程
5. 恢复原先的 max_mds 与 allow_standby_replay ceph fs set <fs_name> max_mds <old_max_mds>
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 并重启服务后,运行:

cephfs-data-scan tmap_upgrade <metadata pool name>
要点 说明
执行次数 只需运行一次
是否需要停服 不需要停止任何其他服务
耗时 可能较长——它会迭代元数据池中的所有对象
是否可在线 可以,执行期间可正常使用文件系统
中断处理 若因任何原因中止,直接重跑即可

若要把 pre-Firefly 的 CephFS 升级到比 Jewel 更新的 Ceph 版本,必须先升级到 Jewel 并运行 tmap_upgrade 命令,然后才能继续升级到最新版本。

引自:Upgrading the MDS Cluster


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


第六部分 灾备与镜像

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)
  • 对数据池有读能力
$ 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 有完整能力:

$ ceph fs authorize <fs_name> client.mirror_remote / rwps

该用户将在添加 peer 时作为 peer 规格的一部分提供。

6.1.3 启动 Mirror 守护进程

Mirror 守护进程由 systemd 管理,但多数情况下最好使用 cephadm 接口:

$ systemctl enable cephfs-mirror@mirror
$ systemctl start cephfs-mirror@mirror

也可以在前台运行:

$ cephfs-mirror --id mirror --cluster site-a -f

注意:这里指定的用户是 mirror,其创建方法见 6.1.2。

多守护进程与负载分担:可以部署多个 cephfs-mirror 守护进程以并发同步并实现高可用。Mirror 守护进程使用简单的 M/N 策略分担同步负载,其中 M 是目录数,N 是 cephfs-mirror 守护进程数。

当用 cephadm 管理集群时:

# 部署(默认数量)
$ 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 模块默认禁用,启用:

$ 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),则:

$ ceph fs snapshot mirror peer_add primary_fs client.mirror_remote@remote_ceph remote_fs

⚠️ 注意:当前只支持单个 peer。

路径规则:

  • 只允许绝对路径。
  • 路径会被 mirroring 模块规范化:/a/b/../b 等价于 /a/b。
  • 路径总是从 CephFS 文件系统根开始,而不是从主机挂载点开始。
$ 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

层级约束:一个目录被加入镜像后,不允许再添加其子目录或祖先目录:

$ 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 来避免这一点。

# 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 发起快照镜像

在主集群上为已配置的目录创建快照即可触发镜像:

$ mkdir -p /d0/d1/d2/.snap/snap1

6.1.8 镜像状态(Mirroring Status)

A. 守护进程状态(模块接口)

$ 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 命令

$ 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 守护进程是异步收到文件系统镜像状态通知的(一个文件系统可能被删除后又以相同名字重建)。

$ 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:

$ 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 下显示正在同步的快照:

$ 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 状态:
$ 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 状态:
# 在远端文件系统上
$ 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


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 子命令扫描可能因此受损的文件:

cephfs-data-scan pg_files <path> <pg id> [<pg id>...]

例如,若 PG 1.4 和 4.5 的数据已丢失,想知道 /home/bob 下哪些文件受损:

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


6.3 高级:元数据修复工具(disaster-recovery-experts)

⚠️ 警告:如果你不具备 CephFS 内部机制的专家知识,在使用以下任何工具之前需要寻求帮助。这里提到的工具既能修好问题,也同样容易造成破坏。在尝试修复之前,必须准确理解你的文件系统到底出了什么问题。 如果无法获得集群的专业支持,请咨询 ceph-users 邮件列表或 #ceph IRC/Slack 频道。

注意:CephFS 文件系统必须先离线,元数据修复工具才能作用于它。若文件系统在线时调用,这些工具会报错。如果任何恢复步骤未成功完成,切勿继续执行后续恢复步骤。 若任何恢复步骤失败,请通过邮件列表、IRC 频道和 Slack 频道向专家求助。

6.3.1 Journal 导出(备份)

在执行任何危险操作之前,先复制一份 journal:

# cephfs-journal-tool journal export backup.bin

若恢复过程不如预期,这份备份会派上用场。可通过导入把它还原到原始状态:

# cephfs-journal-tool journal import backup.bin

6.3.2 从 journal 恢复 dentry

若 journal 损坏、或 MDS 因任何原因无法重放它,尝试运行下面的命令恢复文件元数据:

# 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:

# 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:

# 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 以反映元数据池的新状态:

# 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)缺失/损坏:

# 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:

# 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. 清理

完成元数据恢复后,可能需要运行一次清理操作,删除恢复期间生成的辅助数据:

# cephfs-data-scan cleanup [<data pool>]

清理阶段也可并行运行以加速:

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
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
cephfs-table-tool cephfs_recovery:0 reset snap
cephfs-table-tool cephfs_recovery:0 reset inode
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
cephfs-data-scan scan_extents --alternate-pool cephfs_recovery_meta --filesystem <fs_name>
cephfs-data-scan scan_inodes --alternate-pool cephfs_recovery_meta --filesystem <fs_name> --force-corrupt
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
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


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

# cephfs-journal-tool journal inspect
Overall journal integrity: DAMAGED
Objects missing:
  0x1
Corrupt regions:
  0x400000-ffffffffffffffff

示例:Journal import/export

# 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

# 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

# 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


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


第七部分 生态与集成

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:

$ 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 生成。例如:

[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 平台:

mount -t nfs -o nfsvers=4.1,proto=tcp <ganesha-host-name>:<ganesha-pseudo-path> <mount-point>

引自:NFS


7.2 在 Windows 上挂载 CephFS(ceph-dokan)

ceph-dokan 用于在 Windows 上挂载 CephFS 文件系统。它利用 Dokany——一个允许以类似 FUSE 的方式在用户空间实现文件系统的 Windows 驱动。

7.2.1 挂载文件系统

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 选项修改:

[client]
# client_permissions = true
client_mount_uid = 1000
client_mount_gid = 1000

挂载非默认文件系统:若集群上有多个文件系统,用 --client_fs 选项:

mkdir -Force C:\mnt\mycephfs2
ceph-dokan.exe --mountpoint C:\mnt\mycephfs2 --client_fs mycephfs2

挂载子目录:用 --root-path 参数:

ceph-dokan -l y --root-path /a

可移动标志:若设置了 -o --removable 标志,挂载会出现在 Get-Volume 结果中:

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 用户名。以下两条命令等价:

ceph-dokan --id foo -l x
ceph-dokan --name client.foo -l x

7.2.3 卸载文件系统

可以通过 ctrl-c 或使用 unmap 命令移除挂载:

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


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 时,它会把一百万个名字加载进内存、排序、再输出到显示。

硬链接在文件系统为维护「同一份数据的两个引用」所必须做的内部簿记方面,存在固有代价。在 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


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 阈值。例如下面这样不足以产生足够的元数据负载:

while true; do
  touch "/cephfs/blah-`date`"
done

步骤:

# 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 试图检查它邻居的负载,而那个邻居不存在。

# 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

日志输出解读:

初始时所有人都没有负载:

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 单位负载:

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/ ...]

最终负载会重新分布:

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 代码可以这样取指标:
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 日志接口相同:

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


八、命令速查表

约定:<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
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 脚本的获取方式 文档只给出调用方式,未说明它随哪个软件包发布或位于何处