分类 Ceph 下的文章

OSD down 是 Ceph 运维最常见的事件。新手最容易犯的错是看到 down 就立刻 ceph osd out,结果触发数据迁移,等磁盘其实自己能恢复,白白搬一遍数据。本文是我团队内部用的决策树。

一图先行(文字版)

OSD down
  ├─ down 时长 < 10 分钟?
  │   └─ 是 → 等 mon_osd_down_out_interval 自动处理,先别动
  │
  ├─ 节点是否在线?(ping / ssh)
  │   ├─ 节点离线 → 是计划维护吗?
  │   │     ├─ 是 → noout + 维护,完事 unset noout
  │   │     └─ 否 → 排查节点
  │   └─ 节点在线 → 进入磁盘排查
  │
  ├─ 磁盘 SMART 是否正常?
  │   ├─ 异常 → destroy & 换盘
  │   └─ 正常 → 看 OSD 日志,可能是 OOM / 段错误 / 文件系统问题

几个关键参数搞清楚

  • mon_osd_down_out_interval:OSD down 多久自动 out(默认 600s)
  • noout:禁止 OSD 自动 out(维护必用)
  • nobackfill / norecover:禁止回填/恢复
  • noscrub / nodeep-scrub:禁止 scrub

维护场景三连:

ceph osd set noout
ceph osd set nobackfill
ceph osd set norecover
# 维护...
ceph osd unset norecover
ceph osd unset nobackfill
ceph osd unset noout

out vs destroy vs purge

操作 作用 数据迁移 OSD ID 保留
ceph osd out X 标记为不参与数据分布 触发
ceph osd destroy X 标记销毁,清除认证密钥 不触发
ceph osd purge X 彻底删除 不触发

实战决策

  • 短暂故障要恢复 → 啥都不做,等它自己 up
  • 换盘但想复用 OSD ID(推荐,少一次 CRUSH 抖动)→ destroy → 换盘 → ceph-volume lvm create --osd-id X
  • 彻底下线一块盘 → out → 等回填 → purge

一个真实踩坑

一台机器 4 块 OSD 全 down,我同事下意识 ceph osd out 了 4 个 ID。结果触发大量数据迁移,集群 IO 抖动 1 小时。其实那台机器只是网卡 down 了,5 分钟修好。

正确做法:发现整节点掉了,先 ceph osd set noout,去现场查节点,节点恢复后 OSD 自己 up 就行了,零迁移。

让运维少出事的几个习惯

  1. 节点维护前永远 noout:写进 SOP,没商量
  2. 批量换盘别同时换:一次只换一块,等回填完了再下一块
  3. 集群配 mon_max_pg_per_osd_hard_limit:避免大规模迁移把单个 OSD 撑爆
  4. 死磁盘 destroy 而不是 purge:保留 ID,重建后 CRUSH 拓扑不变,少一轮全盘扫描

教训一句话:OSD down 先别 out,10 分钟内的故障往往是网络或者 OSD 进程的事,不是磁盘的事。

现象

一次 OSD 扩容后,集群有几十个 PG 长期处于 active+remapped+backfilling 状态,ceph -s 显示 recovery 速度只有几 MB/s,按这个速度回填几天都跑不完。本文按我自己的排查路径走一遍。

第一步:先确认是不是"真的慢"

ceph -s
ceph osd pool ls detail
ceph pg dump | awk '/active\+remapped\+backfilling/ {print $1, $15}' | head

重点看几件事:

  • 回填速度ceph -s 里的 recovery: X MiB/s 是否远低于磁盘能力
  • PG 总数 vs OSD 数:单个 OSD 上的 PG 数是否超过 200(默认 mon_max_pg_per_osd=250),上限会卡 PG 创建
  • 是否触发 backfillfull:单个 OSD 接近 90% 会停掉回填

第二步:看是不是被限流卡住了

Ceph 默认对 recovery 限流非常保守,生产经常调:

ceph config get osd osd_max_backfills
ceph config get osd osd_recovery_max_active
ceph config get osd osd_recovery_sleep_hdd

典型现象:HDD 集群 osd_recovery_sleep_hdd 默认是 0.1,意思是每个 IO 之间 sleep 100ms。临时拉高(恢复完记得改回去!):

ceph config set osd osd_max_backfills 4
ceph config set osd osd_recovery_max_active 8
ceph config set osd osd_recovery_sleep_hdd 0

NVMe/SSD 集群可以更激进,HDD 别开太高,会拖业务 IO。

第三步:是不是某几个 OSD 在拖后腿

ceph osd perf
ceph daemon osd.X dump_historic_slow_ops

一两块盘 latency 飙到几百 ms,要么 SMART 看坏块、要么这块就是慢盘,临时 ceph osd out X 把它隔离掉,让回填走完再处理。

