定位一个ceph文件系统umount不掉的问题

定位问题 专栏收录该内容
3 篇文章 0 订阅

前言 本文为原创,可能会存在一些知识点或理解上的问题,欢迎切磋和交流  ^_^

虽然我很想说我就是这两个攻关问题的定位人,但很遗憾并不是我。既然不如人,就要多总结经验教训,虚心向高手请教和学习,在此记录自己的成长经历。

看看时间,2019年已经接近了尾声,如果盘点一下今年跳的坑,那么文件系统umount提示target is Busy,这一类问题绝对是放在最前面的,因为这一类问题对产品版本影响之恶劣,I/O路径跨度之长,定位问题根因之棘手等等吧,对于菜鸟级别的我来说,是锻炼和学习定位内核问题极佳的的机会,应该要好好总结一下,让自己长长记性。朋友说过一句话,定位内核问题就好比是一位技艺精湛的医生在为一位患者诊断病情一样,从初期的问题现象,到做了哪些操作,分析当前的日志,到对应代码流程,提出自己的怀疑点,添加调试日志进行验证怀疑点或者进行问题复现,到最后验证怀疑,再对症下药,给出解决方案,解决并闭环该问题,我很佩服他这个说法。所以这篇文章要记录的是这样一个定位问题的分析过程,重在整个问题的定位思路,具体代码细节不是本文章重点。脑壳也记不住~~

umount文件系统提示target is Busy卸载不掉,这种问题其实可以归为一类,因为根因是一个,但是这里计划分为两个问题案例来进行整理分析,因为问题现象来自于上层不同业务服务导致的。废话不多说,开始。

1.问题背景

分布式存储系统对上层业务提供存储服务,具有去控制器中心化,横向扩展容量能力较强,且性能不会随之下降的特点(随便说的几点,请忽略我的不专业),大概的架构如下草图所示。

那么在进行带业务在线升级的流程中,有业务的服务器会通过vip将业务迁移走,然后本节点执行升级操作,说白了就是替换一些文件(so库文件,内核ko,上层服务脚本或配置文件等)。好,你这个节点不是有业务流量下发嘛,vip先把业务漂走,停上层服务,替换文件,再把服务开启来,业务重新负载均衡和数据迁移,集群恢复正常,在线升级结束。这是一个大概的流程,其中在替换一些ko文件之前,要先将文件系统umount掉,因为文件系统挂载会占用内核ko,使ko替换失败,现在问题就来了,文件系统umout不掉了,ko文件也替换不了,导致整个在线升级流程中断,升级失败!客户本来心情挺好,想着升级一下服务器,新增一些功能,结果登录设备一看,升级失败,报stop xxx service failed!心情突然报表,尼玛!!!什么鬼???(黑人问号头像) 

2.带samba业务在线升级导致文件系统执行umount失败

2.1 问题描述

2.1.1 一句话总结

Windows客户端通过samba服务访问共享,退出共享目录后,底层执行umount 共享挂载失败,提示umount:xxx: target is busy,分析原因为挂载点文件系统mnt_count引用计数不满足卸载条件。如下图所示。

2.1.2问题描述引发的思考

问题描述,要用一句话来完成,基本操作是什么,报错是什么,报错原因是什么。言简意赅既体现出对问题本身的认识和理解程度,也可以让工作沟通成本下降很多。

2.2 问题现象

 

以上问题描述是后来找到了必现条件,才得以如此盖棺定论,证据确凿。实际是集群服务器通过cifs挂载共享给客户端并跑samba业务,执行在线升级,结果升级失败,报stop xxx service failed。

2.3 问题初步定位

拿到问题,首先要看日志,通过分析日志,排查当时发生了什么操作,这些操作引起了什么问题。但是这个问题很不幸,通过查看上层服务日志,只能看到umount xxx报target is busy错误,就这一句话。再没其他日志打印了。所以通过分析查看日志,不能再继续定位。打住了。

2.3.1 定位umount XXX target is busy

出现这行打印,大家都知道一定是要卸载的挂载点正在被某些进程或线程占用,所以只需要通过lsof命令或fuser命令查看一下即可定位。

是否是很简单?但是你有遇到过即便通过lsof命令查看挂载点在没有任何进程或线程占用的情况下,执行umount仍然报device is busy?

2.3.2 系统工具

