Ceph蹚坑笔记 - (1)
现象
由于设备调度的原因,运维的哥们需要把一台服务器上的OSD移除。他做了以下操作:
1. pkill ceph-osd
关闭服务器上的所有OSD
2. 在集群把这些OSD标记成down后,用ceph osd rm <osd-id>
删除对应的OSD ID
3. 期待与这些OSD有关的PG转移到其它OSD上,并由degraded状态转到active+clean状态
4. 等了很久,发现集群完全没有修复这些degraded PG的意思
5. 实在没办法,再执行ceph osd crush remove osd.<osd-id>
,degraded PG终于逐渐转到active+clean状态
分析
OSD的状态发生变化触发OSD Map更新,所有活着的OSD都会基于新的OSD Map和根据CRUSH算法重新计算其所承载的PG的新归宿。如果发现PG的新归宿不是当前承载它的OSD集合,集群就会触发Peering动作(自己主导Peering或通知其他OSD发动Peering),进而进行必要的Recovery或Backfill。
OSD变为down后,相应的PG转入degraded状态是符合预期的。按常理说,在ceph osd rm <osd-id>
之后集群应该注意到OSD的状态又发生了一些变化,并为degraded PG寻找新的归宿。
既然ceph osd rm <osd-id>
没有触发Peering,很有可能是因为OSD不认为PG的归宿发生了变化。按照这个思路,查看了代码。OSDMap::_raw_to_up_osds()
的如下代码确认了这个推测(环境中用的是ReplicatedPG)。这里!exists
与is_down
被认为是等价的,在OSD变为down后,集群的决策是:幸存下来的OSD继续承载degraded PG。ceph osd rm <osd-id>
之后,OSDMap::_raw_to_up_osds()
的结果和OSD变为down时结果是一样的,所以不认为degraded PG需要新的OSD来承载,也就没有触发Peering和相应的Recovery或Backfill。
up->clear();
for (unsigned i=0; i<raw.size(); i++) {
if (!exists(raw[i]) || is_down(raw[i]))
continue;
up->push_back(raw[i]);
}
后来,尝试了以下操作序列,就没有问题了
1. pkill ceph-osd
关闭服务器上的所有OSD
2. 在集群把这些OSD标记成down后,用ceph osd out <osd-id>
把对应OSD标记为out(也可以清除noout标志位,等待足够长的时间,让集群自动把OSD标记为out)
3. 用ceph osd rm <osd-id>
删除对应的OSD ID
4. 相应的PG逐渐从degraded状态转到active+clean状态
本来以为ceph osd rm <osd-id>
会隐含ceph osd out <osd-id>
。从这次的观察看,这样的想当然是错误的。查看了一下OSDMonitor::prepare_command_impl()
中的如下代码发现,原来out操作修改的是OSD ID的权值(OSDMap::osd_weight[id]
在OSDMap::_pg_to_osds()
被使用),而rm是修改OSD ID的状态信息(OSDMap::osd_state[id]
在OSDMap::_raw_to_up_osds()
中被间接使用)。
} if (prefix == "osd out") {
if (osdmap.is_out(osd)) {
ss << "osd." << osd << " is already out. ";
} else {
pending_inc.new_weight[osd] = CEPH_OSD_OUT;
ss << "marked out osd." << osd << ". ";
any = true;
}
}
// ...
} else if (prefix == "osd rm") {
if (osdmap.is_up(osd)) {
if (any)
ss << ", ";
ss << "osd." << osd << " is still up; must be down before removal. ";
err = -EBUSY;
} else {
pending_inc.new_state[osd] = osdmap.get_state(osd);
pending_inc.new_uuid[osd] = uuid_d();
pending_metadata_rm.insert(osd);
if (any) {
ss << ", osd." << osd;
} else {
ss << "removed osd." << osd;
}
any = true;
}
}
OSDMap::_pg_to_osds()
被先调用,然后OSDMap::_raw_to_up_osds()
基于OSDMap::_pg_to_osds()
的结果做运算。out的OSD在OSDMap::_pg_to_osds()
先被清理出局,down的或被rm的OSD在OSDMap::_raw_to_up_osds()
才被清理出局。
假设两副本的PG X最先是由osd.1和osd.11承载的。然后,osd.1进入down状态而不是out。这时,OSDMap::_pg_to_osds()
还是选出了osd.1和osd.11,然后OSDMap::_raw_to_up_osds()
从中选出了osd.11。接着osd.1被执行ceph osd rm
,由于is_down
和!exists
是等价的,OSDMap::_raw_to_up_osds()
还是为PG X选择了osd.11。
假设两副本的PG X最先由osd.1,osd.11承载。但osd.1从down状态进入out状态之后才被执行ceph osd rm
,OSDMap::_pg_to_osds()
会把osd.1淘汰并为PG X选择osd.11和另一个存活的OSD(假设是osd.21),而OSDMap::_raw_to_up_osds()
也在此基础上选出了存活的osd.11和osd.21。这样集群就知道PG X应该重新分布到osd.11和osd.21上了。
ceph osd crush remove
之所以也能能够触发PG的重分布,也是因为它修改CRUSH Map(OSDMap.crush)而促使OSDMap::_pg_to_osds()
为PG X选择osd.11和osd.21而不是osd.1和osd.11。
结论
- 这里的
ceph osd rm <osd-id>
不是很有必要,除非永远都不想再使用这些OSD ID了 ceph osd rm <osd-id>
与ceph osd out <osd-id>
不同,ceph osd rm <osd-id>
也不隐含ceph osd out <osd-id>
- 在
ceph osd rm <osd-id>
之前最好等待对应的OSD变成out