CephFS v16.2.6升级事故

创建时间:2021/11/26 下午8:09:46
编辑时间:2021/11/26 下午8:09:46
作者: huww98@163.com (huww98@163.com)
分类:Linux

本次使用cephadm自动化从Ceph v16.2.5升级到v16.2.6过程中,元数据服务MDS未能正常完成自动升级,造成了从2021/9/17 22:37到次日5:46服务中断。主要原因为上游自动化升级流程缺乏测试,导致明显的bug未被发现。只要在升级时max_mds设置大于1或开启了standby_replay就能触发。但该问题想要恢复其实应该并不难,但我在处理时的一些鲁莽操作走了不少弯路。

升级流程bug

本次更新加入了在MON中协调不同版本的MDS的功能,确保不兼容的MDS版本间不会相互通信。MON要求in状态的MDS数量为1且禁用standby_replay时才能将MDS集群升级至新版本。然而,cephadm的升级流程中并未触发down-scale的流程,导致上述升级条件不能满足,所有新版本的MDS均处于standby状态而不能被提升到replay状态。

cephadm在升级了3个MDS后就因为文件系统处于降级状态停住了,于是我手动升级了最后一个MDS,当时的状态为:

# ceph fs status
cephfs - 0 clients
======
RANK  STATE   MDS  ACTIVITY  DNS  INOS  DIRS  CAPS
0    failed
1    failed
          POOL              TYPE     USED  AVAIL
   cephfs.cephfs.meta     metadata   114G   404G
   cephfs.cephfs.data       data    84.9T  17.6T
cephfs.cephfs.data_ssd     data       0    606G
cephfs.cephfs.data_mixed    data    9879G   404G
    STANDBY MDS
cephfs.gpu023.aetiph
cephfs.gpu018.ovxvoz
cephfs.gpu006.ddpekw
cephfs.gpu024.rpfbnh
MDS version: ceph version 16.2.6 (ee28fb57e47e9f88813e24bbf4c14496ca299d31) pacific (stable)

# ceph fs dump
e41422
enable_multiple, ever_enabled_multiple: 0,1
default compat: compat={},rocompat={},incompat={1=base v0.20,2=client writeable ranges,3=default file layouts on dirs,4=dir inode in separate object,5=mds uses versioned encoding,6=dirfrag is stored in omap,8=no anchor table,9=file layout v2,10=snaprealm v2}
legacy client fscid: 2

Filesystem 'cephfs' (2)
fs_name cephfs
epoch   41422
flags   12
created 2020-09-15T04:10:53.585782+0000
modified        2021-09-17T15:05:26.239956+0000
tableserver     0
root    0
session_timeout 60
session_autoclose       300
max_file_size   1099511627776
required_client_features        {}
last_failure    0
last_failure_osd_epoch  43315
compat  compat={},rocompat={},incompat={1=base v0.20,2=client writeable ranges,3=default file layouts on dirs,4=dir inode in separate object,5=mds uses versioned encoding,6=dirfrag is stored in omap,8=no anchor table,9=file layout v2,10=snaprealm v2}
max_mds 1
in      0,1
up      {}
failed  0,1
damaged
stopped
data_pools      [5,13,16]
metadata_pool   4
inline_data     disabled
balancer
standby_count_wanted    1


Standby daemons:

[mds.cephfs.gpu023.aetiph{-1:7908668} state up:standby seq 1 join_fscid=2 addr [v2:202.38.247.186:6800/3495351337,v1:202.38.247.186:6801/3495351337] compat {c=[1],r=[1],i=[7ff]}]
[mds.cephfs.gpu018.ovxvoz{ffffffff:78cdb8} state up:standby seq 1 join_fscid=2 addr [v2:202.38.247.181:1a90/94680caa,v1:202.38.247.181:1a91/94680caa] compat {c=[1],r=[1],i=[7ff]}]
[mds.cephfs.gpu006.ddpekw{ffffffff:78f84f} state up:standby seq 1 join_fscid=2 addr [v2:202.38.247.175:1a90/fdd0fd1a,v1:202.38.247.175:1a91/fdd0fd1a] compat {c=[1],r=[1],i=[7ff]}]
[mds.cephfs.gpu024.rpfbnh{ffffffff:78fc4e} state up:standby seq 1 join_fscid=2 addr [v2:202.38.247.187:1a90/4e2e69dc,v1:202.38.247.187:1a91/4e2e69dc] compat {c=[1],r=[1],i=[7ff]}]
dumped fsmap epoch 41422

可以看到两个rank均处于failed状态,但所有MDS daemon均处于up:standby状态,却没有进入下一个up:replay状态,进而开始提供服务。incompat列表中缺少7。

从现在来看,要解决这个状态,有两个方法应该比较靠谱:

  1. 停止所有处于up状态的MDS,然后手动修改compat,以允许新版本MDS加入集群,然后启动各新版MDS:

    ceph fs compat cephfs add_incompat 7 "mds uses inline data"
    

    该方案已经在邮件列表中得到了多人的验证,应该是没有问题的。

  2. 暂停cephadm的更新,手动回滚部分MDS,确保没有failed状态的rank,然后手动scale-down,即设置max_mds为1,禁用standby_replay。然后把手动回滚的MDS撤销,再继续升级流程。

执行rmfailed导致MON崩溃

