山洪灾害后的 Ceph 惨案:PG incomplete 到 RBD 镜像消失


!! 大家好,我是wanger,一个爱折腾的运维工程师。


作者:wanger
公众号:运维开发故事
博客:https://devopstory.cn


背景

在一次山洪灾害后,机房的服务器全部断电,等供电恢复后进入系统发现所有的虚拟机文件系统损坏了,并且查看ceph集群有个pg出现inactive和incomplete状态,上传新的镜像io也会卡住,以下是恢复过程。

图片

图片

排查过程

incomplete和inactive状态含义解释

inactive
  • 含义:PG 处于不可用状态。

  • 表现:客户端对这个 PG 的读写请求都会被阻塞。

  • 原因可能包括:

  • 这个 PG 没有足够的 OSD 存活来提供服务。

  • PG 没有被分配到合适的 OSD 上。

  • OSD 没有完成 peering(对等协商)过程。

换句话说,inactive 就是 PG 不能对外提供正常的 IO 服务。

incomplete
  • 含义:PG 在 peering 时发现缺少必需的数据副本,导致无法达到一致性。

  • 表现:PG 中的数据不完整,无法对外提供读写。

  • 常见原因:

  • 某些 OSD 宕机或丢失数据,导致 PG 的对象副本无法凑齐。

  • 新 OSD 加入或者数据迁移时丢失了必要的副本。

  • 硬盘故障或误删导致数据确实丢失。

通常 incomplete 比 inactive 更严重, inactive只是PG 暂时不可用,但数据可能还在,只是没有满足对外服务条件。  incomplete出现时说明peering 过程中无法收集到足够的、权威的一致数据副本,意味着有的数据副本确实不存在了,需要人工干预才能恢复。往往出现在peering的过程中服务器异常断电, 在断电前 PG 的日志还没来得及落盘, 所有副本上的 PG log 都不完整,导致无法确定哪些对象是最新的 。

尝试对pg进行修复

查看集群所有的osd,发现都是up的

图片

尝试常规修复发现没什么用

ceph pg repair 2.1c

后查看pg上的对象数和丢失的对象数,发现pg上的对象数为0

ceph pg ls | grep 2.1c
ceph pg 2.1c list_unfound

图片

尝试回滚pg旧版本和重启pg副本所在的osd服务后重新修复均无效

ceph pg 2.1c mark_unfound_lost revert
ceph pg repair 2.1c

将pg副本的osd out再in后状态仍没有变化

ceph osd out <id>
ceph osd in <id>

使用ceph-objectstore-tool操作pg副本

集群状态一直无法恢复,准备使用ceph-objectstore-tool工具操作pg副本,只保留一份pg的副本,将其他两份的副本删除,并基于剩余的pg副本做回填,最后将剩下的pg副本标记为complete状态。

准备操作
#查看pg副本所在的osd
ceph pg map 2.1c
#防止副本操作期间触发数据重新均衡
ceph osd set noout
# 临时降低 min_size
ceph osd pool set libvirt-pool min_size 1
备份导出pg副本

可以看到导出的副本大小都是十几K,数据基本查看其他正常的pg,对象数量平均是在6000多个

systemctl stop ceph-osd@8
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-8  --type bluestore --pgid 2.1c --op export --file /opt/2.1c.obj_osd_8
systemctl stop ceph-osd@14
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-14  --type bluestore --pgid 2.1c --op export --file /opt/2.1c.obj_osd_14
systemctl stop ceph-osd@11
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-11  --type bluestore --pgid 2.1c --op export --file /opt/2.1c.obj_osd_11

图片

图片

图片

删除两个osd节点上的故障pg副本
systemctl stop ceph-osd@8
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-8/ --type bluestore --pgid 2.1c --op remove --force
systemctl stop ceph-osd@11
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-11/ --type bluestore --pgid 2.1c --op remove --force

图片

从剩余节点导入pg副本到其他两个osd节点
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-11/ --type bluestore --pgid 2.1c --op import --file /opt/2.1c.obj_osd.14
ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-8/ --type bluestore --pgid 2.1c --op import --file /opt/2.1c.obj_osd.14
systemctl start ceph-osd@11
systemctl start ceph-osd@8

图片

将剩余节点的pg副本标记为complete

执行常规恢复操作发现集群还是处于incomplete状态

ceph pg repair 2.1c

将剩余节点的pg副本标记为complete

systemctl stop ceph-osd@14
# 标记 complete
ceph-objectstore-tool --type bluestore --data-path /var/lib/ceph/osd/ceph-14 --pgid 2.1c --op mark-complete
ceph osd pool set libvirt-pool min_size 2
ceph osd unset noout
systemctl start ceph-osd@14

之后集群显示健康状态正常了

图片

但是发现rbd查看镜像发现全没了,存储大小没有变化

图片

对rbd镜像列表数据进行恢复

查看镜像头对象,发现还在,但是根据ID查询存储池中的镜像已经查不到了

rados -p libvirt-pool ls | grep '^rbd_header\.' | head
rbd -p libvirt-pool --image-id <id> info

图片

检查header对象的元数据,发现还在,说明只是目录对象丢失了

rados -p libvirt-pool listomapkeys rbd_header.d8b1996ee6b524 | head

图片

使用rados查看镜像发现能查询到

rados -p libvirt-pool stat rbd_header.d8b1996ee6b524

