Ceph 这个坑,一旦踩进去就很难爬出来,但踩明白之后,你会发现它其实是一套设计相当优雅的分布式存储系统。上一篇我聊了 Ceph 的基础架构和核心组件,把 Monitor、OSD、PG 这些概念过了个遍。这一篇我打算换个角度,直接奔着实操去:把部署方式、OSD 状态异常排查,以及几个平时最容易翻车的细节一次性讲清楚。因为很多朋友私信我说,理论看了一堆,真要自己搭一套环境或者处理一个故障的时候,还是两眼一抹黑。
所以,“简单介绍 Ceph(二)”主要解决三件事:第一,用最稳的方式把一个集群从零搭起来;第二,讲清楚ceph osd tree里那一堆状态到底是什么意思,特别是 down 了该怎么处理;第三,分享一些部署和运维过程中,文档里不会明说、但实测下来非常关键的避坑经验。
这篇文章适合谁看?已经跑过 Ceph 相关服务、或者至少了解过它的基本概念,现在正准备自己动手搭建和运维一套 Ceph 集群的工程师。当然,如果你只是刚接触分布式存储,想搞明白 Ceph 到底怎么玩的,这篇也会是一个很好的切入点。我会尽量把所有步骤和原理串起来讲,保证你不只是照着敲命令,而是真正理解每一步在做什么、为什么这么做。
1. 理解 Ceph 的核心工作流程
1.1 一条 IO 路径的前世今生
很多人在看了好几遍 Ceph 架构图之后,依然搞不清楚一份数据到底是怎么写进集群的。这里我用最简单的语言把这条链路捋一遍。
当你通过块设备(RBD)、对象存储(RGW)或者文件系统(CephFS)写入一份数据时,这个请求会先到达客户端。注意,Ceph 的客户端并不是像传统存储那样把数据发给一个集中式的“头节点”,而是直接和集群里的 OSD 打交道。这中间起到“翻译”作用的,就是 CRUSH 算法。
CRUSH 算法的本质,是一个以集群拓扑结构和数据副本策略为输入的确定性哈希函数。你给定一个对象名和一个 PG ID,它就能计算出这个 PG 应该落在哪几个 OSD 上。因为计算过程不依赖任何中心化的查询表,所以客户端可以直接算出数据位置,然后发起写入。这就是 Ceph 高并发、高扩展性的底层基石。
一条写入请求的完整路径是这样的:客户端将要写入的数据切成一个个对象(通常每个对象大小设定为 4MB),每个对象再通过哈希映射到一个 PG(Placement Group,放置组),然后由 CRUSH 算法找到这个 PG 所在的 OSD 组。数据会同时写入这个 PG 对应的多个 OSD(副本数通常为 2 或 3),只有当所有副本都写入成功,客户端才会收到写入完成的确认。
这个流程看起来简单,但里面有一个非常关键的细节:PG 是 Ceph 数据管理的核心单元,它不是物理存在的实体,而是一个逻辑上的数据容器。每个 PG 内部包含了一组对象,OSD 则是真正负责存储这些对象数据的进程。理解了这一个关系,后续理解数据平衡、故障修复都会顺畅很多。
1.2 PG 数量规划与性能的关系
我在实际操作中发现,很多新集群运行一段时间后出现数据分布不均的问题,根源都在于 PG 数量规划的时候拍脑袋了。PG 的数量不是随便定的,它直接影响集群的性能和稳定性。
每个 PG 都会占用一定的内存和 CPU 资源,且 PG 的迁移和回填(Backfill)过程会占用网络和磁盘 IO。如果 PG 数量太少,每个 PG 的数据量就会非常大,一旦某个 OSD 故障,恢复的时间会非常长;如果 PG 数量太多,又会导致资源浪费,增加 Monitor 的负担。
业内比较公认的估算公式是:PG 总数约等于 OSD 总数乘以 100,然后再除以副本数。比如你有 10 个 OSD,副本数为 3,那么 PG 总数建议为 10×100/3≈333。实际的取值还要考虑集群的规模上限,取最接近且略大的 2 的幂次方(例如 512)。这个配置看起来不起眼,但它是整个集群性能和稳定性的基石,建议在创建存储池之前就要规划好。因为存储池创建后,PG 数量虽然可以调整,但调整过程会触发大规模的数据迁移,操作不当很容易影响线上业务。
2. 部署方式选型与实操对比
2.1 ceph-deploy 与 cephadm 的差距
目前网上搜到的很多老教程还在使用ceph-deploy来部署集群,这个工具在 Ceph 的 Nautilus 版本之后就已经被官方移除了。如果说你搜索 Ceph 部署相关的内容依然看到大量ceph-deploy的使用教程,那只能说明这些内容已经严重过时。
ceph-deploy的核心思路是通过 SSH 远程执行一系列命令来安装和配置各节点的 Ceph 组件。它设计上有一个很大的问题——它需要有一个独立的部署节点,用来远程连接其他所有节点。这种方式在小规模测试环境里看起来很方便,但在生产环境里,它没有内置的高可用机制,部署节点一旦出现问题,整个集群的管理就非常被动。
而 Ceph 现在的官方推荐工具是cephadm。它采用容器化的方式部署和管理 Ceph 组件,所有组件都运行在容器里,cephadm本身只负责容器的编排和生命周期管理。它不需要单独的管理节点,Monitor、Manager、OSD 这些组件都以 systemd 服务的形式在所有节点上运行,任何一个节点宕掉了,其他节点可以继续管理整个集群。
我在实际对比中可以告诉你,用cephadm部署一个全新的集群,从准备节点到集群状态变为 HEALTH_OK,熟练的情况下二十分钟就能搞定。而如果用ceph-deploy,光是解决各个节点之间的依赖关系和版本兼容问题就可能花掉半天时间。
2.2 快速搭建一套最小集群的完整过程
既然说到实操,我就以cephadm为例,把搭建一套三节点最小集群的完整流程记录下来。这套流程我实测过很多次,基本可以稳定复现。
准备三台机器,我建议操作系统使用 Ubuntu 22.04 或者 CentOS Stream 9。每台机器需要配置好静态 IP 和主机名,确保/etc/hosts里三台机器的 IP 和主机名映射关系都正确。所有节点的时间必须同步,否则 Monitor 之间会出现时钟偏移告警,这个在分布式系统里是非常致命的。
所有节点做完基础准备之后,在第一个节点上安装cephadm:
# 下载官方提供的安装脚本 curl --silent --remote-name --location https://github.com/ceph/ceph/raw/quincy/src/cephadm/cephadm chmod +x cephadm sudo ./cephadm add-repo --release quincy sudo ./cephadm install这里要说明一个细节:cephadm add-repo这个命令会配置好 Ceph 的官方软件源,然后cephadm install会安装 cephadm 命令本身以及 ceph-common 等依赖工具。安装完成之后可以验证一下版本:
cephadm version然后在第一个节点上引导集群,这里我用--mon-ip参数指定本机的 IP 地址:
sudo cephadm bootstrap --mon-ip 192.168.1.10bootstrap 过程会自动生成一个最小的 Monitor 和 Manager,并创建一个/etc/ceph目录存放集群的配置文件和 keyring。如果网络环境没有特殊限制,整个过程一般会在两三分钟内完成。结束后终端会输出一个 dashboard 的访问地址和管理员密码,默认端口是 8443,这个要记好。
接下来把另外两个节点加入集群:
# 在第一个节点上执行,将第二个节点加入集群 ceph orch host add node2 192.168.1.11 # 将第三个节点加入集群 ceph orch host add node3 192.168.1.12ceph orch host add执行完之后,Ceph 会自动给这两个节点分发 SSH 密钥并安装必要的软件包。这时候可以用:
ceph orch host ls查看节点的加入状态。一切正常的话,两个节点都会出现在列表里。
最后一步是添加 OSD。这里我不建议手动指定磁盘去创建 OSD,而是让 Ceph 自动发现并准备所有可用的磁盘:
ceph orch apply osd --all-available-devices执行了这个命令之后,Ceph 会把所有节点上可用的、没有分区的磁盘自动初始化为 OSD。整个过程大概需要几十秒,然后你就能看到集群的健康状态了:
ceph -s看到HEALTH_OK的时候,一套最小集群就算搭建完成了。如果是纯测试环境,到这一步就可以开始创建存储池使用 RBD 块设备了。
3. 深入拆解 ceph osd tree 的状态语义
3.1 OSD 状态的真实含义
热度搜索词里出现了ceph osd tree down,说明这是大家运维时最高频遇到的问题之一。ceph osd tree是一个用于查看集群内 OSD 状态的核心命令,它输出一个树形结构的视图,展示了主机、OSD 以及它们之间的层级关系。
很多初学者会把down理解为“这个 OSD 坏了”,但这个理解不够准确。down只表示 OSD 进程当前不在运行状态,并不代表这块磁盘已经物理损坏。有一个很经典的案例:某个 OSD 因为内存不足被系统 OOM Killer 杀掉了,这时候 OS 进程就会显示为down,但磁盘本身是完全没有问题的。
和down经常一起出现的是out状态。out表示该 OSD 已经不在集群的服务范围之内,即不再参与数据的读写。Ceph 设计了一套独立的机制来控制 OSD 的状态迁移:
- 当 OSD 处于
up且in状态时,它会正常参与数据读写。 - 当 OSD 处于
up且out状态时,进程还在,但集群会把它上面的 PG 迁移到其他 OSD 上。 - 当 OSD 处于
down且in状态时,进程挂了,但集群仍然认为它应该提供服务,此时会出现 PG 降级(degraded)。 - 当 OSD 处于
down且out状态时,进程挂了,且集群已经把它从服务列表里移除,PG 会在其他 OSD 上重新恢复完整副本。
在这四种状态里,最危险的其实是down且in。它意味着集群中已经有一部分 PG 处于降级状态,数据副本数不足,如果这个时候再有其他 OSD 故障,就有可能出现数据丢失的风险。
3.2 为什么 OSD 会夯住(hang)和心跳超时
Ceph 集群内部有一套心跳机制来监控各个 OSD 和 Monitor 之间的连通性。每个 OSD 会周期性地向 Monitor 上报自己的状态,这个周期默认为 5 秒。如果 Monitor 在设定的超时时间内没有收到某个 OSD 的心跳,这个 OSD 就会被标记为down。
这个“没收到心跳”的原因其实有很多种。我在实际排障中遇到过最多的情况是磁盘控制器卡死导致 IO 阻塞,OSD 进程被卡在系统调里无法响应心跳。这种问题往往比磁盘物理损坏更隐蔽——你用iostat看到磁盘 util 率接近 100%,但 dmesg 里并没有明显的 IO error 日志,OSD 进程的状态也一直是 D(不可中断睡眠)状态。
另一个常见原因是网络抖动。如果 OSD 所在节点和 Monitor 节点之间的网络有丢包或者延迟过高,心跳超时就会发生。这种问题比较棘手的地方在于,网络恢复了之后 OSD 重新up,但它上的 PG 已经开始被迁移了,会导致大量不必要的集群内流量。
处理 OSD 状态问题的时候,我的习惯是第一时间看监控。这个不只是看ceph -s输出,我会先看该节点的负载、磁盘 IO、网络流量,然后再看 Ceph 自身的日志。cephadm部署的集群,OSD 的日志可以通过cephadm logs来查看:
cephadm logs -f --name osd.3另外,生产环境里我们会把 OSD 的心跳超时时间和 PG 恢复优先级调到一个相对保守的值,避免一个 OSD 短暂抖动就引发大面积的数据迁移。相关的参数是:
osd_heartbeat_grace = 20 mon_osd_report_timeout = 300注意这些参数最好在部署的时候就通过配置文件或者ceph config set设置好,因为运行中调整会导致 Monitor 重新评估所有 OSD 状态,有一定风险。
3.3 实战:一个 OSD down 之后的标准处理流程
现在假设我们通过ceph -s看到了告警,用ceph osd tree发现 OSD 5 的状态是down,这时候我建议按照下面的步骤来处理。
先确认这个 OSD 是在哪台机器上、对应哪块盘:
ceph osd find 5这个命令会返回该 OSD 所在的节点名、以及它的设备路径等信息。拿到信息之后,登录到对应节点,先看这个 OSD 的进程状态:
systemctl status ceph-osd@5如果进程不存在或者状态不对,就尝试手动启动:
systemctl start ceph-osd@5启动之后观察一分钟,再用ceph osd tree查看状态是否恢复为up。如果进程能正常启动,且 OSD 变为up并且集群状态从HEALTH_WARN转好,那就说明这次故障大概率是偶发性的。此时你仍然需要执行:
ceph health detail看一下集群是否还有剩余的告警信息,并检查是否有 PG 需要重新恢复。
如果启动不成功,那就需要进一步查看系统日志:
journalctl -u ceph-osd@5 -n 200这里最常见的问题就是磁盘 IO 错误或者文件系统损坏。如果日志里出现bluestore read failed或者Corruption之类的字样,那大概率是磁盘有问题了。这种时候不要强行反复重启 OSD,而是应该先考虑换盘操作。
更换 OSD 盘的流程是:先把 OSD 标记为out,让集群把数据迁移到其他 OSD 上,然后停掉 OSD 进程并从集群里移除,最后更换磁盘并重新添加 OSD。相关命令如下:
ceph osd out 5 ceph osd crush remove osd.5 ceph osd rm 5 ceph auth del osd.5这是一套标准的 OSD 移除操作。移除之后再用ceph orch apply osd --all-available-devices让 Ceph 自动加载新盘。整个过程会伴随一定量的数据迁移,期间集群会处于HEALTH_WARN状态,这是正常现象,不用紧张,等 PG 状态重新变为active+clean就说明集群恢复了。
4. 部署期间的常见坑与排查技巧
4.1 时钟同步问题的隐蔽性
分布式系统的“时间一致性”是一个容易被忽略但又极其重要的点。Ceph 的 Monitor 集群使用 Paxos 协议来保证一致性,这个协议对时钟偏差非常敏感。如果各个 Monitor 节点之间的时间差异过大,轻则出现clock skew告警,重则导致 Monitor 互相认为对方失联,出现选主动荡。
我的经验是,在所有 Ceph 节点上统一配置chrony,并指定同一个上游时间服务器。配置文件/etc/chrony/chrony.conf里只需要几行:
server ntp.aliyun.com iburst allow 192.168.1.0/24 local stratum 10配完之后重启服务:
systemctl restart chronyd systemctl enable chronyd然后用chronyc sources -v验证一下同步情况。注意这里的local stratum是为了让集群内节点在网络隔离情况下也能维持时间同步,生产环境如果所有节点都能访问外网,可以不设置这个参数。
4.2 主机名和解析配置的连锁反应
我看过太多案例,都是因为/etc/hosts配置不规范,导致 Ceph 集群出现各种诡异问题。特别是使用cephadm部署时,它会把节点的主机名写入集群的 CRUSH map 里。如果你的主机名中包含了下划线或者类似的非法字符,某些组件在解析时就会报错。
更常见的坑是,/etc/hosts里把主机名解析到了127.0.0.1,或者主机名和实际 IP 对应关系混乱。这会导致一个节点上多个 OSD 之间通过主机名互相访问时,连接到了错误的地址,甚至出现 Monitor 无法互通的严重问题。
最稳妥的方式是:所有节点的/etc/hosts保持一致的格式,每台机器都有且只有一个 FQDN 格式的主机名,并且主机名不要包含大写字母、下划线等特殊字符。我在部署前都会先执行一个简单的检查:
hostnamectl set-hostname node1 grep "$(hostname)" /etc/hosts确保这两条命令输出的主机名一致,且 IP 解析正确,再继续后面的流程。
4.3 内核参数与文件系统预设优化
Ceph 底层对磁盘和网络的性能利用非常激进,如果不对操作系统的内核参数做适当调整,很容易出现 IO 延迟飙升或者网络吞吐不稳定的问题。
比较常用的几个参数调整如下。网络方面,增大 socket 缓冲区的大小:
sysctl -w net.core.rmem_max=26214400 sysctl -w net.core.wmem_max=26214400 sysctl -w net.core.rmem_default=26214400 sysctl -w net.core.wmem_default=26214400磁盘方面,调整 IO 调度器为 noop 或 none(视内核版本而定),减少不必要的 IO 重排开销:
echo none > /sys/block/sda/queue/scheduler在 Ceph 使用的 OSD 数据盘上,建议关闭 atime 更新,减少不必要的写入:
mount -o noatime,nodiratime /dev/sdb1 /var/lib/ceph/osd/ceph-0使用cephadm部署时,OSD 的数据盘一般是 LVM 管理的,所以这些参数需要写入到 systemd 单元的配置里,才能保证重启后不丢失。我通常在部署前就把这些参数固化到/etc/sysctl.d/和 systemd 的 mount 配置中,这样后续扩容的新节点也能保持一致。
5. 踩坑实录与核心经验汇总
5.1 一个误操作引发的副本数失衡教训
有一次我在一个测试环境里执行了ceph osd pool set mypool size 1,本意是想临时测试写入性能,却忘了把min_size也同步调整。结果在只有一个副本的情况下,某个 OSD 稍微抖动了一下,整个 pool 直接进入incomplete状态,所有 IO 都被阻塞。
这个教训非常深刻:Ceph 的min_size参数决定了集群在最少多少个副本可用时才会继续对外提供服务。如果你的副本数是 3,而min_size是 2,那即使坏掉一个 OSD,集群仍然可以正常读写。但如果你图省事把副本数改成 1,又忘记设置min_size为 1,那只要有任何一个副本所在的 OSD 出问题,该 pool 的 IO 就全部卡死。
生产环境中我强烈建议保持副本数至少为 3,min_size至少为 2。这个配置能够在可靠性和可用性之间取得一个很好的平衡。如果你确实因为成本原因只能做两副本,那也要明确知道:两副本在任一 OSD 故障时,如果min_size为 1,有数据丢失的风险;如果min_size为 2,则会直接停止对外服务。这是个两难选择,必须在部署前想清楚。
5.2 关于 ceph osd tree 的快速读图技巧
ceph osd tree的输出在 OSD 数量多的时候会显得比较长,新手看得一头雾水。这里面有一个非常实用的小技巧:重点关注UP和IN两列的状态组合。
ceph osd tree输出内容通常是这样的结构:
ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 0.09799 root default -3 0.04899 host node1 0 ssd 0.02499 osd.0 up 1.00000 1.00000 1 ssd 0.02499 osd.1 up 1.00000 1.00000 -5 0.04899 host node2 2 ssd 0.02499 osd.2 up 1.00000 1.00000 3 ssd 0.02499 osd.3 down 0.00000 1.00000如果某个 OSD 的STATUS列显示为down,紧接着它的REWEIGHT也变为 0,说明这个 OSD 已经被集群排除在服务列表之外,此时数据已经或者正在被迁移到其他 OSD 上。如果你看到某个 OSD 的DOWN但REWEIGHT不为 0,这就意味着这个 OSD 是意外宕掉的,需要尽快介入处理。通过这两列的组合,可以快速判断故障的严重程度。
5.3 监控与告警体系的搭建建议
最后,我想强烈建议所有准备把 Ceph 用于生产环境的朋友,尽早搭好监控和告警体系。ceph -s只是被动地告诉你当前集群的状态,但生产环境里我们需要的是“在故障发生前”就能发现风险。
官方开源的node-exporter和ceph-exporter配合 Prometheus 和 Grafana 是比较成熟的方案。采集到的指标里,我重点关注三类:OSD 的读写延迟、OSD 的可用空间、PG 的状态分布。延迟突增往往意味着磁盘性能瓶颈;可用空间低于 15% 就必须马上扩容;PG 状态出现非clean的异常值,就要立即排查。
另外,不要忽略 Ceph 自带的 dashboard 功能。用cephadm部署的集群默认就带 dashboard,可以通过ceph dashboard set-grafana-api-url把 Prometheus 的数据源接进去,在网页上就能看到集群本身的性能指标和健康状态。这对日常巡检来说非常方便,省去了很多手动敲命令的时间。
我在实际使用中还有一个习惯:每次遇到任何集群异常,都会顺手把ceph -s、ceph osd tree、ceph pg stat的输出存到本地的一个日志文件里,标注好时间点和当时的操作。时间久了,这些记录就是最宝贵的问题排查资料,尤其是当你需要回看某个改动对集群产生的影响时,能帮你省下大量用于回忆和猜测的时间。