起初当我看到所有rank都处于failed状态时,我误以为是因为这个failed状态导致新版MDS无法加入集群。但实际上failed状态是个正常的过渡态,它是该rank上一个MDS下线后,下一个MDS接管前的正常状态。也就是说,是因为新版MDS无法加入集群而导致该状态持续存在。个人认为这个failed的名字有一定误导性,若叫pending可能更好一些。我也提了一个PR对这个状态进一步解释了一下。

基于错误的认识,我执行了ceph mds rmfailed命令,这是个隐藏的命令,它的作用就是将某一rank从failed集合中移除。这个操作给我带来了一大堆问题,以下的操作几乎都是用来撤销该命令的影响。

首先是由于failed集合为空,MON认为文件系统不再处于降级状态了,并可以进行resize。在之前试图解决问题时,我将max_mds设置为了1,于是此时MON试图将rank 1上的MDS设置为stopping状态,以收缩集群大小。但此时rank 1上并没有MDS,导致了5个MON中的3个崩溃。

使用gdb恢复MON

此时问题已经相当棘手。若MON集群无法达成quorum,则无法更新MON中存储的配置数据,而若不更新配置,则无法修复MON的崩溃。

我最终手动进入容器中,使用gdb启动MON,并在会崩溃的MDSMonitor::maybe_resize_cluster方法上设置断点,使其立刻返回false,以达到暂时屏蔽resize功能的目的。该方案的大致操作方法如下。操作前需先在容器中按gdb提示安装调试符号。仅需要使用此方法启动将会称为leader的MON进程即可。

# gdb --args /usr/bin/ceph-mon <other args>
(gdb) b MDSMonitor::maybe_resize_cluster
(gdb) commands
return false
cont
end
(gdb) run

在保持MON组成quorum且暂时不会崩溃后,我将max_mds重新设置回2,使MON不再尝试resize MDS集群。

手动修正incampat

这是实际需要的修复步骤,如上所述,较为简单:

ceph fs compat cephfs add_incompat 7 "mds uses inline data"

7正是新版MDS要求而默认的compat中缺少的。若满足升级条件(in状态的MDS数量为1且禁用standby_replay),该步骤本应当回自动执行。

设法撤销rmfailed的修改

然而由于我清空了failed集合,即便我手动修正了compat,standby的MDS也不会自动加入集群(code)。所以我需要撤销之前rmfailed命令的更改。但可惜并没有一个addfailed类似的命令。

首先我试图如法炮制,使用gdb修改MON进程中的逻辑,执行插入到failed集合的逻辑。但可惜,在执行set<...>::insert()时gdb报错Cannot evaluate function -- may be inlined。下回或许可以尝试使用gdb的compile命令编译并执行一段代码。

最终我从源码编译了MON的可执行文件,并修改源码加入了一个addfailed命令。在源码中应用了如下patch:

diff --git a/src/mon/MDSMonitor.cc b/src/mon/MDSMonitor.cc
index 4373938..786f227 100644
--- a/src/mon/MDSMonitor.cc
+++ b/src/mon/MDSMonitor.cc
@@ -1526,7 +1526,7 @@ int MDSMonitor::filesystem_command(
     ss << "removed mds gid " << gid;
     return 0;
     }
-  } else if (prefix == "mds rmfailed") {
+  } else if (prefix == "mds addfailed") {
     bool confirm = false;
     cmd_getval(cmdmap, "yes_i_really_mean_it", confirm);
     if (!confirm) {
@@ -1554,10 +1554,10 @@ int MDSMonitor::filesystem_command(
         role.fscid,
         [role](std::shared_ptr<Filesystem> fs)
     {
-      fs->mds_map.failed.erase(role.rank);
+      fs->mds_map.failed.insert(role.rank);
     });

-    ss << "removed failed mds." << role;
+    ss << "added failed mds." << role;
     return 0;
     /* TODO: convert to fs commands to update defaults */
   } else if (prefix == "mds compat rm_compat") {
diff --git a/src/mon/MonCommands.h b/src/mon/MonCommands.h
index 463419b..5c6a927 100644
--- a/src/mon/MonCommands.h
+++ b/src/mon/MonCommands.h
@@ -334,7 +334,7 @@ COMMAND("mds repaired name=role,type=CephString",
COMMAND("mds rm "
        "name=gid,type=CephInt,range=0",
        "remove nonactive mds", "mds", "rw")
-COMMAND_WITH_FLAG("mds rmfailed name=role,type=CephString "
+COMMAND_WITH_FLAG("mds addfailed name=role,type=CephString "
         "name=yes_i_really_mean_it,type=CephBool,req=false",
        "remove failed rank", "mds", "rw", FLAG(HIDDEN))
COMMAND_WITH_FLAG("mds cluster_down", "take MDS cluster down", "mds", "rw", FLAG(OBSOLETE))

同样也只需要替换作为leader的MON进程即可。执行完自己添加的addfailed命令后,MDS总算按照期望正常加入集群了。

总结

心怀敬畏,切莫冲动。隐藏命令之所以隐藏是有原因的。遇到这样的问题其实首先可以尝试回滚。

文档很重要。若有更好的文档,我可能也不会产生上述误解。

测试。难以想象,作为一个如此著名的开源软件,如此正常的升级流程中的bug居然没被测试覆盖。在该版本发布的第一天,从邮件列表中来看,包括我至少有4人受到这个bug影响,并发邮件求助了。

使用开源软件有风险。在新版发布的第一天升级则更有风险。


返回文章列表

评论

登录 / 注册 后发布评论