图片

搭建测试环境复现

搭建好测试环境后修复好后再在生产环境修复,先设置osd暂停恢复/回填,且把存储池 min_size 暂时降到 1

ceph osd set noout
ceph osd set norecover
ceph osd set nobackfill
ceph osd pool set libvirt-pool min_size 1

只保留 主副本 的osd在线,停掉另外两个副本

systemctl stop ceph-osd@4
systemctl stop ceph-osd@5

删除总目录对象,再删每个镜像的 id 映射条目

rados -p libvirt-pool rm rbd_directory
for img in $(rbd ls -p libvirt-pool 2>/dev/null); do
  rados -p libvirt-pool rm rbd_id.$img || true
done

让另外两个副本上线并恢复回填

systemctl start ceph-osd@4
systemctl start ceph-osd@5

ceph osd unset noout
ceph osd unset norecover
ceph osd unset nobackfill

验证复现结果,目录对象确实丢了,但是数据还在

rados -p libvirt-pool stat rbd_directory
rbd ls -p libvirt-pool
rados -p libvirt-pool ls | grep '^rbd_header\.' | head

图片

图片

恢复rbd的目录对象

从网上找了两个脚本,一个脚本可以根据header的ID反查镜像名,另一个是根据镜像名和header的ID来恢复rbd镜像目录

#!/bin/bash
# 用法: ./find_rbd_name.sh <IMAGE_ID>
# 例子: ./find_rbd_name.sh 3c456b8b4567

set -euo pipefail

POOL="libvirt-pool"

if [ $# -ne 1 ]; then
  echo "用法: $0 <IMAGE_ID>"
  exit 1
fi

ID="$1"

found=0
for obj in $(rados -p "$POOL" ls | grep '^rbd_id\.'); do
  got=$(rados -p "$POOL" get "$obj" - 2>/dev/null | tr -d '\n\r')
  if [ "$got" = "$ID" ]; then
    echo "发现: $obj  -> name = ${obj#rbd_id.}"
    found=1
  fi
done

if [ $found -eq 0 ]; then
  echo "未找到 ID=$ID 对应的镜像"
  exit 2
fi

通过直接改写 pool 里的 rbd_directory 对象的 OMAP 键值 来恢复

  • 每个 RBD 镜像都有一个头对象:rbd_header.<ID>,镜像的各种元数据都挂在它的 omap 上。

  • rbd ls 并不是去遍历所有 rbd_header.*,而是读 **rbd_directory** 这个对象的 omap

  • name_<NAME> → 值为 <ID>

  • id_<ID> → 值为 <NAME>

  • 值的编码不是裸字符串,而是:小端 4 字节长度(LE uint32) + 字符串本体。
    例如 name_foo 的值若为 "abc123",实际二进制是:06 00 00 00 61 62 63 31 32 33

  • 只要把这两条映射补上,rbd ls 就能重新列出 <NAME>rbd info <NAME> 也能通过目录映射定位到头对象 <ID>

#!/usr/bin/env bash
# 用法: ./fix_rbd_mapping.sh <NAME> <ID>
# 例子: ./fix_rbd_mapping.sh windows_7sp1_x86_dvd677486.img 2557396b8b4567

set -euo pipefail

POOL="libvirt-pool"

if [ $# -ne 2 ]; then
  echo "用法: $0 <NAME> <ID>"
  exit 1
fi

NAME="$1"
ID="$2"

# 0)(可选)先确保池已初始化过 RBD 目录对象;幂等,安全
rbd pool init "$POOL"

# 1) 备份一下目前这两条(如果不存在会报错但不影响继续)
rados -p "$POOL" getomapval rbd_directory "name_$NAME" /tmp/old_name_val.bin 2>/dev/null || true
rados -p "$POOL" getomapval rbd_directory "id_$ID"   /tmp/old_id_val.bin   2>/dev/null || true

# 2) name_<name> -> <id> (值为:LE4长度 + 字符串ID)
python3 - <<'PY' | rados -p "$POOL" setomapval rbd_directory "name_$NAME"
import sys,struct
img_id="$ID"
sys.stdout.buffer.write(struct.pack("<I", len(img_id)))
sys.stdout.buffer.write(img_id.encode())
PY

# 3) id_<id> -> <name> (值为:LE4长度 + 字符串NAME)
python3 - <<'PY' | rados -p "$POOL" setomapval rbd_directory "id_$ID"
import sys,struct
name="$NAME"
sys.stdout.buffer.write(struct.pack("<I", len(name)))
sys.stdout.buffer.write(name.encode())
PY

# 4) 校验两条键写好了
rados -p "$POOL" listomapvals rbd_directory | egrep 'name_$NAME|id_$ID' -n

# 5) 看列表与信息
rbd -p "$POOL" ls | grep -F -- "$NAME" || true
rbd -p "$POOL" info "$NAME"

图片

查看恢复后的镜像列表发现已经恢复

图片

最后,求关注。如果你还想看更多优质原创文章,欢迎关注我们的公众号「运维开发故事」。

如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!

你还可以把我的公众号设为「星标」,这样当公众号文章更新时,你会在第一时间收到推送消息,避免错过我的文章更新。


我是 wanger,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!

------本页内容已结束,喜欢请分享------

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发
运维开发故事的头像-运维开发故事

昵称

取消
昵称表情代码图片