2.3.2.1 lsof命令

当umount操作失败提示device or target is busy时,通过lsof工具可以查看哪些进程或线程在占用要卸载的挂载点,进而定位umount不掉的问题。下面就简单说一下lsof命令的使用。

man手册里对lsof的information如下

功能描述:lsof命令用来查看系统下打开文件的进程或者进程打开了哪些文件

lsof基本用法:

(1) lsof  

查看系统内打开的所有文件,输出信息意义如下

 

(2) lsof 文件名称

用于查看打开该文件的所有进程

(3) lsof -p PID

用于查看该进程打开的所有文件,实例如下

我在使用lsof工具过程中,基本用到以上两种方式,另外lsof支持不同的参数,比如可以监控网络连接、网络端口等,这里暂没有用到这些参数配置,暂时不说

(4) lsof命令可以恢复被删除的文件(扩展一下)

使用如下命令可以查看被删除的文件

lsof | grep deleted

实例操作如下:

a. 执行demo程序open一个TEXT.log文件并始终保持打开

实例如下图

b. rm命令删除TEXT.log文件

c. lsof | grep TEXT.log

以上可以查看到open_file进程正在打开该文件,且文件描述符为3,而且可以看到该文件已被删除

d. cat /proc/4012/fd/3

通过/proc下对应open_file进程fd目录下文件描述符可以查看被删除的文件。

lsof恢复删除文件的实现原理以及/proc内存文件系统在后面会继续学习并整理。

2.3.2.2 fuser命令

man手册里对fuser工具描述如下

fuser工具作用和lsof一样,用于监控系统里哪些进程占用磁盘上的文件、挂载点、网络端口等,并输出详细的进程信息。

Fuser基本用法如下所示

(1)fuser -v filename

加了参数v表示以ps命令查看进程的方式,呈现出占用文件的所有进程

实例-写demo始终打开一个文件test_fuser

(2) fuser filename

显示打开该文件的所有进程pid

(3)fuser -m -v filename

列出打开该文件所有进程的pid和pid拥有者

通过以上两个系统工具是可以查看到到底是还有哪些进程在占用文件,获取相关信息后,就可以做下一步处理了。

但是这个问题很诡异,通过以上两个系统工具依然查看不到。直接来定位分析该问题似乎摸不着头脑,因为从系统检测工具检查结果来看,没有发现异常问题,这就促使不得不看一下到底什么操作能够导致这种现象的发生,接下来就是复现手段。

lsof命令只能查看用户态进程的占用,因为它的原理是通过/proc内存文件系统来获取对某一个文件系统进行访问的用户态进程,但是它对内核线程无能为力。所以这就是为什么明明卸载不掉,可lsof就是查不到。

既然日志没有内容,那就只能尝试复现或者说找出必现条件。

2.4 问题复现手段

复现手段和添加调试日志的目的是为了确定是哪段流程,哪段代码的问题。

2.4.1 找到必现条件

确认出现问题现象之前该设备都做过哪些操作,列举的思路如下:

  1. 查看历史命令操作记录
  2. 直接与TC进行沟通
  3. 是否与上层业务流量(iometer、vdbench)有关,在打流量和无流量情况下,分析问题现象是否发生
  4. 是否与相关服务(如nfs、samba、ftp)有关,停服务和开启服务,分析问题现象是否发生

通过与TC沟通,确认操作步骤如下表

操作步骤

  1. win通过samba访问共享并打流量
  1. 执行在线升级
  1. 底层执行失败,报stop xxx failed

从以上操作来看,很简单,外部因素只有samba访问共享,并执行在线升级。经过操作,发现只要通过samba进入共享目录再退出,就会必先该问题现象。

所以,必现条件找到了!

反思一下:这里必现条件很重要,很多攻关问题搞不定,就是不能稳定复现,或者干脆找不到必现条件,能够稳定复现的问题,加点调试信息,比如复现三把,总能出一把,这种根据调试信息看流程,一点一点总能有点收获;如果直接找到了必现条件,那么可以直接看对应的代码就可以了。所以,现在觉得,攻关问题或者定位问题,如果日志不能够足以帮助分析,就要找复现条件,找不到,真的就是大海捞针,太难了!所以这类问题定位的关键是找必现条件。

2.4.2 问题现象和操作之间的关联

