使用新调试功能探测Nginx内核

jopen 9年前

 

Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的一个高性能HTTP和反向代理服务器,第一个公开版本0.1.0发布于2004年10月4日,最新的版本已经到1.9.3。Nginx也是一个 IMAP/POP3/SMTP 服务器,还可作为 Web 服务器、负载均衡服务器。Nginx将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。

从Nginx关于调试工具的 官方网页 来看,在Nginx最近的版本中,增加了很多有用的调试功能,通过使用GDB从运行的服务器中提取信息。虽然现在并不建议在生产环境的Nginx上使用GDB,但其在开发或测试环境下是非常有用的。

本文要介绍的新的功能包括了建立Nginx 时的--with-debug配置选项。为了确定文件是否内置了这个选项,运行nginx -V命令:

# nginx -V  nginx version nginx/1.9.3 built by gcc 5.1.1 20150618 (Red Hat 5.1.1-4) (GCC)   configure arguments: --with-debug --prefix-/opt/nginx-debug

在内存中编写调试日志

第一个新功能是在内存中的调试日志。Nginx调试日志对于挖掘复杂的问题非常有用。在以前的版本当中,调试日志存储在磁盘空中,但由于日志文件可能会迅速变得非常大,导致占用大量的磁盘空间。

Nginx 1.7.11增加了使用循环缓冲区(cyclic memory buffer)直接访问内存的能力,使得调试日志完全不用使用磁盘存储。有关详细信息,请参阅Nginx文档中<a href="http://nginx.org/en/docs/debugging_log.html?&amp; _ga=1.228176989.1157264337.1438403117#memory">循环内存缓冲区</a>。要启用一个 32 MB的缓冲区调试日志记录,在Nginx配置文件的主环境下,使用error_log指令:

error_log memory:32m debug;

可以使用GDB从内存中提取日志。 同时可以到<a href="https://gist.github.com/LinuxJedi/ae68bf582068395de317">此处< /a>下载一个GitHub Gist,里面包含了文章中介绍的例子所需要的所有东西。下载并保存为Nginx.gdb,或在home目录下重新命名为.gdbinit,让其自动加载。

首先,运行以下命令可以显示Nginx工作进程的进程ID:

# pgrep -f "nginx: worker"

找到探测对象的进程ID。在这个例子中,有一个ID为20192的工作进程,要启动GDB并加载工作进程,运行以下命令(请注意,当GDB运行的时候,工作进程被暂停):

# sudo gdb --pid 20192

加载从GitHub Gist上下载的脚本并转储调试日志,运行以下命令:

(gdb) source nginx.gdb  (gdb) ddl

GDB创建了一个名为debug_log.txt的文件,里面包含了使用error_log指令分配的内存转储,因此在这个例子中的文件大小为32 MB。可以轻松使用下面的sed命令截断它。在大多数情况下,不需要这样做,如果日志文件已经打包,该命令无效:

# sed -i 's/[[:space:]]*$//' debug_log.txt

显然,在探测日志的时候让工作进程暂停并不总是一个好办法。所以,可以将暂停的时间限制得非常短,如果超出时间限制,告诉GDB要探测日志并让工作进程立刻停止,这种办法很常用,更多细节可以参考<a href="http://poormansprofiler.org/">这里</a>。

# gdb --pid 20192 -iex "source nginx.gdb" -ex "ddl" –batch

转储活动Nginx配置

在Nginx 1.9.2及以后的版本中,当Nginx使用--with-debug配置选项进行建立的时候,整个配置存储在内存中,从而可以使用GDB从主进程中提取配置。这可能是很有用的,可以用来核实哪些配置已经被加载,如果磁盘上的版本已经被意外删除或覆盖的时候可以帮助恢复之前的配置。

和前面一样,<a href="https://gist.github.com/LinuxJedi/ae68bf582068395de317">GitHub Gist</a>中的nginx.gdb文件也包含了运行内存转储的功能。因此,首先载入GDB:

# sudo gdb --pid `pgrep -f "nginx: master"`

然后,运行以下命令来转储配置。

(gdb) source nginx.gdb (gdb) dcfg

在转储它们的时候,GDB输出文件的名称。例如,这个例子中的输出,每个文件名的末尾可能有一些乱码,这是因为当字符串没有NUL终止的时候,GDB的printf函数不是一直运行得很好。最后的结果是一个包含完整活动配置的名为nginx_conf.txt的文件。

<img _href="img://null" _p="true" src="http://cdn2.infoqstatic.com/statics_s2_20150819-0313/resource/articles/Nginx-GDB/zh/resources/gdb-dcfg-output.png" width="550" />

在使用调试日志的时候,还可以使用批处理模式转储配置:

# gdb --pid `pgrep -f "nginx: master"` -iex "source nginx.gdb" -ex "dcfg" –batch

用core文件使用GDB

文章中涵盖的所有东西都可以用来帮助使用core文件调试问题产生的原因:

# gdb --core core.9491 nginx

无论是在这篇文章中描述的dcfg函数还是ddl函数,都可以用作core文件。如果需要在core文件生成的时候找到NGINX服务器的配置,或者需要为导致core文件生成的事件找到调试信息,这可能会有用。

对于提取关于Nginx内核的信息,转储调试日志和配置都是非常有用的方法,当然也可以通过调整GDB的脚本,在文章介绍的技术的基础上进行拓展。例如,转储配置时,可以转储每个加载的配置文件到一个单独的输出文件中,而不是转储所有东西到单独的一个文件中。文件名的长度用文件名自身存储,所以要使用它们的时候,应该有一种方法要么复制它们要么截断它们,标准API脚本是一种非常好的方式,GDB最近的版本开始支持Python脚本,也提供了一种选择。

值得重申的是,这些技术仅仅建议在开发和测试环境中使用。暂停Nginx过程不是一个好主意,尤其是在生产环境下。