1. 问题现象描述
2023 年 06 月 30 日在迁移数据库过程中,遇到数据库 crash 的缺陷,原因如下:在数据库启动时候生成的一组临时文件中,有 owner 为 root 的文件, 文件权限默认为 640, 当数据库需要使用的时候, mysql 用户又没有权限,然后直接导致数据库 crash,临时文件如下:
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo1_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo2_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo3_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo4_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo5_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo6_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo7_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo8_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo9_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo10_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo11_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo12_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo13_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo14_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo15_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo16_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo17_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo18_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo19_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo20_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo21_tmp'
-rw-r----- 1 mysql dbgrp 33554432 Jun 30 17:44 '#ib_redo22_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo23_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo24_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo25_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo26_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo27_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo28_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo29_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo30_tmp'
-rw-r----- 1 root dbgrp 33554432 Jun 30 17:44 '#ib_redo31_tmp'
-rwxrwxrwx 1 mysql dbgrp 33554432 Jun 30 17:46 '#ib_redo0'
通过分析代码发现,这些都是在数据库启动的时候一起创建的,启动的时候,会删除现有的临时文件(带 tmp 的文件),然后再根据#ib_redoN_tmp(本例为 0), 创建后续的临时文件,本例从#ib_redo1_tmp 开始,创建 31 个临时文件,但是,经常出现后面几个文件的 owner 变成了 root, 具体有几个文件的 owner 是 root 也不固定,有时候只有 1-2 个,有时候有 10 来个,有时候一个都没有。
现场认为: 从理论上跟实际来讲, MySQlD 进程创建的所有文件的 owner ,都应该
是 mysql , mysql 没有理由跟必要去修改自己的文件的 owner 为 root, 通过官方网站的后续版本也没有发现关于这个现象的任何介绍,目前暂时怀疑是操作系统创建文件时的一些差异。
2. 问题分析
检查 my.conf 中配置的相关 mysql 目录权限,目录属主和属组为 mysql:dbgrp,
未发现异常。 my.cnf 如图 1
图 1
mysql 相关目录及权限如图 2,值得注意的是网上有提到, mysql 有些日志目录权
限不能为 777,得改成 700,如 redo_log 目录(此处仅作为参考供数据库厂家确认)
图 2
检查 my.cnf 文件配置参数,发现未配置 user=mysql 参数,但是检查 mysql 进程可以看到进程启动是带了—user=mysql 的,所以 mysqld 进程是完全以 mysql 用户启
动的,未见异常,如图 3
图 3
将启动脚本写在 /etc/rc.local 中,重启服务器进行测试,数据库产生的临时文件属主正常。但是如果在启动之后, 将启动脚本写在/etc/rc.local 中,执行systemctl restart rc-local,这样启动数据库后,有概率性产生的#ib_redoN_tmp文件属主不正常,并且每次都是从#ib_redo26_tmp 开始才会有属主为 root 的问题,#ib_redo25_tmp 及之前的 tmp 文件属主为正常的 mysql。 如图 4
图 4
/etc/rc.local 启动脚本如图 5
图 5
怀疑是写在 rc-local 中,有些资源或者配置没有完全加载完的原因,或者是环境变量不一致的原因导致的该差异。由于现场没有 root 密码,不能以 single 的方式进入单用户进行验证,所以后续将重点放在了环境量是否有差异上面。
经过测试,通过堡垒机的方式 ssh 到服务器上,执行数据库启动命令存在#ib_redoN_tmp 属主为 root 的问题、通过平台 console 的方式同样也有该问题,但是,如果是先通过堡垒机方式 ssh 到服务,然后执行 su - root 的方式,这样在启动数据库,就不会有问题。这步怀疑,可能是几种登录方式的不一致,各自的环境变量不同导致的该问题。
通过 set > /tmp/set.baolei 和 su - root 后 set > /tmp/set.root 做对比vimdiff /tmp/set.baolei /tmp/set.root 发现变量存在很大差异,包含 PATH 等变量均有不同处。 Vimdiff 结果如图 6、图 7、图 8
图 6
图 7
图 8
查看当前系统相关环境变量配置文件,如: /etc/profile, /root/.bashrc/root/.bash_profile 等,无异常 mysql 变量,如图 9、图 10、图 11
图 9
图 10
图 11
从 目 前 来 看 , 环 境 变 量 的 不 一 致 并 非 /etc/profile 、 /root/.bashrc 、/root/.bash_profile 引起,而是由于登录方式的不一致导致。
继续寻找根源,配置 audit 审计规则,发现异常时候, audit 审计到虽然都是mysqld 进程里面的某个线程创建的#ib_redo26_tmp 文件,但是异常时候, euid 也就是有效用户却是 root 而非 mysql, 如图 12
图 12
而正常时候, audit 审计到的有效用户 euid 却是 mysql, 如图 13,怀疑是 mysqld在创建第#ib_undo26_tmp 之前,有一段逻辑将有效用户设置为了 root。
图 13
通 过 strace 抓 取 异 常 和 正 常 时 候 的 系 统 调 用 , 发 现 异 常 时 候 , 在 创建!ib_redo25_tmp 和!ib_redo26_tmp 之间存在 setreuid 设置 uid 行为, `setreuid(-1,0)=0`是一个系统调用的返回值,表示调用成功执行。具体来说,这个系统调用是用于修改进程的实际用户 ID(ruid)和有效用户 ID(euid),其中, `-1`表示保持原有的 ruid 不变, `0`表示将 euid 设置为 0(即 root 用户)。
在 Linux 系统中,进程的 ruid 和 euid 通常是相同的。通过`setreuid()`系统调用,可以修改进程的 ruid 和 euid,从而改变进程的权限。如果这个调用返回了 0,则表示修改成功,否则返回的是一个错误码,表示修改失败。 如图 14
这也和从!ib_redo26_tmp 及之后的属主变成 root 吻合。
图 14
而正常时候的 strace 信息,则是在线程创建完所有的#!b_redoN_tmp 之后,才执行 setreuid 操作,如图 15。
图 15
通过以上分析,发现启动过程中调用了 setreuid 方法,对比异常与正常日志,发现此参数出现的位置也所有不同,正常的是在日志生成后才有设置 setereuid,而异常的是在日志生成过程产生,从而有 root 属主问题,其中 setreuid 参数调用时会修改有效用户导致后续生产日志文件属主变 root。
并且在 mysql 的源码中,存在设置 euid 逻辑的代码, 如图 16, 需要麻烦数据库厂家同事一起排查,是否是由于环境变量不一致,触发了数据库里面的某一段逻辑,使得提前执行了 setreuid 的操作,从而导致了后续#ib_redoN_tmp 文件属组变成了root。
图 16
3. 问题分析结果
初步怀疑是由于 msyqld 提前调用 setreuid 将线程的有效用户设置为了 root,使得接下里产生的日志文件属主变成了 root,需要数据库厂家结合代码看一下该段逻辑是怎么样的,看是否和环境变量有直接或者间接的关系。