第四步:PG 数量配比有没有问题

ceph osd df tree

如果某些 OSD 上 PG 数明显高于平均(差 30% 以上),说明 CRUSH 分布不均,可以:

  • 启用 upmap 平衡器:ceph balancer mode upmap && ceph balancer on
  • 或手动 reweight:ceph osd reweight-by-utilization

真正的坑:mClock 调度器

Reef 之后默认调度器从 WPQ 换成了 mClock,它会按 IOPS 配额自动限速。如果你发现怎么调上面的参数 recovery 都不动,多半是 mClock 在卡:

ceph config get osd osd_op_queue
# 临时切回 WPQ(需重启 OSD):
ceph config set osd osd_op_queue wpq

或者用 mClock 的 profile:

ceph config set osd osd_mclock_profile high_recovery_ops

收尾

回填完成后务必把限流改回默认,否则下次掉盘时业务 IO 会被 recovery 抢光:

ceph config rm osd osd_max_backfills
ceph config rm osd osd_recovery_max_active
ceph config rm osd osd_recovery_sleep_hdd

教训一句话:Ceph 的 recovery 速度=慢盘短板 + 限流参数 + 调度器策略,三个维度都得看。

副本(replication)和纠删码(EC)是 Ceph 两种数据保护机制。新人最纠结的就是怎么选。本文给一个决策框架。

一图概括

维度 3 副本 EC 4+2 EC 8+3
空间利用率 33% 67% 73%
容忍失败 OSD 2 个 2 个 3 个
写性能
读性能
修复速度
CPU 开销
小对象友好

选副本的场景

  • 块存储 RBD(VM 盘、数据库):高 IOPS、小 IO,必须副本
  • CephFS 热数据池:小文件多,EC 性能跌得厉害
  • 小集群(OSD < 30):EC 性能开销占比太大

选 EC 的场景

  • 对象存储 RGW 冷桶:大对象、读为主
  • 备份/归档:写入一次、几乎不读
  • 大集群存视频/影像:对象大且数量少

EC 怎么选 K+M

  • K+M 不能超过 host 数(默认 failure domain=host)
  • K 越大空间利用率越高,但修复越慢
  • M 决定能挂多少 OSD

我的默认:

  • 中等集群(30-50 OSD):EC 4+2,平衡空间和性能
  • 大集群(>100 OSD):EC 8+3,最佳空间利用

一个新手陷阱:混合 pool

很多人想"热数据副本池、冷数据 EC 池,自动迁移"——这叫 cache tier。Reef 之后官方已经 deprecated cache tier,不再推荐用。

替代方案:

  • RGW 桶级 storage class,新对象写入 EC 池
  • 应用层冷热分离(用户访问频次驱动)

实际数据规划公式

裸容量 = 业务数据 ÷ 利用率 ÷ 平均使用率(0.7)

例:要存 100TB 业务数据,用 EC 4+2
裸容量 = 100 / 0.67 / 0.7 ≈ 213 TB

那 0.7 哪来的?Ceph 推荐 OSD 使用率 ≤70%,超过会触发 backfillfull 限制写入。

极端但有用的组合

临时数据池(如 K8s emptyDir 持久化):

ceph osd pool create tmp 32 32 replicated
ceph osd pool set tmp size 2
ceph osd pool set tmp min_size 1

size=2/min_size=1 在生产是反模式(容易丢数据),但临时数据可接受,省一倍空间。

教训:选 EC 前先用 rados bench 在你的硬件上跑一遍——纸面性能和实际差距经常很大。

MON 是 Ceph 的"大脑",3 个 MON 挂 2 个集群就死。本文记录我处理过的几次恢复,按"死多少 MON"分支。

一个挂了:稳

集群可用,只是冗余降级。

ceph -s   # 看 quorum 信息
systemctl restart ceph-mon@<id>  # 老版本
# 或
ceph orch daemon restart mon.<id>  # cephadm

不行就重建:

# 1. 找一个活的 MON 导出 monmap
ceph mon getmap -o /tmp/monmap
# 2. 停掉坏的,删它的 store
systemctl stop ceph-mon@<id>
rm -rf /var/lib/ceph/mon/ceph-<id>/*
# 3. 用 monmap 重建
ceph-mon -i <id> --mkfs --monmap /tmp/monmap --keyring /etc/ceph/ceph.mon.keyring
systemctl start ceph-mon@<id>

两个挂了:危

集群只读甚至完全不可用。怎么救:

先想:是机器问题还是 mon 数据问题?

  • 机器问题(网络、磁盘)→ 修复机器,mon 自己会回来
  • mon 数据损坏 → 进入"单 MON 恢复模式"

单 MON 恢复模式

让仅剩的那个 MON 单独组成 quorum:

# 在活着的 MON 节点上
systemctl stop ceph-mon@<id>
monmaptool /tmp/monmap --rm <挂掉的mon1> --rm <挂掉的mon2>
ceph-mon -i <id> --inject-monmap /tmp/monmap
systemctl start ceph-mon@<id>

这样集群恢复成 1-MON,业务能跑了。然后重建挂掉的 2 个 MON。

全挂了:极危

3 个 MON 全死,且数据可能损坏。此时如果有 OSD 还活着,Ceph 提供了从 OSD 重建 MON store 的工具:

# 在任意 OSD 节点上
ceph-objectstore-tool --type bluestore --data-path /var/lib/ceph/osd/ceph-<id> \
  --op update-mon-db --mon-store-path /tmp/mon-store

# 在所有 OSD 节点都跑一遍,把 mon-store 累积起来

# 然后用累积的 mon-store 启一个新 MON
ceph-mon --mkfs -i <new_id> --keyring /etc/ceph/ceph.mon.keyring \
  --monmap /tmp/monmap
cp -r /tmp/mon-store/* /var/lib/ceph/mon/ceph-<new_id>/store.db/

这是最后的稻草,恢复出来的集群可能丢部分元数据(pool 配置、用户权限)。

预防

  • MON 一定要奇数个(3 或 5),不要 2 也不要 4
  • MON 节点分散在不同机架/机房,避免单点故障带走多个
  • 每天备份 monmapceph mon getmap -o /backup/monmap-$(date +%F)
  • monitor 节点磁盘用 SSD:MON 是写密集,HDD 会被慢死

教训:MON 挂的恢复操作都是高危,操作前 dd 备份每个 MON 的 /var/lib/ceph/mon 目录,搞错可以回退。

K8s 里用 Ceph 做持久化存储是经典组合。RBD 走 CSI 是当前标准方案,但配起来坑很多。

整体架构

StorageClass --> PVC --> CSI Provisioner --> Ceph (创建 RBD image)
                       \
                        --> CSI Node Plugin --> rbd map (节点上)
                                            --> mount (容器看到)

部署 ceph-csi

helm repo add ceph-csi https://ceph.github.io/csi-charts
helm install ceph-csi-rbd ceph-csi/ceph-csi-rbd \
  --namespace ceph-csi-rbd --create-namespace \
  --set csiConfig[0].clusterID="<集群ID>" \
  --set csiConfig[0].monitors[0]="<mon1>:6789" \
  --set csiConfig[0].monitors[1]="<mon2>:6789"

集群 ID 从 ceph fsid 拿。

创建 StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: csi-rbd-sc
provisioner: rbd.csi.ceph.com
parameters:
  clusterID: <fsid>
  pool: rbd
  imageFeatures: layering
  csi.storage.k8s.io/provisioner-secret-name: csi-rbd-secret
  csi.storage.k8s.io/provisioner-secret-namespace: ceph-csi-rbd
  csi.storage.k8s.io/node-stage-secret-name: csi-rbd-secret
  csi.storage.k8s.io/node-stage-secret-namespace: ceph-csi-rbd
reclaimPolicy: Delete
allowVolumeExpansion: true

imageFeatures: layering 必须只有 layering,多了内核 RBD 模块不支持会挂载失败。

几个典型坑

1. PVC 一直 Pending

describe pvc 通常看到 failed to provision volume with StorageClass。看 provisioner Pod 日志:

kubectl logs -n ceph-csi-rbd ceph-csi-rbd-provisioner-xxx -c csi-rbdplugin

最常见:secret 用户在 Ceph 上没权限。ceph auth get-or-create client.k8s mon 'profile rbd' osd 'profile rbd pool=rbd' 给权限。

2. Pod 起来 mount 失败

MountVolume.MountDevice failed for volume "xxx" : rpc error

节点上看 rbd 映射:

rbd showmapped
dmesg | tail

经常是 kernel rbd 模块 features 不支持(如 fast-diff、object-map 没关)。

3. 节点重启后 mount 不回来

CSI plugin 重启后会自动恢复,但如果 plugin 自己挂了就麻烦。建议给 plugin DaemonSet 加 priorityClass: system-node-critical

4. 扩容 PVC 卡住

PVC 改 size 后状态长期 Resizing

  • 检查 RBD image 是否真的扩了:rbd info rbd/csi-vol-xxx
  • 文件系统扩展需要 Pod 内执行(CSI 一般自动做 xfs_growfs,但 ext4 偶尔不灵)

监控指标

ceph-csi 自带 prometheus exporter,关键指标:

  • csi_operations_seconds:操作耗时
  • csi_operation_errors_total:错误数
  • rbd_image_count:当前管理的 image 数

教训:CSI 出问题先看 provisioner Pod 日志,再看节点 rbd 映射,再去 Ceph 集群查 auth。