有些问题在现象出现后,很有可能无法通过操作复现问题现象。这是最头疼的,因为这个时候没线索了,所以只能自外至内,从外部因素一一进行排查,可供参考的外部因素如下表所示。

外部因素

问题现象

上层业务流量(iometer、vdbench)

在打流量和无流量情况下,分析问题现象是否发生

相关服务(如nfs、samba、ftp)

停服务和开启服务,分析问题现象是否发生

产品软件版本

是否只有当前版本存在该问题,对比测试进行分析

合入问题单是否引入问题

分析对应版本合入的问题单是否会引入问题

通过以上分析,确认win通过samba访问共享目录必先该问题现象,对比不同版本,发现只有当前版本存在该问题,沿着这条线索,继续刨根问底,查看了一下该版本合入的问题单,终于有了重大发现!其中合入的一个问题单现象描述和本问题很像,且该版本无其他合入,该问题进一步缩小定位范围。

此时可以提出怀疑点:该问题单导致win访问共享并退出后,文件系统mnt_count引用计数异常,导致umount命令执行失败。

经过验证,确认怀疑点。

现在找到了问题原因,但是未分析出根因,到底是哪行代码出了问题,目前仍是未知的。那么需继续定位,下面我们从整个io栈流程出发,进一步锁定是哪一行代码或逻辑出的问题。

2.4.3 规律很重要

规律,对,就是这个词,为什么要强调这个词,因为我们在复现问题的时候,往往复现不出来,是因为还没有找到规律,一旦找到了,摸索出来了,就可以使问题由概率复现,到稳定复现,到必现。这个samba案例就是每cd一次共享目录,引用计数就+1,然后可以看到引用计数不断增大,这就说明了cd操作的IO路径一定有一个地方没减回去,大概率是自己引入的;而下个nfs服务案例就是操作一通后,引用计数只多增1,这就说明这个IO路径一定要满足一定条件才发生,会不会并发操作等等。

与问题现象上的规律相对应的自然是操作上的改变,比如,跑业务,大量的创建文件,写文件,删文件,重命名一个文件,是不是可以只对一个文件进行操作,或者多个机头,多个客户端只对一个文件进行操作?

2.4.4 抓主要操作

我在复现该问题时,犯的最大一个错误就是严格按照测试操作来执行,这一点并没有什么错误,但是恰恰是严格按照,导致花了大量的时间进行问题复现,结果呢,并没有复现出,或者概率出,其实在复现问题时,完全可以想一想,这个问题和升级有关系吗,是不是一点关系都没有,那还升个毛级啊,完全是浪费时间,直接打流量跑啊!不要小看了这一思维,这一下子就把复现操作的时间大大缩短,只抓重点,忽略不相干。

2.4.5 千万不能守株待兔

什么意思?我在攻关nfs问题时就犯了个毛病,这个问题不是通过添加调试信息定位出来的吗,既然找不到必现条件,那就把日志都加上,等下次出现的时候,一定能够定位,这个思路是完全错误的!实事证明下个nfs带上调试信息确实复现了,但是根据调试信息,确实定位不出来,因为只看引用计数变化,没规律,看堆栈,就都是vfs层调用。

2.5 I/O流程分析

2.5.1 samba访问共享目录XXX 的io路径

2.5.2 umout流程

通过调试打印信息确认,如上红框内代码未执行,返回值为-EBUSY。该问题初步分析原因为文件系统mnt_count引用计数不为2,所以导致执行umount命令时提示target is busy。

说明:mnt_count引用计数的作用

每一个文件系统装载到挂载目录时,mnt_count引用计数会初始化为1,当访问该挂载目录或访问该挂载目录下的文件时,mnt_count引用计数会加1,退出该挂载目录时,mnt_count引用计数会减1,当执行umount命令时,代码逻辑会判断mnt_count引用计数是否是2,如果是2,则执行成功,返回值为0,引用计数不为2,则犯错-EBUSY,既我们看到的target is busy。

接下来通过分析mnt_count引用计数操作的代码,确认mntget对mnt_count引用计数增加,mntput对mnt_count引用计数减减。其中mntget和mntput都调用mnt_add_count,故只需要在这三个接口添加调试信息就可以了。

其实,很简单的一个事,但是后面却大费周折,还没有解决问题。

