在一次集群数据恢复过程中遇到的,最终导致osd无法全部启动,数据恢复失败
踩坑环境背景
3osd,两副本pool,三个osd同时离线,且有一个osd无法启动,已经认为out和rm了,需要恢复数据(原集群可能不是active+clean状态,和两副本集群,坏一个副本的情况不一样,可能认为因素导致某些数据没恢复完就删掉了主pg所在osd)
- 无历史pgmap记录(由于没有历史pg map信息,pg整正常状态和原本主pg所在osd信息无法获取)
- 异常osd已经auth del,和rm过,外加硬件错误,导致该osd已无法使用ceph-objectstore-tool进行pg export-remove和import等操作
因此只有尽可能从另外两个osd中尽量挽回数据。
恢复过程
取一个卡在down状态pg,在两个osd进行比对后,使用ceph-objectstore-tool在其中一个osd上mark 该pg complete,并拉起osd,pg从down状态进入active+degrade+undersize+backfilling等状态,最终可以进入active+clean状态。
后续pg如法炮制,最终所有pg全部从down状态进入active状态,还需要等待backfill恢复。
在恢复过程中,原本标记为完整副本的osd突然停止(如果是服务的话,应该是反复重启)。报错堆栈如下
1: (SnapSet::get_clone_bytes(snapid_t) const+0xb6) [0x707b46]#012
2: (ReplicatedPG::_scrub(ScrubMap&)+0x9e8) [0x7c0198]#012
3: (PG::scrub_compare_maps()+0x5b6) [0x755306]#012
4: (PG::chunky_scrub(ThreadPool::TPHandle&)+0x1d9) [0x758999]#012
5: (PG::scrub(ThreadPool::TPHandle&)+0x19a) [0x75b96a]#012
6: (OSD::ScrubWQ::_process(PG*, ThreadPool::TPHandle&)+0x19) [0x657309]#012
7: (ThreadPool::worker(ThreadPool::WorkThread*)+0xaf1) [0xa56dd1]#012
8: (ThreadPool::WorkThread::entry()+0x10) [0xa57cc0]#012
9: (()+0x8182) [0x7f579ce6e182]#012
10: (clone()+0x6d) [0x7f579b5e0fbd]
查看日志,指向osd_types.cc中如下代码段。
oid SnapSet::from_snap_set(const librados::snap_set_t& ss, bool legacy)
{
// NOTE: our reconstruction of snaps (and the snapc) is not strictly
// correct: it will not include snaps that still logically exist
// but for which there was no clone that is defined. For all
// practical purposes this doesn't matter, since we only use that
// information to clone on the OSD, and we have already moved
// forward past that part of the object history.
seq = ss.seq;
set<snapid_t> _snaps;
set<snapid_t> _clones;
for (vector<librados::clone_info_t>::const_iterator p = ss.clones.begin();
p != ss.clones.end();
++p) {
if (p->cloneid != librados::SNAP_HEAD) {
_clones.insert(p->cloneid);
_snaps.insert(p->snaps.begin(), p->snaps.end());
clone_size[p->cloneid] = p->size;
clone_overlap[p->cloneid]; // the entry must exist, even if it's empty.
for (vector<pair<uint64_t, uint64_t> >::const_iterator q =
p->overlap.begin(); q != p->overlap.end(); ++q)
clone_overlap[p->cloneid].insert(q->first, q->second);
if (!legacy) {
// p->snaps is ascending; clone_snaps is descending
vector<snapid_t>& v = clone_snaps[p->cloneid];
for (auto q = p->snaps.rbegin(); q != p->snaps.rend(); ++q) {
v.push_back(*q);
}
}
}
}
// ascending
clones.clear();
clones.reserve(_clones.size());
for (set<snapid_t>::iterator p = _clones.begin(); p != _clones.end(); ++p)
clones.push_back(*p);
// descending
snaps.clear();
snaps.reserve(_snaps.size());
for (set<snapid_t>::reverse_iterator p = _snaps.rbegin();
p != _snaps.rend(); ++p)
snaps.push_back(*p);
}
根据代码中clone_overlap[p->cloneid];与q != p->snaps.rend(),某些clone条目已经不存在或者不完整时,此处就会出现问题。
redhat中相关链接
文中提到,复现此bug需要1.某种特定方式损坏快照集 2.正好触发损坏部分的校验恢复。实际上该osd可以单独启动,只有启动另一个osd需要进行pg的backfilling时才会报错,情况类似。
可能的规避或者恢复手段
本身是小概率bug,特定情况下损坏,此处pg所有osd同时离线且出现系统问题的几率还是较小,人为因素较多,如果有历史pgmap,已经保留原本集群内所有osd,恢复难度应该小很多。与项目相关人员沟通,集群可能之前很多pg就是单副本状态运行,只存在于个别osd上,未能进入clean的ok状态。运维时发现集群有降级或异常,需立即处理,避免侥幸心理。
网上提供的方法(指clear-snapset clone_size方式),本身只适合少量snap丢失部分内容的场景,而该集群很多snap已经获取不到,丢失程度严重,无法完成最终数据恢复。即使进行了全部的有损恢复,对块存储以及快照,虚拟机等业务基本也是致命的,部分数据损坏对于这些业务来讲,可能一小块影响整段数据无法使用,与rgw可能只是影响部分对象访问的情况不同。