MariaDB调试容器的使用介绍

109 阅读5分钟

MariaDB确实有bug。用户有时会看到它们。有时开发人员看了很久的bug报告和代码,仍然看不出情况是如何发生的。开发人员在分析过程中会问这样的问题。

  • 我想知道这个问题是否已经在{未发布的版本}中被修复了?但是我怎么能让用户来测试呢?
  • 我能否让用户得到好的堆栈跟踪,以帮助更好地理解这个问题?但用户有时会觉得这很难。
  • 这个错误到底是在什么硬件和内核配置上发生的?我又该如何重现这个问题呢?
  • 我想有一份这个数据的副本,以尝试一些潜在的破坏性操作来更好地理解这个问题?但是,用户数据并不总是可以共享的,也不容易复制。
  • 一个关于MariaDB进入这种状态的rr记录会帮助我更好地理解这个问题?
  • 我知道如果我在本地攻击这个问题会得到什么调试器信息,我可以让用户来做吗?

用户也会对MariaDB提出一些棘手的问题,比如

  • MariaDB的CPU时间都花在哪里了?
  • 我可以记录下MariaDB在做什么的火焰图吗?

MariaDB基金会开发了容器镜像quay.io/mariadb-fou…,以帮助给开发者带来更多的问题信息,以解决MariaDB的问题,也让开发者以不会消耗大量时间的方式向用户询问更多的信息。

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

quay.io/mariadb-foundation/mariadb-debug是一个容器镜像。它基于quay.io/mariadb-foundation/mariadb-devel镜像根据之前的博客镜像,它是当前MariaDB的最新开发版本,支持每个主要的稳定分支(以及不稳定的10.8分支)。这个镜像与你可能正在使用的Docker Librarydocker.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. 运行quay.io/mariadb-foundation/mariadb-devel:10.5镜像,就像你运行之前的10.5镜像一样。例如podman run -v {volume}:/var/lib/mysql -p quay.io/mariadb-foundation/mariadb-devel:10.5
  4. 运行你之前的SQL/测试,看看是否观察到相同的行为。

我需要backtrace来做一个bug报告

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

  1. 创建数据目录,通过停止当前容器记下数据卷,/var/lib/mysql
  2. 启动调试容器。注意所有的调试都需要CAP_SYS_PTRACE 的能力。
  3. 触发崩溃
  4. 记录回溯
  5. 将回溯记录复制到容器外,用于提交错误报告。

下面是再现现有BUGMDEV-26412的步骤指南。

创建数据目录

这将在新卷上创建一个空的数据目录db105 。下面使用Docker Librarymariadb镜像来创建,但是quay.io/mariadb-foundation/mariadb-develquay.io/mariadb-foundatin/mariadb-debug镜像也能达到同样的效果。

$ podman volume create db105

启动调试容器

这里我们通过在/var/lib/mysql 上使用volumedb105 来启动前面的debug容器,我们使用--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 

上面的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库问题#338中所进行的那样。

我们欢迎大家就如何使这个容器镜像更有用,或者你对它的其他创新使用提出反馈。