疑难问题?MariaDB 调试容器

MariaDB 确实存在错误。用户有时会遇到这些错误。有时开发者长时间查看错误报告和代码,仍然无法确定情况是如何发生的。开发者在分析过程中会提出类似以下的问题:

  • 我想知道这是否已在 {未发布版本} 中修复?但我怎么能要求用户测试呢?
  • 能否让用户获取有助于更好地理解问题的优秀堆栈跟踪?但用户有时觉得这很难。
  • 此错误发生在何种确切的硬件和内核配置上?我该如何重现此问题?
  • 我想获取这些数据的一个副本,尝试一些可能有损的操作来更好地理解问题?但用户数据并非总是可共享的,或容易复制的。
  • 记录 MariaDB 进入此状态的方式的 rr 记录能否帮助我更好地理解这个问题?
  • 我知道如果我在本地处理这个问题会获得什么样的调试器信息,我能要求用户这样做吗?

用户也向 MariaDB 提出一些棘手的问题,例如:

  • MariaDB 将所有 CPU 时间花在哪里了?
  • 我能否记录 MariaDB 在做什么的火焰图?

MariaDB 基金会开发了容器镜像 quay.io/mariadb-foundation/mariadb-debug,以帮助为开发者提供关于问题的更多信息,从而解决 MariaDB 问题,并让开发者能够以不占用大量时间的方式向用户请求更多信息。

什么是 quay.io/mariadb-foundation/mariadb-debug?

quay.io/mariadb-foundation/mariadb-debug 是一个容器镜像。它基于 quay.io/mariadb-foundation/mariadb-devel 镜像,该镜像 如之前的博客文章所述,是每个主要受支持的稳定分支(以及不稳定的 10.8 分支)的 MariaDB 当前最新开发版本。此镜像的行为与您可能正在使用的 Docker Library docker.io/library/mariadb 完全相同。

区别在于:

  • 安装了调试信息包,以便更容易获取堆栈解析。
  • 容器中安装了额外的调试工具。
  • 安装了 Curl,方便上传到 MariaDB 私有 FTP 服务器(最近刚添加 – 可能并非所有镜像都包含)。

值得注意的是,这并非服务器调试构建(即使用 -DCMAKE_BUILD_TYPE=Debug 编译的构建)。

我的 MariaDB 运行时错误是否已经修复?

quay.io/mariadb-foundation/mariadb-devel 镜像足以测试这一点。如果问题是崩溃或需要进一步调试,可以在以下步骤中将其替换为 quay.io/mariadb-foundation/mariadb-debug 镜像。

步骤如下:

  1. podman pull quay.io/mariadb-foundation/mariadb-devel:{same major version};在下面的示例中,我们将使用 10.5。
  2. 停止现有容器
  3. 像运行之前的 10.5 镜像一样运行 quay.io/mariadb-foundation/mariadb-devel:10.5 镜像。例如:podman run -v {volume}:/var/lib/mysql -p quay.io/mariadb-foundation/mariadb-devel:10.5
  4. 运行您之前的 SQL/测试,查看是否观察到相同的行为。

我需要一个用于错误报告的回溯

与上面使用 quay.io/mariadb-foundation/mariadb-devel:10.5 镜像来确定错误是否仍然存在的步骤类似,这里使用调试信息来提供详细信息,以便用于错误报告。步骤如下:

  1. 创建数据目录,通过停止当前容器并记录 /var/lib/mysql 上的数据卷信息。
  2. 启动调试容器。请注意,所有调试都需要 CAP_SYS_PTRACE 能力。
  3. 触发崩溃
  4. 记录回溯
  5. 将回溯文件从容器复制出来用于错误报告。

以下是重现现有错误 MDEV-26412 的分步指南。

创建数据目录

这会在新的卷 db105 上创建一个空的数据目录。下面使用 Docker Library mariadb 镜像来创建此目录,但 quay.io/mariadb-foundation/mariadb-develquay.io/mariadb-foundatin/mariadb-debug 镜像也能实现相同的结果。

$ podman volume create db105
db105

$ podman run -v db105:/var/lib/mysql -e MARIADB_DATABASE=test -e MARIADB_USER=bob -e MARIADB_PASSWORD=pluto -e MARIADB_RANDOM_ROOT_PASSWORD=1 -d --name mdb105 --rm mariadb:10.5
91b59609aa83f474422aa3f7b6b693243782a61698153f4dac1e689744edf937

$ podman kill mdb105
91b59609aa83f474422aa3f7b6b693243782a61698153f4dac1e689744edf937

启动调试容器

在这里,我们通过在 /var/lib/mysql 上使用卷 db105 来启动之前卷的调试容器,并使用 --user mysql(以便权限正确,需要 CAP_SYS_PTRACE 以便我们可以调试和跟踪进程),以及使用 gdb --args mysqld 在调试器下运行服务器实例。

$ podman run -ti -v db105:/var/lib/mysql --user mysql --cap-add CAP_SYS_PTRACE --name mdb105 quay.io/mariadb-foundation/mariadb-debug:10.5 gdb --args mysqld
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.html
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
https://gnu.ac.cn/software/gdb/bugs/.
Find the GDB manual and other documentation resources online at:
https://gnu.ac.cn/software/gdb/documentation/.
For help, type "help".
Type "apropos word" to search for commands related to "word"…
Reading symbols from mysqld…
Reading symbols from /usr/lib/debug/.build-id/c3/b5a5cf349a1d49643b9eaab6e9d667fcc2d005.debug…
(gdb) r
Starting program: /usr/sbin/mysqld

