写在前面
喜欢ceph的话欢迎关注 奋斗的cepher 微信公众号阅读更多好文!
上篇松鼠哥从操作层面恢复了一个被删除了的rbd存储池,对比了删除前后数据的情况,有部份数据已经完全被删除回收了,很多时候,尤其对于块存储,如果被回收的数据是块的元数据,那恢复出来其他的数据也许就没有意义了,因为块本身无法正常组织起来,因而,我们更关注的是能否完全做到不丢失数据!
答案是可以做到完全不丢数据!但是需要修改代码
代码的探索
首先,我们先理清楚当一个存储池被删除后,osd是如何处理删除pg流程的,这个很关键。
在测试集群vstart中,我们通过观察其日志,可以窥见其处理流程
2023-01-05 10:49:33.634 osd.1 15 queue_for_pg_delete on 1.0 e 15
2023-01-05 10:49:38.636 crt=0'0 unknown NOTIFY mbc={}] _delete_some
2023-01-05 10:49:38.636 _collection_list 1.0_head start GHMIN end GHMAX max 30
2023-01-05 10:49:38.636 _collection_list range #-3:00000000::::0#0 to #-3:40000000::::0#0 and #1:00000000::::0#0 to #1:40000000::::0#0 start GHMIN
2023-01-05 10:49:38.636 _collection_list pend #-3:40000000::::0#0
2023-01-05 10:49:38.636 _collection_list oid #-1:30f0e826:::osdmap.13:0# >= #-3:40000000::::0#0
2023-01-05 10:49:38.636 _collection_list oid #1:00000000::::head# end GHMAX
2023-01-05 10:49:38.636 _collection_list oid #1:40000000::::head# >= #1:40000000::::0#0
2023-01-05 10:49:38.636 collection_list 1.0_head start GHMIN end GHMAX max 30 = 0, ls.size() = 1, next = GHMAX
2023-01-05 10:49:38.636 crt=0'0 unknown NOTIFY mbc={}] _delete_some finished
2023-01-05 10:49:38.636 queue_transactions ch 0x55b09b60de00 1.0_head
......
2023-01-05 10:49:38.654 osd.1 15 try_finish_pg_delete 1.0 0x55b09916f000
2023-01-05 10:49:38.654 osd.1:0._detach_pg 1.0 0x55b09916f000
2023-01-05 10:49:38.654 cancel_reservation cancel 1.0 (not found)
2023-01-05 10:49:38.668 bluestore.OnodeSpace(0x55b09b60df58 in 0x55b099090000) clear
日志太长,这里只截取了部份关键的信息,简单来说,PG删除的OP到达osd后,osd开始删除PG对应的object,然后开始清理这些object对应的空间,try_finish_pg_delete
完成收尾工作,最后bluestore.OnodeSpace
进行了空间的回收,即删除OP一到,立刻开始pg的删除及空间的回收。
经过分析,代码中主要完成PG删除任务的是_delete_some
函数,这个函数有个很有意思的地方,就是它自带了一个延迟PG删除的功能
ghobject_t PG::_delete_some(ObjectStore::Transaction *t,
ghobject_t _next)
{
dout(10) << __func__ << dendl;
{
float osd_delete_sleep = osd->osd->get_osd_delete_sleep();//读取配置文件中osd_delete_sleep的值
if (osd_delete_sleep > 0 && delete_needs_sleep) { //判断PG是否进入延迟删除
epoch_t e = get_osdmap()->get_epoch();
PGRef pgref(this);
auto delete_requeue_callback = new FunctionContext([this, pgref, e](int r) {
dout(20) << __func__ << " wake up at "
<< ceph_clock_now()
<< ", re-queuing delete" << dendl;
lock();
delete_needs_sleep = false;
if (!pg_has_reset_since(e)) {
osd->queue_for_pg_delete(get_pgid(), e);
}
unlock();
});
utime_t delete_schedule_time = ceph_clock_now();
delete_schedule_time += osd_delete_sleep; //如果延迟删除的话,将会延后osd_delete_sleep秒
Mutex::Locker l(osd->sleep_lock);
osd->sleep_timer.add_event_at(delete_schedule_time,
delete_requeue_callback);
dout(20) << __func__ << " Delete scheduled at " << delete_schedule_time << dendl;
return _next;
}
}
......
}
真不错,自带了延迟删除功能!这样一来,如果我们启用了延迟删除PG,就能在它延迟期间进行数据的抢救!
进入延迟删除的两个条件是osd_delete_sleep
和delete_needs_sleep
,其中osd_delete_sleep
可以在配置文件中配置,我们可以设定一个比较合理的时间,比如1小时,而delete_needs_sleep
这个值比较麻烦。
从代码来看,当它为true时,才有可能进入延迟删除的流程,但它并不能通过配置文件进行配置,也没有其他地方能够对它进行修改,通过跟踪发现,它作为一个PG类的保护成员变量,初始值为false,在PG.h
的2902行中定义
bool delete_needs_sleep = false;
这就非常奇怪,默认为false,因而一开始就无法进入到延迟删除,但这个值又不给修改或者配置(虽然函数后面有改为true的操作,但是没用,因为走到后面pg都删掉了),那这个延迟删除流程就怎么都跑不进去,这么设计是什么意思呢????有明白的读者朋友希望赐教。
修改代码后的初步测试
delete_needs_sleep
默认是false不行,我们将其修改为true,然后重新编译代码,看看效果。
修改PG.h
后,重新编译,再重新起测试集群,将osd delete sleep
配置为300,再来一次删除存储池,然后观察其处理流程有何变化
2023-01-05 11:58:28.169 osd.1 15 queue_for_pg_delete on 1.0 e 15
2023-01-05 11:58:28.169 _delete_some Delete scheduled at 2023-01-05 12:03:28.169538 //表明进入了延迟删除
2023-01-05 12:03:28.171 osd.1 op_wq(0) _process OpQueueItem(1.0 PGDelete(1.0 e15) prio 5 cost 1048576 e15) queued
2023-01-05 12:03:28.172 _delete_some
2023-01-05 12:03:28.172 collection_list 1.0_head start GHMIN end GHMAX max 30
2023-01-05 12:03:28.172 _collection_list range #-3:00000000::::0#0 to #-3:40000000::::0#0 and #1:00000000::::0#0 to #1:40000000::::0#0 start GHMIN
2023-01-05 12:03:28.172 _collection_list pend #-3:40000000::::0#0
2023-01-05 12:03:28.172 _collection_list oid #-1:30f0e826:::osdmap.13:0# >= #-3:40000000::::0#0
2023-01-05 12:03:28.172 _collection_list oid #1:00000000::::head# end GHMAX
2023-01-05 12:03:28.172 _collection_list oid #1:40000000::::head# >= #1:40000000::::0#0
2023-01-05 12:03:28.172 collection_list 1.0_head start GHMIN end GHMAX max 30 = 0, ls.size() = 1, next = GHMAX
2023-01-05 12:03:28.172 _delete_some [#1:00000000::::head#]
2023-01-05 12:03:28.172 _delete_some finished
2023-01-05 12:03:28.172 queue_transactions ch 0x55bcf93d6000 1.0_head
......
2023-01-05 12:03:28.174 osd.1 15 try_finish_pg_delete 1.0 0x55bcf93e6000
2023-01-05 12:03:28.174 osd.1:0._detach_pg 1.0 0x55bcf93e6000
2023-01-05 12:03:28.174 cancel_reservation cancel 1.0 (not found)
2023-01-05 12:03:28.188 bluestore.OnodeSpace(0x55bcf93d6158 in 0x55bcf6cfc000) clear
出现了Delete scheduled at 2023-01-05 12:03:28.169538
推迟的日志!而且在既定的时间PG继续了之前的删除工作,这表明,让osd延迟删除pg是行得通的!我们将有可能在这段延迟的时间段内将所有数据恢复回来!
我们还需要关注当pg延迟删除后,在此期间如果恢复存储池,其后续是否真的能够恢复到完好如初的程度!推迟的op是否直接作废。
那么做法上,我们从测试集群将修改代码并完成编译后的二进制ceph-osd
对准生产集群对应文件进行替换,看看其实际的效果如何。
准生产场景下的恢复验证
我们这里直接开搞,配置延迟删除时间为10分钟,并打开相关osd日志
[twj@osd-node-66 ~]$ tail -n3 /etc/ceph/ceph.conf
osd_delete_sleep = 600
[osd.1962]
debug_osd = 20/20
严谨一点,我们将rbd map后,格式化为ext4,然后往里面写入2个大文件,并计算md5值,记录pool的对象数量
[root@mon-node-01 twj]# ceph df
RAW STORAGE:
CLASS SIZE AVAIL USED RAW USED %RAW USED
hdd 28 PiB 28 PiB 161 TiB 163 TiB 0.56
ssd 31 TiB 31 TiB 18 GiB 54 GiB 0.17
TOTAL 28 PiB 28 PiB 161 TiB 163 TiB 0.56
POOLS:
POOL ID PGS STORED OBJECTS USED %USED MAX AVAIL
rbd-test 21 2048 2.8 GiB 1.25k 8.4 GiB 0 138 TiB
[root@mon-node-01 mnt]# df -h|grep rbd0
/dev/rbd0 1008G 2.8G 954G 1% /mnt
[root@mon-node-01 twj]# cd /mnt/
[root@mon-node-01 mnt]# md5sum test-file.tar
c41d683573f35f564916fcfb4792ef6b test-file.tar
[root@mon-node-01 mnt]# md5sum test-file2.tar
dd36b478953677a5b34861454cdde081 test-file2.tar
[root@mon-node-01 mnt]# rados -p rbd-test ls |wc -l
1248
接下来,删除这个存储池,预料之中,osd.1962的日志显示延迟删除,推迟时间为10分钟
2023-01-05 14:30:20.816 7f2ff50ee700 20 osd.1962 pg_epoch: 3540 pg[21.527( v 3539'115 (0'0,3539'115] lb MIN (bitwise) local-lis/les=3538/3539 n=0 ec=3531/3527 lis/c 3538/3538 les/c/f 3539/3539/0 3540/3540/3540) [] r=-1 lpr=3540 DELETING pi=[3538,3540)/1 crt=3539'115 lcod 3539'114 unknown NOTIFY mbc={}] _delete_some Delete scheduled at 2023-01-05 14:40:20.817426
ok,接下来模拟线上,我们先等待5分钟,然后将对应的osd全部stop掉,接着进行存储池的恢复。
如果不down掉的话,恢复存储池的时间过长,就会导致数据丢失,实际上我们在线上处理也应该是这样,保险起见,在条件允许的情况下(比如pool不共用osd),应该把osd先down掉,以确保数据的安全。
接下来就是**“成熟”**的技术了,上篇及之前的文章已经操作过了,这里直接快进
- 回滚集群osdmap到删除存储池之前的版本,这个要根据时间戳来确定
- force-create一个pg激活pool
- 当集群重新显示被删除的pool后,对相关osd进行手工mark complete
- 启动osd
[twj@mon-node-01 twj]$ cat mon_osdmap.sh
#!/bin/bash
ndel='3539
3540
3541'
for x in ${ndel}
do
ceph-kvstore-tool rocksdb /var/lib/ceph/mon/ceph-`hostname`/store.db/ rm osdmap ${x}
ceph-kvstore-tool rocksdb /var/lib/ceph/mon/ceph-`hostname`/store.db/ rm osdmap full_${x}
done
ceph-kvstore-tool rocksdb /var/lib/ceph/mon/ceph-`hostname`/store.db/ set osdmap full_latest ver 3538
ceph-kvstore-tool rocksdb /var/lib/ceph/mon/ceph-`hostname`/store.db/ set osdmap last_committed ver 3538
chown ceph.ceph /var/lib/ceph/mon/ceph-`hostname`/store.db/*
[twj@mon-node-01 twj]$ sudo ceph osd force-create-pg 21.2ae --yes-i-really-mean-it
[root@osd-node-66 store]# cat recover.sh
#!/bin/bash
osds=$( seq 1950 1979 )
for x in $osds
do
allpgs=`ceph-objectstore-tool --data-path /var/lib/ceph/osd/ceph-${x}/ --type bluestore --op list-pgs`
for apg in $allpgs
do
ceph-objectstore-tool --pgid ${apg} --op mark-complete --data-path /var/lib/ceph/osd/ceph-${x}/ --type bluestore
done
done
集群完全恢复后,我们再次验证数据
data:
pools: 12 pools, 45280 pgs
objects: 158.38k objects, 2.8 GiB
usage: 163 TiB used, 28 PiB / 28 PiB avail
pgs: 45277 active+clean
3 active+clean+scrubbing+deep+repair
io:
client: 2.0 MiB/s rd, 192 MiB/s wr, 1.99k op/s rd, 1.40k op/s wr
[twj@mon-node-01 ~]$ sudo rbd ls rbd-test
test1
[twj@mon-node-01 ~]$ sudo rbd map rbd-test/test1
/dev/rbd0
[twj@mon-node-01 ~]$ sudo mount /dev/rbd0 /mnt
[twj@mon-node-01 ~]$ df -h |grep mnt
/dev/rbd0 1008G 2.8G 954G 1% /mnt
[twj@mon-node-01 ~]$ cd /mnt/
[twj@mon-node-01 mnt]$ ls
lost+found test-file2.tar test-file.tar
[twj@mon-node-01 mnt]$ sudo md5sum test-file.tar
c41d683573f35f564916fcfb4792ef6b test-file.tar
[twj@mon-node-01 mnt]$ sudo md5sum test-file2.tar
dd36b478953677a5b34861454cdde081 test-file2.tar
[twj@mon-node-01 mnt]$ sudo rados -p rbd-test ls |wc -l
1248
数据完全一致!yes! i did it!
pool恢复后检查osd日志,完全没有pg删除的相关操作了!很明显,原来的删除op失效了,数据完完全全恢复了!真的是人类福音_
总结
这波验证意义还是很大的,通过修改代码层面的配置及ceph.conf的设置,完全恢复被删除存储池的数据很显然是可以做到的,不过遗留的delete_needs_sleep
的问题,默认是false,逻辑上还有点迷糊,接下来还是要再搞搞懂才行~
另外,本次演示操作是针对3副本的pool,而ec的pool是有点不同的,毕竟pg的设计差异很大,后面有时间和精力了,再深挖一下关于ec存储池恢复的问题。
不管怎么说,手工恢复被删除的存储池属于迫于无奈的极限操作,希望大家在日常维护中打醒12分精神,希望这种骚操作,大家都没有机会用上_
如果觉得松鼠哥的文章写得不错,就请赏杯咖啡吧。
喜欢ceph的话欢迎关注 奋斗的cepher 微信公众号阅读更多好文!