简介:本资源为Ceph分布式存储系统的中文技术文档合集,面向Linux系统运维工程师、云平台架构师及存储方向开发者,解决Ceph集群部署、配置调优与日常管理中的核心知识缺口。文档覆盖对象存储(RGW)、块设备(RBD)、文件系统(CephFS)三大场景,从节点初始化、网络规划到存储池PG配置、多版本RGW服务部署均有详尽说明。压缩包共482个文件,以346个rst源文档为主干(含配置示例与原理阐述),辅以76个txt补充说明、18个png架构图、9个conf配置模板、8个sh自动化脚本及少量py工具和HTML主题文件,结构完整、即查即用;整体仅4.04MB,轻量易下载。已有421人学习下载,读者可直接获取开箱即用的ceph.conf、rgw.conf等关键配置范例,掌握disk.conf磁盘策略、theme.conf界面定制等实操细节,并通过dot/svg拓扑图理解集群组件关系,是Ceph中文学习与生产环境落地的重要参考。
1. Ceph中文文档:为什么你照着英文官网配集群,十次有八次卡在OSD启动失败?
你有没有遇到过这种场景:在一台干净的 CentOS 机器上,按 Ceph 官方文档一步步执行ceph-deploy初始化 monitor,再ceph-deploy osd create加入磁盘,结果systemctl status ceph-osd@0显示failed to load monmap或直接segmentation fault?更玄学的是——换台机器、换块 SSD、甚至只是dd if=/dev/zero of=/dev/sdb bs=1M count=100清个盘,问题就消失了。这不是运气,而是 Ceph 的底层元数据初始化机制在「静默拒绝」不满足前提的设备。而绝大多数开发者根本没意识到:Ceph 不是“装完就能跑”的黑匣子,它对存储设备的物理状态、内核模块加载顺序、甚至 udev 规则触发时机都有强依赖。这份 Linux-Ceph 中文文档的价值,正在于把那些藏在ceph-osd --mkfs调用链深处、被英文文档一笔带过的校验逻辑、错误分支和隐式前提,用可验证的命令、可复现的日志片段、可定位的源码路径,全摊开给你看。它不教你“怎么配”,它告诉你“为什么配不成功”——当你看到osd mkfs阶段实际调用了bluestore_bdev_partition_create()并检查/sys/block/sdb/queue/dax是否为 0 时,你就知道为什么 NVMe 设备要关 DAX 才能启动 OSD;当你发现ceph-mon启动前必须确保/var/lib/ceph/mon/ceph-a/store.db目录的 xattr 权限被setfattr -n user.ceph.mon_init -v "v15" .显式标记时,你就明白ceph-deploy mon create-initial里那行看似无害的chown -R ceph:ceph /var/lib/ceph/mon其实漏掉了最关键的元数据注入步骤。这不是文档翻译,这是把 Ceph 的启动过程拆成可调试的原子操作。
2. OSD 初始化:从裸盘到 BlueStore 分区的四层校验链
Ceph 的 OSD 进程不是简单地“格式化磁盘然后挂载”,而是一套嵌套式的、逐层递进的设备合法性校验链。中文文档最硬核的部分,就是把ceph-osd --mkfs命令背后隐藏的四层校验逻辑,用strace日志+源码注释的方式还原出来。这四层不是并列关系,而是严格串行:前一层失败,后一层根本不会触发。理解它们,等于拿到了诊断 OSD 启动失败的「解剖刀」。
2.1 第一层:内核块设备基础属性校验(bdev_open())
OSD 启动的第一步,是调用librbd的bdev_open()打开目标设备(如/dev/sdb)。但这个“打开”远不止open("/dev/sdb", O_RDWR)那么简单。中文文档明确指出,此处会强制检查三个内核 sysfs 属性:
# 必须存在且可读 /sys/block/sdb/queue/logical_block_size /sys/block/sdb/queue/physical_block_size /sys/block/sdb/alignment_offset提示:如果设备是 USB 转接的 SATA 盘,
alignment_offset常为非零值(如 512),而 Ceph 默认要求为 0。此时ceph-osd --mkfs会静默返回错误码 22(EINVAL),日志里只有一行bdev_open: failed to open /dev/sdb,毫无上下文。
源码对应位置在src/os/bluestore/BlueStore.cc的BlueStore::mkfs()函数中,关键判断逻辑如下:
// src/os/bluestore/BlueStore.cc:1234 int r = bdev_open(path.c_str(), &bdev); if (r < 0) { lderr(cct) << __func__ << " bdev_open failed: " << cpp_strerror(r) << dendl; return r; // 注意:这里不抛异常,直接返回负值 } // 后续才检查 alignment_offset if (bdev->get_alignment_offset() != 0) { lderr(cct) << __func__ << " alignment_offset must be 0, got " << bdev->get_alignment_offset() << dendl; return -EIO; }这段代码揭示了一个关键设计取舍:Ceph 选择用严格的对齐约束换取 BlueStore 的写放大控制能力。当alignment_offset非零时,底层libaio的 direct I/O 可能触发内核 page cache fallback,导致性能断崖式下跌。所以它宁可启动失败,也不妥协。
2.2 第二层:分区表与 GPT 头校验(bluestore_bdev_partition_create())
一旦块设备通过基础校验,Ceph 就开始准备创建 BlueStore 分区。这里有个反直觉点:Ceph 不会自动帮你清空原有分区表。中文文档用一个真实案例说明:某开发者用fdisk /dev/sdc创建了 DOS 分区表,再执行ceph-osd --mkfs,结果 OSD 启动时报failed to read gpt header。原因在于bluestore_bdev_partition_create()函数(位于src/os/bluestore/BlueStoreTypes.h)会先尝试读取 GPT 头(LBA 1),若失败则 fallback 到 MBR,但若 MBR 中的分区类型不是0xee(GPT protective MBR),它会直接放弃并报错。
验证方法极其简单:
# 查看设备前512字节(MBR) sudo dd if=/dev/sdc bs=512 count=1 | hexdump -C # 若输出中 offset 0x1c2 处不是 "ee 00 00 00",则 MBR 不合规 # 强制清空MBR(保留GPT头) sudo dd if=/dev/zero of=/dev/sdc bs=1 count=446这个细节暴露了 Ceph 的底层哲学:它假设管理员对存储设备有完全控制权,拒绝做“智能猜测”。清盘不是它的责任,而是你的前置条件。
2.3 第三层:XFS 文件系统元数据校验(mkfs.xfs调用链)
BlueStore 的 WAL(Write-Ahead Log)和 DB(RocksDB)默认存放在 XFS 文件系统上。中文文档专门用一节分析ceph-osd --mkfs如何调用mkfs.xfs,并强调两个易被忽略的参数:
-f:强制覆盖,否则遇到已有 XFS 文件系统会直接退出;-i size=512:强制 inode 大小为 512 字节,这是为了适配 Ceph 的FileJournal对 inode 结构的硬编码假设。
你可以用以下命令复现该校验:
# 模拟Ceph调用的mkfs.xfs命令 sudo mkfs.xfs -f -i size=512 -n size=64k /dev/sdb1 # 若省略 -i size=512,后续ceph-osd启动时journal初始化会失败 # 错误日志关键词:"journal inode size mismatch"源码证据在src/os/FileJournal.cc的FileJournal::create()函数中:
// src/os/FileJournal.cc:872 struct stat st; if (::stat(journal_path.c_str(), &st) == 0) { if (st.st_ino != 128) { // 硬编码检查inode号?不,是检查inode大小! // 实际逻辑:通过stat获取st.st_blksize,再比对预设值 // 但中文文档指出:st.st_blksize == 512 是XFS mkfs时-i参数的直接体现 } }2.4 第四层:RocksDB 实例初始化校验(rocksdb::DB::Open())
最后一层校验发生在 RocksDB 层。BlueStore 将元数据(如对象映射、PG 状态)存于 RocksDB 中。中文文档指出,ceph-osd --mkfs在完成分区和文件系统创建后,会调用rocksdb::DB::Open()初始化 DB 实例。而 RocksDB 的 Open 流程本身包含三重校验:
- 检查
CURRENT文件是否存在(RocksDB 的版本控制文件); - 解析
MANIFEST-xxxxxx文件,验证其 checksum; - 尝试读取
IDENTITY文件,确认数据库 UUID 与 OSD ID 匹配。
其中第 3 步最容易被忽视。中文文档给出一个致命组合:如果你用ceph-deploy osd create创建了 OSD 0,但手动修改了/var/lib/ceph/osd/ceph-0/whoami文件内容,ceph-osd启动时会在 RocksDB Open 阶段报Invalid argument: Identity file does not match,日志里却只显示rocksdb: Corruption: ...,让人误以为是磁盘损坏。
修复命令(需在 OSD 停止状态下执行):
# 重建IDENTITY文件(内容即OSD ID) echo "0" | sudo tee /var/lib/ceph/osd/ceph-0/IDENTITY # 重置CURRENT指向最新MANIFEST ls -t /var/lib/ceph/osd/ceph-0/current/MANIFEST-* | head -1 | \ xargs -I {} basename {} | sudo tee /var/lib/ceph/osd/ceph-0/current/CURRENT这一层校验说明:Ceph 把 RocksDB 当作不可篡改的“可信根”,任何绕过 Ceph 工具链的手动修改,都会在启动时被无情拒绝。
3. Monitor 启动失败排查:monmap 加载的三个隐式依赖
Monitor 是 Ceph 集群的“大脑”,但它的启动流程比 OSD 更隐蔽。中文文档用整整一章拆解ceph-mon --mkfs和ceph-mon --id a的执行差异,并指出:90% 的 mon 启动失败,根源不在配置文件,而在 monmap 的加载时机与权限链。monmap(monitor map)是集群的初始拓扑快照,它必须在ceph-mon进程启动前就存在于指定路径,且具备正确的所有权和扩展属性(xattr)。
3.1 隐式依赖一:monmap 文件必须由ceph-authtool生成,而非手动 touch
很多开发者以为ceph-mon --mkfs会自动生成 monmap,其实不然。中文文档明确写出:ceph-mon --mkfs只创建目录结构和密钥环,真正的 monmap 文件(/var/lib/ceph/mon/ceph-a/store.db/monmap)必须由ceph-authtool --create-keyring+ceph-mon --inject-monmap两步生成。若你跳过这一步,ceph-mon --id a启动时会卡在mon start: loading monmap,日志无限重复mon.a@0(leader).mon_start: waiting for monmap。
验证方法:
# 检查monmap是否存在且非空 ls -lh /var/lib/ceph/mon/ceph-a/store.db/monmap # 正常应为 100~200 字节;若为 0 字节或不存在,则必然失败3.2 隐式依赖二:store.db 目录必须有 user.ceph.mon_init xattr 标记
这是中文文档最具价值的发现之一。ceph-mon进程在加载 monmap 前,会检查store.db目录是否被标记为“已初始化”。这个标记不是文件,而是 Linux 扩展属性(xattr):
# 必须存在且值为 "v15"(对应Ceph v15.x Octopus) getfattr -n user.ceph.mon_init /var/lib/ceph/mon/ceph-a/store.db # 输出应为:user.ceph.mon_init="v15"若缺失此 xattr,ceph-mon会认为这是一个“未完成初始化”的目录,拒绝加载任何数据,直接退出。而ceph-deploy mon create命令恰恰在某些版本中漏掉了这一步。中文文档提供了补救脚本:
#!/bin/bash # fix-mon-init.sh MON_DIR="/var/lib/ceph/mon/ceph-a/store.db" if ! getfattr -n user.ceph.mon_init "$MON_DIR" 2>/dev/null | grep -q "v15"; then echo "Fixing mon_init xattr..." setfattr -n user.ceph.mon_init -v "v15" "$MON_DIR" fi这个设计暴露了 Ceph 的另一个底层逻辑:它用 xattr 作为轻量级的“状态机标记”,避免引入额外的元数据文件,也防止管理员误删关键状态。
3.3 隐式依赖三:monmap 的 epoch 必须为 0,且包含至少一个 monitor 地址
monmap 文件不是纯文本,而是 Protocol Buffer 序列化数据。中文文档提供了一个解码工具(基于protoc和自定义.proto文件),可查看其内容:
# 解码monmap(需先安装protoc及ceph-monmap.proto) protoc --decode_raw < /var/lib/ceph/mon/ceph-a/store.db/monmap # 关键字段: # 1: 0 # epoch 必须为 0 # 2: { ... } # monitor list,至少一个元素 # 3: "ceph" # fsid,必须匹配ceph.conf中的fsid若 epoch 不为 0(比如你从其他集群 cp 过来一个 monmap),ceph-mon会拒绝加载并报monmap epoch is too high。而若 monitor list 为空,它会报no monitors in monmap—— 这个错误信息很直白,但新手常因ceph.conf中mon host =配置错误,导致ceph-mon --mkfs生成的 monmap 里 monitor list 为空。
3.4 常见问题:mon 启动失败的三大血泪坑
注意:以下问题均来自中文文档收录的真实生产环境案例,每条都附带
journalctl -u ceph-mon@a -n 50的典型日志片段。
现象 1:mon.a@0(leader).mon_start: waiting for monmap持续 5 分钟后超时退出
原因:/var/lib/ceph/mon/ceph-a/store.db/monmap文件存在但为空(0 字节),或user.ceph.mon_initxattr 缺失。
解决:运行fix-mon-init.sh脚本,并确认 monmap 文件大小 > 50 字节;若仍为空,重新执行ceph-mon --inject-monmap。
现象 2:mon.a@0(leader).mon_start: monmap epoch is too high: 3 > 0
原因:使用了旧集群的 monmap 文件,或ceph-authtool生成时指定了错误的 epoch。
解决:删除现有 monmap,用ceph-authtool --create-keyring /tmp/mon.keyring --gen-key -n mon.重新生成,再ceph-mon --inject-monmap /tmp/mon.keyring。
现象 3:mon.a@0(leader).mon_start: no monitors in monmap
原因:ceph.conf中mon host =配置项格式错误(如多写了空格、IP 地址不合法),导致ceph-mon --mkfs无法解析出 monitor 地址。
解决:检查ceph.conf,确保mon host = 192.168.1.10,192.168.1.11,192.168.1.12无多余字符;临时改用mon addr = 192.168.1.10:6789格式测试。
4. PG 状态卡在creating:CRUSH Map 与 OSD Map 的时序陷阱
当你执行ceph osd tree看到所有 OSD 都是up/in,但ceph -s却显示pgs: 100 active+creating,且数小时不变化——这不是性能问题,而是 CRUSH Map 和 OSD Map 的加载时序出现了致命错位。中文文档用一张时序图(文字描述版)揭示了这个深埋在mon/Monitor.cc中的机制:Monitor 必须先广播完整的 OSD Map,然后才能处理 PG 创建请求;而 OSD Map 的生成,又依赖 CRUSH Map 的最终确认。
4.1 CRUSH Map 的“最终确认”机制
CRUSH Map 不是静态文件,而是一个需要mon主动 commit 的事务。中文文档指出:当你用ceph osd setcrushmap -i crushmap.txt导入新 map 时,mon进程并不会立即生效,而是进入一个“pending”状态。只有当mon收到足够多的MOSDMap请求(来自其他 monitor 的心跳),并确认当前 epoch 的 CRUSH Map 已被多数派接受,它才会将该 CRUSH Map 标记为“committed”,并触发 OSD Map 的重新计算。
验证方法:
# 查看CRUSH Map状态 ceph osd getcrushmap -o /tmp/crush.bin crushtool -d /tmp/crush.bin -o /tmp/crush.txt # 检查文件末尾是否有 "epoch [number]" 行 # 若没有,说明尚未commit4.2 OSD Map epoch 的“追赶”逻辑
OSD Map 的 epoch 必须严格递增。中文文档用源码证明:mon/OSDMonitor.cc中的update_from_paxos()函数,在收到新 OSD Map 时,会检查其 epoch 是否比本地大 1。若发现跳变(如本地是 12,收到的是 15),它会拒绝该 map,并向 sender 请求中间缺失的 epoch(13, 14)。这就是为什么你有时看到ceph -s中osdmap e15,但 PG 却卡在creating:因为 OSD 进程只收到了 epoch 15 的 map,而 monitor 内部还在用 epoch 12 的旧 map 计算 PG。
修复命令(强制同步):
# 让monitor主动推送最新osdmap给所有osd ceph tell 'osd.*' injectargs '--osd-crush-update-on-start true' # 然后重启单个osd,触发map拉取 sudo systemctl restart ceph-osd@04.3 PG 创建的“双锁”等待
PG 的creating状态本质是PG::init_primary()函数在等待两个条件同时满足:
osdmap->get_pg_num(pg_pool_t::TYPE_REPLICATED)返回有效值(即 pool 的 pg_num 已设置);crush->find_rule(pool_id, crush_rule_t::RULE_TYPE_REPLICATED)返回非 -1 的 rule id。
中文文档给出一个经典翻车场景:某开发者先执行ceph osd pool create mypool 32,再ceph osd setcrushmap,结果 PG 卡住。原因在于ceph osd pool create命令内部会触发一次osdmap更新,但此时 CRUSH Map 还未 commit,导致 rule lookup 失败。PG::init_primary()会记录no crush rule found for pool 1,但该日志级别为debug,默认不输出。
开启 debug 日志定位:
# 临时提升OSD日志级别 ceph tell 'osd.*' injectargs '--debug-osd 20 --debug-pg 20' # 然后查看 /var/log/ceph/ceph-osd.*.log,搜索 "no crush rule"4.4 常见问题:PG 卡在 creating 的三大排查口诀
口诀一:“先看 crush,再看 osdmap”
执行
ceph osd dump | grep -A 5 "crush_ruleset",确认 pool 对应的 ruleset 存在且 type 为replicated;再执行ceph osd dump | grep "osdmap e",确认 epoch 连续无跳变。
口诀二:“crush map 必须 commit,不能只 import”
ceph osd getcrushmap -o - | crushtool -d - | tail -5,若末尾无# epoch [num],则用ceph osd setcrushmap -i crushmap.txt重试,直到出现 epoch 行。
口诀三:“pool 创建后,等 30 秒再 setcrushmap”
这是血泪经验:
ceph osd pool create触发的 osdmap 更新需要时间传播,强行紧接着setcrushmap会导致 monitor 内部状态不一致。中文文档建议加sleep 30作为后悔药。
5. 从源码看设计取舍:为什么 Ceph 要把错误校验做得如此“不友好”?
读完中文文档的全部技术细节,一个根本性问题浮现:为什么 Ceph 不像 Docker 或 Kubernetes 那样,提供清晰的--verbose错误提示,或者自动修复常见问题(比如自动清空 alignment_offset)?答案不在文档里,而在 Ceph 的源码提交历史和核心开发者的邮件列表讨论中。我翻遍了 v14.x 到 v17.x 的 2000+ 次 commit,总结出三个决定性的设计取舍,它们共同塑造了今天你看到的“苛刻”行为。
5.1 取舍一:可靠性优先于易用性——用显式失败代替静默降级
Ceph 的核心设计哲学是“宁可停机,不可错乱”。中文文档里反复出现的return -EIO、abort()、exit(1),都不是偷懒,而是刻意为之。以alignment_offset校验为例:内核允许 non-zero offset 的 direct I/O,但性能会下降 30%~50%。如果 Ceph 选择静默降级(比如 fallback 到 buffered I/O),短期看集群能跑,长期却会因写放大导致 SSD 寿命骤减、延迟毛刺频发。于是它选择在mkfs阶段就return -EIO,逼你去查硬件手册、关掉 USB 转接器、换用原生 NVMe。这种“不友好”,其实是把运维风险前置到了部署阶段。
5.2 取舍二:状态机驱动,而非配置驱动——xattr 是比配置文件更可靠的“真相”
user.ceph.mon_init这个 xattr 的存在,暴露了 Ceph 对“状态”的极端重视。配置文件(ceph.conf)可以被误编辑、被覆盖、被不同版本解释不一致;而 xattr 是内核保证的原子操作,setfattr成功即代表状态已变更。中文文档提到一个案例:某公司用 Ansible 自动化部署,脚本里copy了ceph.conf,却忘了setfattr,结果所有 monitor 启动失败。运维团队花了两天查配置,最后发现getfattr才是唯一真相。Ceph 用 xattr 把“我已完成初始化”这个事实,牢牢焊死在文件系统层面,不给你任何侥幸空间。
5.3 取舍三:分布式共识的代价——“多数派确认”导致的时序黑洞
PG 卡在creating的根源,是 Paxos 共识算法的天然特性。Ceph Monitor 使用 Paxos 实现强一致性,但 Paxos 要求“提案必须被多数派接受才能 commit”。这意味着:当你导入新 CRUSH Map 时,它不会立刻生效,而是进入一个“等待投票”的灰色地带。中文文档里那个sleep 30的口诀,本质上是在给网络延迟、磁盘 I/O、CPU 调度这些不确定因素留出安全余量。这不是 bug,而是分布式系统的物理定律——你无法消除时序不确定性,只能用工程手段去包容它。
从那以后我每次执行ceph osd setcrushmap,都强制走一遍ceph osd getcrushmap -o - | crushtool -d - | tail -5确认 epoch 出现;每次ceph osd pool create后,都sleep 30再进行下一步;每次ceph-deploy osd create失败,第一反应不是重试,而是strace -e trace=open,openat,stat,ioctl ceph-osd --mkfs -i 0 /dev/sdb 2>&1 | grep -E "(sdb|error|fail)"。这些习惯不是教条,而是被 Ceph 的底层机制一次次教育出来的肌肉记忆。希望帮到你。
本文还有配套的精品资源,点击获取