上面输入的 r 命令是我们用来开始调试的。

触发崩溃

我们正在触发的错误是一个基本的 SQL 错误。我们使用容器中安装的 MariaDB 监视器来运行 SQL。

$ podman exec -ti mdb105 mysql -u bob -ppluto test
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.14-MariaDB-88b339805d7a9ddebc3fd61e9dee83270dbf474d mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [test]> CREATE TABLE v0 ( v1 BIGINT ( 67 ) NOT NULL ) ;
Query OK, 0 rows affected (0.033 sec)

MariaDB [test]>  CREATE TABLE v2 ( v4 INT , v3 INT NOT NULL UNIQUE KEY CHECK ( -128 | ( str_to_date ( CHAR ( 33 ) , 'x' ) ) ) ) ;
Query OK, 0 rows affected (0.017 sec)

MariaDB [test]>  DROP FUNCTION IF EXISTS v0 ;
Query OK, 0 rows affected, 1 warning (0.000 sec)

MariaDB [test]>  INSERT INTO v0 SELECT DISTINCT * FROM v0 FULL JOIN v2 ON ( SELECT v0 . v1 ) ;

如果我们回头看启动容器的终端,可以看到它在崩溃发生的位置停止了

Version: '10.5.14-MariaDB-1:10.5.14+maria~focal' as '10.5.14-MariaDB-88b339805d7a9ddebc3fd61e9dee83270dbf474d' socket: '/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution
[New Thread 0x7fc5f05eb700 (LWP 33)]
--Type for more, q to quit, c to continue without paging--c

Thread 26 "mysqld" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fc5f05eb700 (LWP 33)]
Item_field::fix_outer_field (this=0x7fc568014a90, thd=0x7fc568000c58, from_field=0x7fc5f05e9750, reference=0x7fc568014bd8) at ./sql/item.cc:5636
5636 ./sql/item.cc: No such file or directory.

按照我们的知识库文章 创建回溯 中的模式。我们使用 /var/lib/mysql 来存储创建的跟踪文件,因为调试器运行所在的 mysql 用户对该目录可写。

(gdb) set logging file /var/lib/mysql/gdb_output.txt
(gdb) set pagination off
(gdb) set logging on
Copying output to /var/lib/mysql/gdb_output.txt.
Copying debug output to /var/lib/mysql/gdb_output.txt.
(gdb) thread apply all bt -frame-arguments all full

创建了大量的回溯信息后,我们使用 set logging off 结束。

.....
#5  0x00007fc5f3d850b3 in __libc_start_main (main=0x55ac89d1bab0 <main(int, char**)>, argc=1, argv=0x7fff5f7f1dc8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fff5f7f1db8) at ../csu/libc-start.c:308
        self = <optimized out>
        result = <optimized out>
        unwind_buf = {cancel_jmp_buf = {{jmp_buf = {94199544867104, 284094712732557841, 94199535158800, 140734795554240, 0, 0, -283743969626709487, -253704532302519791}, mask_was_saved = 0}}, priv = {pad = {0x0, 0x0, 0x1, 0x7fff5f7f1dc8}, data = {prev = 0x0, cleanup = 0x0, canceltype = 1}}}
        not_first_call = <optimized out>
#6  0x000055ac89d4c63e in _start () at ./sql/mysqld.cc:4321
No symbol table info available.

(gdb) set logging off
Done logging to /var/lib/mysql/gdb_output.txt.

我们可以将文件复制到容器外部,用于错误报告,同时提供触发错误的 SQL 和进程信息,以及 SELECT VERSION() 信息。

$ podman cp mdb105:/var/lib/mysql/gdb_output.txt /tmp/

变体:将调试器附加到正在运行的实例

在上面的示例中,我们将 gdb 调试器作为容器的主进程运行。另一种方法是运行容器并在需要时附加 gdb 调试器。这具有无需初始化步骤的优点

$ podman run --cap-add CAP_SYS_PTRACE -d --rm --name mdb105 -e MARIADB_DATABASE=test -e MARIADB_USER=bob -e MARIADB_PASSWORD=pluto -e MARIADB_RANDOM_ROOT_PASSWORD=1 quay.io/mariadb-foundation/mariadb-debug:10.5
e4aafa995837524264bd0db024ef808f1efb394528264feb0d059bb9dbe980c0

然后在容器内以用户 mysql 执行调试器,并附加到进程 1(主进程 – mariadbd)

$ podman exec -ti --user mysql mdb105 gdb -p 1
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
...
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
--Type <RET> for more, q to quit, c to continue without paging--
0x00007f2d3c1d7aff in __GI___poll (fds=fds@entry=0x7ffe82371350, nfds=nfds@entry=2, timeout=timeout@entry=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
29	../sysdeps/unix/sysv/linux/poll.c: No such file or directory.
(gdb) c

由于它已经在运行,我们使用 c 来继续执行,并且触发/记录信息的先前步骤是相同的。

结论

这只是描述如何使用调试容器的开始。开发者在错误报告中可能会要求在调试器中打印其他信息。您可以通过将非容器数据目录作为卷传递给容器来使用调试容器。调试器还可以在初始化时用于查找硬件/内核不兼容性,就像在 Docker Library 问题 #338 中所做的那样。

我们欢迎关于如何使此容器镜像更有用,或您对此的其他创新用途的反馈。