其实到这里就深入到代码层面进行定位了,这一阶段要么是干看代码,把问题直接看出来,要么看代码,梳理流程,提出怀疑点,添加调试日志进行验证。

2.6添加调试信息

2.6.1“笨蛋”方法解决不了问题

分析代码,已经确定要在mntget、mntput、mnt_add_counts三处添加调试信息,将引用计数打印出来。调试信息涉及内核一些变量、函数未定义问题,做了修改后,进行编译。这里使用的工具是rpmbuild。通过rpmbuild工具整编内核源码,效率非常低,编译出的rpm包后面证明没有起到定位问题的关键,所以大费周折,浪费了大量的时间,却收不到应有的效果。如果不是做内核裁剪或内核项目开发,是不应该进行整编内核源码的。所以这是一个教训。

2.6.2 内核热补丁调试方法

这里使用jprobe工具对内核源码进行调试大大提高了编译效率,使之前5分钟编码1小时编译的工作进度提高到5分钟编码1分钟编译。jprobes工具是kprobes内核调试工具的一种,这里简单讲一下jprobe工具的应用,后续会详细讲解kprobe内核热补丁工具原理。jprobe工具不能在函数任意位置插入探测点,只能在函数入口处,对函数入参进行探测。使用jprobe探测工具探测函数入参,需要编写内核模块。Kernel内核源码提供了jprobe实例程序jprobe_example.c(存放路径为sample/kprobes),该程序模板已实现一个do_fork()实例。实例代码如下图所示。

使用jprobe工具对函数入参进行探测,需注意如下两点:

  1. 探测回调函数入参必须和被探测函数一致;
  2. 在回调函数执行完,必须调用jprobe_return();

对mnt_add_count使用jprobe工具探测函数入参,使用dump_stack打印出函数调用堆栈。编译ko文件,并替换到设备节点上。

2.7 关联操作和调试日志

通过samba访问共享目录操作,dmesg可以查看并获取打印堆栈日志信息。

2.8 得出怀疑点并验证

通过mnt_add_count函数堆栈信息,分析对mnt_count引用计数增加和减少函数堆栈信息,发现user_path_at_empty接口会对mnt_count引用计数增加,而与之对应的一定是path_put,这两个接口是一对。

其中问题分析到这里,再对比模块代码,就明白了为什么引用计数异常,只增不减的原因了。

既然找到了怀疑点,可以做一下验证,添加调试再跑一遍。

2.9 得出定位根因

最终定位到该问题原因是当使用user_path_at_empty函数对mnt_count引用计数增加后,未调用path_put释放mnt_count引用计数,导致文件系统mnt_count一直不为2,所以umount不掉。

3. 带nfs业务在线升级时导致升级失败

3.1 问题描述

一句话总结:集群挂载nfs客户端并跑业务,执行在线升级,出现在线升级失败,分析原因是ceph文件系统引用计数异常,执行umount不掉,导致升级流程中断。

3.2 问题现象

通过分析日志,还是没有办法进行定位,需要找到必现条件。

3.3 问题复现

  1. 既然是跑nfs业务才出现的引用计数异常,是不是和升级操作没有关系?直接nfs挂载给客户端,跑nfs业务试试能不能复现出来,这里通过多个vip挂载同个共享目录到不同客户端目录进行跑业务,结果是大概率复现!但是问题是带了调试信息,一堆计数,也没用!这就很蛋疼。
  2. 再看引用计数异常,始终是3,即多了1个,跑这么多nfs业务创建文件,结果只多了1,是不是和跑多个文件没关系?多个客户端只跑一个文件呢?在此两个客户端同时执行dd一个大文件,结果必现!后面操作发现同时touch同一个文件也是必现!

3.4 I/O流程分析

  1. 找到了必现条件,就知道出问题的IO流程,根据添加调试信息就可以不断缩短这段IO流;
  2. 但是通过大量的版本测试,怀疑是最近合入代码引起问题,即直接找到了合入的代码进行分析;

3.5 添加调试信息

直接分析流程,得出怀疑点,添加调试信息,验证怀疑点;

3.6 得出定位根因

略。

4. 定位思路

5. 定位问题注意事项

  1. 当TC在测试过程中发现bug,找到开发时,我们一定要第一时间保留环境,并且在允许情况下,登录到环境查看相关日志和操作记录,确认是否是本模块问题;
  • 1
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

清水浊酒

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值