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。

Ceph 性能差通常不是单点问题,是从内核到应用一系列设置叠加的结果。本文给一个我经常用的 checklist。

1. 内核 / 系统层

# 文件描述符
echo "* soft nofile 1048576" >> /etc/security/limits.conf
echo "* hard nofile 1048576" >> /etc/security/limits.conf

# 内核网络 buffer
sysctl -w net.core.rmem_max=268435456
sysctl -w net.core.wmem_max=268435456
sysctl -w net.ipv4.tcp_rmem="4096 87380 268435456"
sysctl -w net.ipv4.tcp_wmem="4096 65536 268435456"

# 关闭 transparent hugepage(OSD 内存碎片元凶)
echo never > /sys/kernel/mm/transparent_hugepage/enabled

2. 磁盘 / IO 调度

# NVMe / SSD 用 none 调度器
echo none > /sys/block/nvme0n1/queue/scheduler

# HDD 用 mq-deadline
echo mq-deadline > /sys/block/sdb/queue/scheduler

# 关闭 readahead 对 OSD 是反优化(OSD 自己管缓存)
blockdev --setra 0 /dev/sdb

3. 网络层

  • MTU 9000:jumbo frame 对 Ceph 复制流量收益巨大,但要全链路 9000
  • bond mode=4 (LACP):交换机配 LAG,单流也能跑多端口
  • public/cluster 分离:上面说过

4. Ceph 配置

# OSD 内存上限(按物理内存 70% / OSD 数)
ceph config set osd osd_memory_target 4G

# bluestore 缓存(默认对 SSD 偏小)
ceph config set osd bluestore_cache_size_ssd 4G
ceph config set osd bluestore_cache_size_hdd 2G

# 并发
ceph config set osd osd_op_num_shards 8
ceph config set osd osd_op_num_threads_per_shard 2

5. Pool 层

  • PG 数量:每 OSD 100-150 个 PG 是甜点,少了并发不够,多了元数据压力大
  • EC 还是副本:冷数据 EC 8+3,热数据 size=3 副本
  • min_size:副本池别设成 1,掉一个 OSD 就丢数据

6. 客户端

  • RBD:开 librbd cache rbd_cache=true
  • CephFS:客户端缓存 client_cache_size=1GiB
  • RGW:开 civetweb/beast 的 keep-alive

实测怎么 baseline

# RADOS 层基准
rados bench -p test 60 write -t 32
rados bench -p test 60 seq

# RBD 层
rbd bench --io-type write --io-size 4K --io-threads 16 --io-total 10G test/bench

调优前后跑同样的 bench,差异肉眼可见。

教训:Ceph 调优最大的坑是"调一个看一个"——很多参数会互相干扰,建议每次只改一个,跑 bench 对比。

默认 CRUSH 把数据按 host 分布,但生产经常需要按机架、机房分布做高可用。本文示范从默认改到机架感知的完整流程。

默认 CRUSH 长啥样

ceph osd tree

输出大概:

ID  CLASS  WEIGHT   TYPE NAME
-1         12.0     root default
-2         4.0          host node1
 0   hdd   1.0              osd.0
 ...

只有 root → host → osd 三层。

加 rack 一层

# 创建 rack
ceph osd crush add-bucket rack1 rack
ceph osd crush add-bucket rack2 rack

# 把 host 移到对应 rack 下
ceph osd crush move node1 rack=rack1
ceph osd crush move node2 rack=rack1
ceph osd crush move node3 rack=rack2

# 把 rack 挂到 root
ceph osd crush move rack1 root=default
ceph osd crush move rack2 root=default

改规则 — 副本跨机架

# 导出默认 rule
ceph osd getcrushmap -o /tmp/crush.bin
crushtool -d /tmp/crush.bin -o /tmp/crush.txt

# 改 rule,把 step chooseleaf type host 改成 type rack
vi /tmp/crush.txt

改完编译回去:

crushtool -c /tmp/crush.txt -o /tmp/crush.new
ceph osd setcrushmap -i /tmp/crush.new

验证

ceph pg dump | awk '/active+clean/ {print $1, $15}' | head
# 看 acting set 里的 OSD 是不是真的跨 rack 了

一个常见坑

如果 rack 数量 < pool 副本数(比如只有 2 个 rack 但 size=3),改完规则后 PG 会变 undersized。要么加 rack,要么把 size 降到 rack 数量。

进阶:upmap 平衡器

CRUSH 是伪随机,分布不可能完全均匀。打开 upmap 平衡器:

ceph balancer mode upmap
ceph balancer on

它会自动微调 PG 位置,把不均匀度降到 5% 以内。Nautilus 以上默认就是 upmap。

为什么用 cephadm 而不是 ceph-ansible

Nautilus 之后官方主推 cephadm,原因是它只依赖 SSH + container runtime,部署比 ansible 简洁很多。生产推荐直接上 cephadm,老的 ceph-deploy/ceph-ansible 维护不动了。

引导(bootstrap)

# 引导节点上
curl --silent --remote-name --location https://github.com/ceph/ceph/raw/quincy/src/cephadm/cephadm
chmod +x cephadm
./cephadm add-repo --release quincy
./cephadm install
cephadm bootstrap --mon-ip <第一个MON的IP> --initial-dashboard-password 'YourPassword'

引导完成会输出 dashboard URL 和 admin 凭据。

加节点 + 加 OSD

# 把其他节点加入集群
ceph orch host add node2 <node2-ip> _admin
ceph orch host add node3 <node3-ip>

# 自动发现并使用所有未用的盘
ceph orch apply osd --all-available-devices

# 或精确指定
ceph orch daemon add osd node2:/dev/sdb

第一周必做清单

  1. 改时间同步:所有节点 chronyd,偏移 >50ms MON 会闹脾气
  2. 关闭 swapswapoff -a + 注释 fstab
  3. public_network / cluster_network 分离:业务流量和复制流量分开物理网卡
  4. mon_max_pg_per_osd 调大:默认 250,集群规模小时容易撞
  5. 设 mon_allow_pool_delete=true:测试期允许删 pool(生产再关)
  6. dashboard 改 HTTPSceph dashboard create-self-signed-cert
  7. 配 telegraf/prometheus 抓监控ceph mgr module enable prometheus
  8. 加一份 admin keyring 备份:丢了集群进不去

一个反直觉的事

cephadm 部署后,所有服务都是容器,不要去 systemctl restart ceph-mon@xxx,要用 ceph orch daemon restart mon.xxx。否则状态会和 orchestrator 不一致。